Merge lp:~exarkun/divmod.org/remove-epsilon-1325289 into lp:divmod.org

Proposed by Jean-Paul Calderone
Status: Merged
Approved by: Tristan Seligmann
Approved revision: 2750
Merged at revision: 2750
Proposed branch: lp:~exarkun/divmod.org/remove-epsilon-1325289
Merge into: lp:divmod.org
Diff against target: 11136 lines (+1/-10718)
80 files modified
Divmod.pth (+1/-2)
Epsilon/.coveragerc (+0/-8)
Epsilon/LICENSE (+0/-20)
Epsilon/MANIFEST.in (+0/-3)
Epsilon/NAME.txt (+0/-13)
Epsilon/NEWS.txt (+0/-113)
Epsilon/README (+0/-9)
Epsilon/bin/benchmark (+0/-4)
Epsilon/bin/certcreate (+0/-5)
Epsilon/doc/amp-auth.xhtml (+0/-115)
Epsilon/doc/amp-routes.xhtml (+0/-180)
Epsilon/doc/index.xhtml (+0/-21)
Epsilon/doc/listings/amp/amp_auth_client.py (+0/-42)
Epsilon/doc/listings/amp/amp_auth_server.py (+0/-76)
Epsilon/doc/listings/amp/auth_client.py (+0/-37)
Epsilon/doc/listings/amp/auth_server.py (+0/-77)
Epsilon/doc/listings/amp/route_client.py (+0/-60)
Epsilon/doc/listings/amp/route_server.py (+0/-37)
Epsilon/doc/listings/amp/route_setup.py (+0/-49)
Epsilon/doc/stylesheet.css (+0/-129)
Epsilon/doc/template.tpl (+0/-23)
Epsilon/epsilon/__init__.py (+0/-8)
Epsilon/epsilon/_version.py (+0/-1)
Epsilon/epsilon/ampauth.py (+0/-312)
Epsilon/epsilon/amprouter.py (+0/-205)
Epsilon/epsilon/asplode.py (+0/-34)
Epsilon/epsilon/caseless.py (+0/-135)
Epsilon/epsilon/cooperator.py (+0/-32)
Epsilon/epsilon/descriptor.py (+0/-147)
Epsilon/epsilon/expose.py (+0/-141)
Epsilon/epsilon/extime.py (+0/-980)
Epsilon/epsilon/hotfix.py (+0/-81)
Epsilon/epsilon/hotfixes/deferredgenerator_tfailure.py (+0/-59)
Epsilon/epsilon/hotfixes/delayedcall_seconds.py (+0/-160)
Epsilon/epsilon/hotfixes/filepath_copyTo.py (+0/-403)
Epsilon/epsilon/hotfixes/internet_task_clock.py (+0/-38)
Epsilon/epsilon/hotfixes/loopbackasync_reentrancy.py (+0/-26)
Epsilon/epsilon/hotfixes/plugin_package_paths.py (+0/-42)
Epsilon/epsilon/hotfixes/proto_helpers_stringtransport.py (+0/-11)
Epsilon/epsilon/hotfixes/timeoutmixin_calllater.py (+0/-60)
Epsilon/epsilon/hotfixes/trial_assertwarns.py (+0/-61)
Epsilon/epsilon/iepsilon.py (+0/-25)
Epsilon/epsilon/juice.py (+0/-1009)
Epsilon/epsilon/liner.py (+0/-67)
Epsilon/epsilon/modal.py (+0/-131)
Epsilon/epsilon/pending.py (+0/-26)
Epsilon/epsilon/process.py (+0/-67)
Epsilon/epsilon/react.py (+0/-35)
Epsilon/epsilon/remember.py (+0/-39)
Epsilon/epsilon/scripts/benchmark.py (+0/-581)
Epsilon/epsilon/scripts/certcreate.py (+0/-62)
Epsilon/epsilon/setuphelper.py (+0/-77)
Epsilon/epsilon/spewer.py (+0/-125)
Epsilon/epsilon/structlike.py (+0/-183)
Epsilon/epsilon/test/iosim.py (+0/-110)
Epsilon/epsilon/test/mantissa-structure.py (+0/-67)
Epsilon/epsilon/test/test_ampauth.py (+0/-430)
Epsilon/epsilon/test/test_amprouter.py (+0/-319)
Epsilon/epsilon/test/test_benchmark.py (+0/-426)
Epsilon/epsilon/test/test_caseless.py (+0/-291)
Epsilon/epsilon/test/test_descriptor.py (+0/-172)
Epsilon/epsilon/test/test_expose.py (+0/-321)
Epsilon/epsilon/test/test_extime.py (+0/-499)
Epsilon/epsilon/test/test_juice.py (+0/-287)
Epsilon/epsilon/test/test_modes.py (+0/-60)
Epsilon/epsilon/test/test_process.py (+0/-74)
Epsilon/epsilon/test/test_react.py (+0/-191)
Epsilon/epsilon/test/test_remember.py (+0/-90)
Epsilon/epsilon/test/test_setuphelper.py (+0/-63)
Epsilon/epsilon/test/test_structlike.py (+0/-186)
Epsilon/epsilon/test/test_unrepr.py (+0/-7)
Epsilon/epsilon/test/test_version.py (+0/-19)
Epsilon/epsilon/test/test_view.py (+0/-457)
Epsilon/epsilon/test/utils.py (+0/-84)
Epsilon/epsilon/unrepr.py (+0/-54)
Epsilon/epsilon/view.py (+0/-53)
Epsilon/requirements-testing.txt (+0/-2)
Epsilon/setup.py (+0/-35)
Epsilon/testhotfix (+0/-8)
Epsilon/tox.ini (+0/-27)
To merge this branch: bzr merge lp:~exarkun/divmod.org/remove-epsilon-1325289
Reviewer Review Type Date Requested Status
Tristan Seligmann Approve
Review via email: mp+224912@code.launchpad.net

Description of the change

To post a comment you must log in.
Revision history for this message
Tristan Seligmann (mithrandi) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Divmod.pth'
--- Divmod.pth 2014-06-08 12:14:35 +0000
+++ Divmod.pth 2014-06-28 16:02:12 +0000
@@ -1,7 +1,6 @@
1# -*- test-case-name: axiom,combinator,epsilon,xmantissa,xquotient,reverend,sine,hyperbola -*-1# -*- test-case-name: axiom,combinator,xmantissa,xquotient,reverend,sine,hyperbola -*-
2Axiom2Axiom
3Combinator3Combinator
4Epsilon
5Mantissa4Mantissa
6Quotient5Quotient
7Reverend6Reverend
87
=== removed directory 'Epsilon'
=== removed file 'Epsilon/.coveragerc'
--- Epsilon/.coveragerc 2014-01-13 11:18:20 +0000
+++ Epsilon/.coveragerc 1970-01-01 00:00:00 +0000
@@ -1,8 +0,0 @@
1[run]
2branch = True
3source =
4 epsilon
5
6[report]
7exclude_lines =
8 pragma: no cover
90
=== removed file 'Epsilon/LICENSE'
--- Epsilon/LICENSE 2005-12-10 22:31:51 +0000
+++ Epsilon/LICENSE 1970-01-01 00:00:00 +0000
@@ -1,20 +0,0 @@
1Copyright (c) 2005 Divmod Inc.
2
3Permission is hereby granted, free of charge, to any person obtaining
4a copy of this software and associated documentation files (the
5"Software"), to deal in the Software without restriction, including
6without limitation the rights to use, copy, modify, merge, publish,
7distribute, sublicense, and/or sell copies of the Software, and to
8permit persons to whom the Software is furnished to do so, subject to
9the following conditions:
10
11The above copyright notice and this permission notice shall be
12included in all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21\ No newline at end of file0\ No newline at end of file
221
=== removed file 'Epsilon/MANIFEST.in'
--- Epsilon/MANIFEST.in 2014-01-15 10:08:27 +0000
+++ Epsilon/MANIFEST.in 1970-01-01 00:00:00 +0000
@@ -1,3 +0,0 @@
1include LICENSE
2include NAME.txt
3include NEWS.txt
40
=== removed file 'Epsilon/NAME.txt'
--- Epsilon/NAME.txt 2005-08-27 23:09:07 +0000
+++ Epsilon/NAME.txt 1970-01-01 00:00:00 +0000
@@ -1,13 +0,0 @@
1
2See: http://mathworld.wolfram.com/Epsilon.html
3
4The constant 'epsilon' is a value that is as close as possible to zero without
5being zero. It is frequently used by computer scientists to refer to values
6which are negligeable.
7
8Divmod Epsilon is named for that because it is a small body of code upon which
9all of our other projects depend. It has no particular theme associated with
10it, except to remain small and lightweight, and enforce certain conventions and
11to provide common conveniences that do not belong in any lower level of
12infrastructure.
13
140
=== removed file 'Epsilon/NEWS.txt'
--- Epsilon/NEWS.txt 2014-01-15 10:21:17 +0000
+++ Epsilon/NEWS.txt 1970-01-01 00:00:00 +0000
@@ -1,113 +0,0 @@
10.7.0 (2014-01-15):
2 Major:
3
4 - Only Python 2.6 and 2.7 are supported now. 2.4, 2.5 is deprecated.
5 - setup.py now uses setuptools, and stores its dependencies. This
6 means you no longer need to manually install dependencies.
7 - setup.py no longer requires Twisted for egg_info, making it easier
8 to install Epsilon using pip.
9 - Significant improvements to PyPy support. PyPy is now a supported
10 platform, with CI support.
11 - epsilon.release is now removed. It relied on a bunch of machinery
12 specific to divmod that no longer existed.
13 - epsilon.sslverify is now removed. Use twisted.internet.ssl instead.
14 - epsilon.asTwistedVersion takes a string version ("1.2.3") and
15 turns it into a twisted.python.versions.Version.
16
17 Minor:
18
19 - Several deprecation warnings have been cleaned up.
20
210.6.0 (2009-11-25):
22 - Disable loopback hotfix on Twisted 8.2 and newer.
23 - Remove the implementation of Cooperator and use Twisted's implementation
24 instead.
25 - Use Twisted's deferLater implementation.
26 - Add a service for communicating via stdio.
27 - Add a `precision` argument to `Time.asHumanly` to control the precision
28 of the returned string.
29
300.5.12 (2008-12-09):
31 - Added support for AMP authentication via one-time pads.
32
330.5.11 (2008-10-02):
34 - epsilon.amprouter added, providing support for multiplexing
35 unrelated AMP communications over the same connection.
36
370.5.10 (2008-08-12):
38 - Added the epsilon.caseless module, with case-insensitive string
39 wrappers.
40 - Better repr() for epsilon.structlike.record added.
41 - epsilon.juice now uses twisted.internet.ssl instead of epsilon.sslverify.
42
430.5.9 (2008-01-18):
44
450.5.8 (2007-11-27):
46 - extime.Time.asHumanly() no longer shows a time of day for all-day timestamps.
47
480.5.7 (2007-04-27):
49 - view.SlicedView added, allowing slicing and indexing of large
50 sequences without copying.
51
520.5.6 (2006-11-20):
53 - Added a --quiet option to Epsilon's certcreate and use it in a few unit
54 tests to avoid spewing garbage during test runs.
55
560.5.5 (2006-10-21):
57 - extime.Time now accepts RFC2822-like dates with invalid fields: it
58 rounds them to the nearest valid value.
59
600.5.4 (2006-10-17):
61 - extime.Time now accepts RFC2822-like dates with no timezone.
62
630.5.3 (2006-09-20):
64 - structlike.Record now raises TypeError on unexpected args.
65
660.5.2 (2006-09-12):
67 - extime.Time now avoids time_t overflow bugs.
68
690.5.1 (2006-06-22):
70 - Added hotfix for twisted.test.proto_helpers.StringTransport.
71
720.5.0 (2006-06-12):
73 - Replaced '%y' with '%Y' in Time.asHumanly() output - the year is now
74 four digits, rather than two.
75 - Added new 'epsilon.structlike' functionality for simple record.
76 - All uses of defer.wait and deferredResult were removed from the tests.
77 - Added epsilon.juice, an asynchronous messaging protocol slated for
78 inclusion in Twisted. Improved a few features, such as the repr() of
79 JuiceBox instances. This was moved from Vertex.
80 - Added epsilon.sslverify, a set of utilities for dealing with PyOpenSSL
81 using simple high-level objects, performing operations such as signing and
82 verifying certificates. This was also moved from Vertex, and slated for
83 inclusion in Twisted.
84 - Added epsilon.spewer, a prettier version of the spewer in
85 twisted.python.util.
86 - Added "benchmark" tool for measuring and reporting run-times of python
87 programs.
88
890.4.0 (2005-12-20):
90 - Disabled crazy sys.modules hackery in test_setuphelper
91 - Added module for creating a directory structure from a string template
92 - Added support for 'now' to Time.fromHumanly()
93 - Added a structured "hotfix" system to abstract and formalize monkey
94 patches and version testing logic away from code which requires it.
95
960.3.2 (2005-11-05):
97 - Added automatic support for Twisted plugins to autosetup
98
990.3.1 (2005-11-02):
100 - Removed bogus dependency on Axiom.
101
1020.3.0 (2005-11-02):
103 - Added SchedulingService, an IService implementation, to epsilon.cooperator
104 - Added autosetup, a utility to actually include files in distutils releases,
105 to epsilon.setuphelper
106
1070.2.1 (2005-10-25):
108 - Added 'short()' to epsilon.versions.Version
109 - fixed setup.py to use epsilon.version.short() rather than static string.
110
1110.2.0 (2005-10-25):
112 - Added epsilon.modal.ModalType, metaclass for writing classes that
113 behave in some respects like state machines
1140
=== removed file 'Epsilon/README'
--- Epsilon/README 2006-06-14 11:54:41 +0000
+++ Epsilon/README 1970-01-01 00:00:00 +0000
@@ -1,9 +0,0 @@
1
2Divmod Epsilon
3==============
4
5Epsilon is a set of utility modules, commonly used by all Divmod projects.
6
7This is intended mainly as a support package for code used by Divmod projects,
8and not as an external library. However, it contains many useful modules and
9you can feel free to use them!
100
=== removed directory 'Epsilon/bin'
=== removed file 'Epsilon/bin/benchmark'
--- Epsilon/bin/benchmark 2006-05-19 15:23:46 +0000
+++ Epsilon/bin/benchmark 1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
1#!/usr/bin/python
2
3from epsilon.scripts import benchmark
4benchmark.main()
50
=== removed file 'Epsilon/bin/certcreate'
--- Epsilon/bin/certcreate 2006-06-08 19:22:15 +0000
+++ Epsilon/bin/certcreate 1970-01-01 00:00:00 +0000
@@ -1,5 +0,0 @@
1#!/usr/bin/python
2# Copyright 2005 Divmod, Inc. See LICENSE file for details
3
4from epsilon.scripts import certcreate
5certcreate.main()
60
=== removed directory 'Epsilon/doc'
=== removed file 'Epsilon/doc/amp-auth.xhtml'
--- Epsilon/doc/amp-auth.xhtml 2008-11-27 07:37:39 +0000
+++ Epsilon/doc/amp-auth.xhtml 1970-01-01 00:00:00 +0000
@@ -1,115 +0,0 @@
1<?xml version="1.0"?>
2<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
3 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
5<html xmlns="http://www.w3.org/1999/xhtml">
6 <head>
7 <title>AMP Authentication</title>
8 </head>
9 <body>
10 <h1>AMP Authentication</h1>
11
12 <h2>Overview</h2>
13
14 <p>
15 <code class="API">epsilon.ampauth</code> integrates Twisted Cred with
16 <code>twisted.protocols.amp</code>, providing support for selecting a
17 <code>IBoxReceiver</code> based on the result of a Cred login.
18 </p>
19
20 <p>
21 Readers should familiarize themselves with the following concepts in
22 order to understand all sections of this document:
23 </p>
24
25 <ul>
26 <li>
27 Twisted <a
28 href="http://twistedmatrix.com/projects/core/documentation/howto/clients.html">TCP
29 clients</a> and <a
30 href="http://twistedmatrix.com/projects/core/documentation/howto/servers.html">TCP
31 servers</a>
32 </li>
33 <li>
34 <a href="http://twistedmatrix.com/projects/core/documentation/howto/defer.html">
35 Using Deferreds
36 </a>
37 </li>
38 <li>
39 Twisted <code class="API" base="twisted.protocols.amp">AMP</code>
40 </li>
41 <li>
42 <a href="http://twistedmatrix.com/projects/core/documentation/howto/cred.html">
43 Twisted Cred
44 </a>
45 </li>
46 </ul>
47
48 <h2>Servers</h2>
49
50 <p>
51 <code class="API" base="epsilon.ampauth">CredAMPServerFactory</code>
52 is a factory for the <code class="API"
53 base="epsilon.ampauth">CredReceiver</code> protocol, an
54 <code>AMP</code> subclass which implements responders for commands
55 which allow a client to prove their identity. It uses a
56 <code>Portal</code> to handle these commands and retrieve an <code
57 class="API" base="twisted.protocols.amp">IBoxReceiver</code> which
58 will be used to handle all further AMP boxes it receives.
59 </p>
60
61 <a href="listings/amp/auth_server.py" class="py-listing">
62 AMP server with authentication
63 </a>
64
65 <p>
66 <code>Add</code> and <code>Adder</code> together define a simple AMP
67 protocol for adding two integers together. <code>AdditionRealm</code>
68 provides the necessary integration between this AMP protocol and Cred,
69 creating new <code>Adder</code> instances whenever an
70 <code>IBoxReceiver</code> is requested - which will be whenever a client
71 attempts to authenticate itself to the server.
72 </p>
73
74 <h2>Clients</h2>
75
76 <p>
77 AMP clients can authenticate with an AMP server using <code class="API"
78 base="epsilon.ampauth">login</code>. <code>login</code> takes a
79 connected AMP instance and a credentials object as arguments and returns
80 a <code>Deferred</code> which fires when authentication finishes.
81 </p>
82
83 <a href="listings/amp/auth_client.py" class="py-listing">
84 Authenticating AMP client
85 </a>
86
87 <p>
88 The TCP connection is set up as usual, and the <code>Add</code> command
89 is also issued in the usual way. The only change from a normal AMP
90 client is the use of <code>login</code> after a connection has been set
91 up but before any commands are issued.
92 </p>
93
94 <h2>One-Time Pad Authentication</h2>
95
96 <p>
97 <code class="API">epsilon.ampauth</code> includes an <code class="API"
98 base="twisted.cred.checkers">CredentialsChecker</code> for validating
99 one-time pads: <code class="API"
100 base="epsilon.ampauth">OneTimePadChecker</code>. If this checker is
101 registered with the portal, clients may use the <code class="API"
102 base="epsilon.ampauth">OTPLogin</code> command to authenticate.
103 </p>
104
105 <a href="listings/amp/amp_auth_server.py" class="py-listing">
106 AMP server with OTP authentication
107 </a>
108
109 <p></p>
110
111 <a href="listings/amp/amp_auth_client.py" class="py-listing">
112 OTP-authenticating AMP client
113 </a>
114 </body>
115</html>
1160
=== removed file 'Epsilon/doc/amp-routes.xhtml'
--- Epsilon/doc/amp-routes.xhtml 2008-08-29 16:02:56 +0000
+++ Epsilon/doc/amp-routes.xhtml 1970-01-01 00:00:00 +0000
@@ -1,180 +0,0 @@
1<?xml version="1.0"?>
2<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
3 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
5<html xmlns="http://www.w3.org/1999/xhtml">
6 <head>
7 <title>AMP Routes</title>
8 </head>
9 <body>
10 <h1>AMP Routes</h1>
11
12 <h2>Overview</h2>
13
14 <p>
15 Normally, an AMP connection is between two <code class="API"
16 base="twisted.protocols.amp">AMP</code> instances; each instance receives
17 all AMP boxes sent by the other side and handles them by interpreting
18 them as commands, responses to commands, or in some other way. This
19 typically means that the logic for handling boxes on each side of the
20 connection is completely defined by a single object. Sometimes it is
21 useful to allow multiple objects, perhaps of different types, to
22 participate in defining this logic.
23 </p>
24
25 <p>
26 <code>epsilon.amprouter</code> implements utilities which allow an
27 arbitrary number of objects, providers of <code>IBoxReceiver</code> (for
28 example, instances of <code>AMP</code>), to define how received AMP boxes
29 are interpreted. This is useful to multiplex unrelated <code>AMP</code>
30 instances over a single TCP connection, to split up a single AMP protocol
31 into multiple simpler protocols, and for many other purposes.
32 </p>
33
34 <p>
35 Readers should familiarize themselves with the following concepts in
36 order to understand all sections of this document:
37 </p>
38
39 <ul>
40 <li>
41 Twisted <a
42 href="http://twistedmatrix.com/projects/core/documentation/howto/clients.html">TCP
43 clients</a> and <a
44 href="http://twistedmatrix.com/projects/core/documentation/howto/servers.html">TCP
45 servers</a>
46 </li>
47 <li>
48 <a href="http://twistedmatrix.com/projects/core/documentation/howto/defer.html">
49 Using Deferreds
50 </a>
51 </li>
52 <li>
53 Twisted <code class="API" base="twisted.protocols.amp">AMP</code>
54 </li>
55 </ul>
56
57 <h2>Routers</h2>
58
59 <p>
60 When working with routes, the object primarily of interest will be a <a
61 class="API" base="epsilon.amprouter">Router</a> instance. Each AMP
62 client and server will have a <code>Router</code> instance which they can
63 use to create new routes. They will use its <code class="API"
64 base="epsilon.amprouter">Router.bindRoute</code> method to set up
65 whatever routes they require.
66 </p>
67
68 <h2>Servers</h2>
69
70 <p>
71 <code>epsilon.amprouter</code> does not define a command for creating new
72 routes because different applications have different requirements for how
73 new routes are set up. An application may want to negotiate about the
74 <code>IBoxReceiver</code> implementation which is associated with a
75 route, it may want to supply initial arguments to that object, it may
76 want to do version negotiation, and so on. The first thing an
77 application using routes must do, then, is to define a way to create new
78 routes. Consider the following example which allows routes to be created
79 with a <code>NewRoute</code> AMP command and associates them with a
80 parameterized <code>IBoxReceiver</code> implementation.
81 </p>
82
83 <a href="listings/amp/route_setup.py" class="py-listing">
84 Creation of new routes
85 </a>
86
87 <p>
88 <code>AMPRouteServerFactory.buildProtocol</code> creates new
89 <code>RoutingAMP</code> instances, each with a new <code>Router</code>.
90 The <code>Router</code> instance will become the <code>RoutingAMP</code>
91 instance's <code>boxReceiver</code> attribute. This is important for two
92 reasons. First, it allows the router to work by causing all AMP boxes
93 received from the connection to be delivered to the router to be
94 dispatched appropriately. Second, it gives the <code>RoutingAMP</code>
95 instance a reference to the <code>Router</code> instance; this is
96 necessary so that new routes can be created.
97 </p>
98
99 <p>
100 After creating the <code>Router</code> and <code>RoutingAMP</code>,
101 <code>buildProtocol</code> also sets up the <code>RoutingAMP</code>
102 instance to be the default receiver by binding it to the
103 <code>None</code>. All AMP boxes without routing information will be
104 delivered to the default receiver. This is important because it allows
105 the <code>NewRoute</code> command to be handled by the
106 <code>RoutingAMP</code> instance.
107 </p>
108
109 <p>
110 <code>RoutingAMP</code>'s <code>NewRoute</code> responder uses
111 <code>self.boxReceiver</code>, the <code>Router</code> instance provided
112 by the factory, to <em>bind</em> the return value of
113 <code>self.factory.routeProtocol()</code> to a new route. Then, it
114 connects the route to the identifier specified in the
115 <code>NewRoute</code> command. Finally, it returns the identifier of the
116 route it has just created. Once this has happened, the route is
117 completely set up on the server.
118 </p>
119
120 <p>
121 Finally, the <code>connect</code> function wraps up the necessary calls
122 to routing methods and a use of the <code>NewRoute</code> command to form
123 the client side of the setup.
124 </p>
125
126 <p>
127 First, let's look at an example of using <code>AMPRouteServerFactory</code> and
128 <code>RoutingAMP</code> to run a server.
129 </p>
130
131 <a href="listings/amp/route_server.py" class="py-listing">
132 Routed counters server
133 </a>
134
135 <p>
136 In this example, a simple counting protocol is hooked up to the server.
137 Each route which is created is associated with a new instance of this
138 protocol. The protocol does just one simple thing, it keeps track of how
139 many times the <code>Count</code> command is issued to it and returns
140 this value in the response to that command.
141 </p>
142
143 <p>
144 Next we'll look at how a client can connect to this server, create new
145 routes, and issue commands over them.
146 </p>
147
148 <h2>Clients</h2>
149
150 <p>
151 Just as servers must, clients must first set up a route before they can
152 send boxes over it. A client uses the same methods as the server,
153 <code>Router.bindRoute</code> and <code>Route.connectTo</code>, to set up
154 a new route. Here's an example which makes one TCP connection to an AMP
155 server, sets up three routes, and then issues multiple commands over each
156 of them.
157 </p>
158
159 <a href="listings/amp/route_client.py" class="py-listing">
160 Routed counters client
161 </a>
162
163 <p>
164 Note first how <code>main</code> creates an <code>AMP</code> with a
165 <code>Router</code> instance. Note also how <code>makeRoutes</code>
166 binds and connects the protocol to the default route. This mirrors the
167 route setup which was done on the server and is necessary for the same
168 reasons.
169 </p>
170
171 <p>
172 Once an AMP connection is set up and the default route is bound,
173 <code>makeRoutes</code> uses the previously defined <code>connect</code>
174 function to establish three new routes. Each route is associated with a
175 <code>CountClient</code> instance which will issue several count commands
176 and report the results. The results of each command are tracked so that
177 when they have all been received the client can exit.
178 </p>
179 </body>
180</html>
1810
=== removed file 'Epsilon/doc/index.xhtml'
--- Epsilon/doc/index.xhtml 2008-08-29 16:02:56 +0000
+++ Epsilon/doc/index.xhtml 1970-01-01 00:00:00 +0000
@@ -1,21 +0,0 @@
1<?xml version="1.0"?>
2<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
3 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
5<html xmlns="http://www.w3.org/1999/xhtml">
6 <head>
7 <title>Index</title>
8 </head>
9 <body>
10 <h1>Index</h1>
11
12 <ul class="toc">
13 <li>
14 <a href="amp-auth.xhtml">AMP Authentication</a>
15 </li>
16 <li>
17 <a href="amp-routes.xhtml">AMP Routes</a>
18 </li>
19 </ul>
20 </body>
21</html>
220
=== removed directory 'Epsilon/doc/listings'
=== removed directory 'Epsilon/doc/listings/amp'
=== removed file 'Epsilon/doc/listings/amp/amp_auth_client.py'
--- Epsilon/doc/listings/amp/amp_auth_client.py 2008-11-27 07:37:39 +0000
+++ Epsilon/doc/listings/amp/amp_auth_client.py 1970-01-01 00:00:00 +0000
@@ -1,42 +0,0 @@
1# Copyright (c) 2008 Divmod. See LICENSE for details.
2
3"""
4An AMP client which connects to and authenticates with an AMP server using OTP,
5then issues a command.
6"""
7
8from twisted.internet.protocol import ClientCreator
9from twisted.cred.credentials import UsernamePassword
10from twisted.protocols.amp import AMP
11
12from epsilon.react import react
13from epsilon.ampauth import OTPLogin
14
15from auth_server import Add
16
17
18def add(proto):
19 return proto.callRemote(Add, left=17, right=33)
20
21
22def display(result):
23 print result
24
25
26def otpLogin(client):
27 client.callRemote(OTPLogin, pad='pad')
28 return client
29
30
31def main(reactor):
32 cc = ClientCreator(reactor, AMP)
33 d = cc.connectTCP('localhost', 7805)
34 d.addCallback(otpLogin)
35 d.addCallback(add)
36 d.addCallback(display)
37 return d
38
39
40if __name__ == '__main__':
41 from twisted.internet import reactor
42 react(reactor, main, [])
430
=== removed file 'Epsilon/doc/listings/amp/amp_auth_server.py'
--- Epsilon/doc/listings/amp/amp_auth_server.py 2008-11-27 07:37:39 +0000
+++ Epsilon/doc/listings/amp/amp_auth_server.py 1970-01-01 00:00:00 +0000
@@ -1,76 +0,0 @@
1# Copyright (c) 2008 Divmod. See LICENSE for details.
2
3"""
4An AMP server which requires authentication of its clients before exposing an
5addition command.
6"""
7
8from sys import stdout
9
10from twisted.python.log import startLogging, msg
11from twisted.internet import reactor
12from twisted.cred.portal import Portal
13from twisted.protocols.amp import IBoxReceiver, Command, Integer, AMP
14
15from epsilon.ampauth import CredAMPServerFactory, OneTimePadChecker
16
17
18class Add(Command):
19 """
20 An example of an application-defined command which should be made available
21 to clients after they successfully authenticate.
22 """
23 arguments = [("left", Integer()),
24 ("right", Integer())]
25
26 response = [("sum", Integer())]
27
28
29
30class Adder(AMP):
31 """
32 An example of an application-defined AMP protocol, the responders defined
33 by which should only be available to clients after they have successfully
34 authenticated.
35 """
36 def __init__(self, avatarId):
37 AMP.__init__(self)
38 self.avatarId = avatarId
39
40
41 @Add.responder
42 def add(self, left, right):
43 msg("Adding %d to %d for %s" % (left, right, self.avatarId))
44 return {'sum': left + right}
45
46
47
48class AdditionRealm(object):
49 """
50 An example of an application-defined realm.
51 """
52 def requestAvatar(self, avatarId, mind, *interfaces):
53 """
54 Create Adder avatars for any IBoxReceiver request.
55 """
56 if IBoxReceiver in interfaces:
57 return (IBoxReceiver, Adder(avatarId), lambda: None)
58 raise NotImplementedError()
59
60
61
62def main():
63 """
64 Start the AMP server and the reactor.
65 """
66 startLogging(stdout)
67 checker = OneTimePadChecker({'pad': 0})
68 realm = AdditionRealm()
69 factory = CredAMPServerFactory(Portal(realm, [checker]))
70 reactor.listenTCP(7805, factory)
71 reactor.run()
72
73
74if __name__ == '__main__':
75 main()
76
770
=== removed file 'Epsilon/doc/listings/amp/auth_client.py'
--- Epsilon/doc/listings/amp/auth_client.py 2008-08-29 16:02:56 +0000
+++ Epsilon/doc/listings/amp/auth_client.py 1970-01-01 00:00:00 +0000
@@ -1,37 +0,0 @@
1# Copyright (c) 2008 Divmod. See LICENSE for details.
2
3"""
4An AMP client which connects to and authenticates with an AMP server, then
5issues a command.
6"""
7
8from twisted.internet.protocol import ClientCreator
9from twisted.cred.credentials import UsernamePassword
10from twisted.protocols.amp import AMP
11
12from epsilon.react import react
13from epsilon.ampauth import login
14
15from auth_server import Add
16
17
18def add(proto):
19 return proto.callRemote(Add, left=17, right=33)
20
21
22def display(result):
23 print result
24
25
26def main(reactor):
27 cc = ClientCreator(reactor, AMP)
28 d = cc.connectTCP('localhost', 7805)
29 d.addCallback(login, UsernamePassword("testuser", "examplepass"))
30 d.addCallback(add)
31 d.addCallback(display)
32 return d
33
34
35if __name__ == '__main__':
36 from twisted.internet import reactor
37 react(reactor, main, [])
380
=== removed file 'Epsilon/doc/listings/amp/auth_server.py'
--- Epsilon/doc/listings/amp/auth_server.py 2008-11-05 18:50:57 +0000
+++ Epsilon/doc/listings/amp/auth_server.py 1970-01-01 00:00:00 +0000
@@ -1,77 +0,0 @@
1# Copyright (c) 2008 Divmod. See LICENSE for details.
2
3"""
4An AMP server which requires authentication of its clients before exposing an
5addition command.
6"""
7
8from sys import stdout
9
10from twisted.python.log import startLogging, msg
11from twisted.internet import reactor
12from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
13from twisted.cred.portal import Portal
14from twisted.protocols.amp import IBoxReceiver, Command, Integer, AMP
15
16from epsilon.ampauth import CredAMPServerFactory
17
18
19class Add(Command):
20 """
21 An example of an application-defined command which should be made available
22 to clients after they successfully authenticate.
23 """
24 arguments = [("left", Integer()),
25 ("right", Integer())]
26
27 response = [("sum", Integer())]
28
29
30
31class Adder(AMP):
32 """
33 An example of an application-defined AMP protocol, the responders defined
34 by which should only be available to clients after they have successfully
35 authenticated.
36 """
37 def __init__(self, avatarId):
38 AMP.__init__(self)
39 self.avatarId = avatarId
40
41
42 @Add.responder
43 def add(self, left, right):
44 msg("Adding %d to %d for %s" % (left, right, self.avatarId))
45 return {'sum': left + right}
46
47
48
49class AdditionRealm(object):
50 """
51 An example of an application-defined realm.
52 """
53 def requestAvatar(self, avatarId, mind, *interfaces):
54 """
55 Create Adder avatars for any IBoxReceiver request.
56 """
57 if IBoxReceiver in interfaces:
58 return (IBoxReceiver, Adder(avatarId), lambda: None)
59 raise NotImplementedError()
60
61
62
63def main():
64 """
65 Start the AMP server and the reactor.
66 """
67 startLogging(stdout)
68 checker = InMemoryUsernamePasswordDatabaseDontUse()
69 checker.addUser("testuser", "examplepass")
70 realm = AdditionRealm()
71 factory = CredAMPServerFactory(Portal(realm, [checker]))
72 reactor.listenTCP(7805, factory)
73 reactor.run()
74
75
76if __name__ == '__main__':
77 main()
780
=== removed file 'Epsilon/doc/listings/amp/route_client.py'
--- Epsilon/doc/listings/amp/route_client.py 2008-08-29 16:02:56 +0000
+++ Epsilon/doc/listings/amp/route_client.py 1970-01-01 00:00:00 +0000
@@ -1,60 +0,0 @@
1# Copyright (c) 2008 Divmod. See LICENSE for details.
2
3import random
4
5from twisted.internet.defer import Deferred, gatherResults
6from twisted.internet.protocol import ClientCreator
7from twisted.protocols.amp import AMP
8
9from epsilon.react import react
10from epsilon.amprouter import Router
11
12from route_setup import connect
13from route_server import Count
14
15
16def display(value, id):
17 print id, value
18
19
20class CountClient(AMP):
21 def __init__(self, identifier):
22 AMP.__init__(self)
23 self.identifier = identifier
24 self.finished = Deferred()
25
26 def startReceivingBoxes(self, sender):
27 AMP.startReceivingBoxes(self, sender)
28
29 counts = []
30 for i in range(random.randrange(1, 5)):
31 d = self.callRemote(Count)
32 d.addCallback(display, self.identifier)
33 counts.append(d)
34 gatherResults(counts).chainDeferred(self.finished)
35
36
37
38def makeRoutes(proto, router):
39 router.bindRoute(proto, None).connectTo(None)
40
41 finish = []
42 for i in range(3):
43 client = CountClient(i)
44 finish.append(connect(proto, router, client))
45 finish.append(client.finished)
46 return gatherResults(finish)
47
48
49
50def main(reactor):
51 router = Router()
52 cc = ClientCreator(reactor, AMP, router)
53 d = cc.connectTCP('localhost', 7805)
54 d.addCallback(makeRoutes, router)
55 return d
56
57
58if __name__ == '__main__':
59 from twisted.internet import reactor
60 react(reactor, main, [])
610
=== removed file 'Epsilon/doc/listings/amp/route_server.py'
--- Epsilon/doc/listings/amp/route_server.py 2008-11-05 18:50:57 +0000
+++ Epsilon/doc/listings/amp/route_server.py 1970-01-01 00:00:00 +0000
@@ -1,37 +0,0 @@
1# Copyright (c) 2008 Divmod. See LICENSE for details.
2
3from sys import stdout
4
5from twisted.python.log import startLogging
6from twisted.protocols.amp import Integer, Command, AMP
7from twisted.internet import reactor
8
9from route_setup import AMPRouteServerFactory
10
11
12class Count(Command):
13 response = [('value', Integer())]
14
15
16
17class Counter(AMP):
18 _valueCounter = 0
19
20 @Count.responder
21 def count(self):
22 self._valueCounter += 1
23 return {'value': self._valueCounter}
24
25
26
27def main():
28 startLogging(stdout)
29 serverFactory = AMPRouteServerFactory()
30 serverFactory.routeProtocol = Counter
31 reactor.listenTCP(7805, serverFactory)
32 reactor.run()
33
34
35
36if __name__ == '__main__':
37 main()
380
=== removed file 'Epsilon/doc/listings/amp/route_setup.py'
--- Epsilon/doc/listings/amp/route_setup.py 2008-08-29 16:02:56 +0000
+++ Epsilon/doc/listings/amp/route_setup.py 1970-01-01 00:00:00 +0000
@@ -1,49 +0,0 @@
1# Copyright (c) 2008 Divmod. See LICENSE for details.
2
3import operator
4
5from twisted.internet.protocol import ServerFactory
6from twisted.protocols.amp import Unicode, Command, AMP
7
8from epsilon.amprouter import Router
9
10
11class NewRoute(Command):
12 arguments = [('name', Unicode())]
13 response = [('name', Unicode())]
14
15
16
17class RoutingAMP(AMP):
18 @NewRoute.responder
19 def newRoute(self, name):
20 route = self.boxReceiver.bindRoute(self.factory.routeProtocol())
21 route.connectTo(name)
22 return {'name': route.localRouteName}
23
24
25
26class AMPRouteServerFactory(ServerFactory):
27 protocol = RoutingAMP
28 routeProtocol = None
29
30 def buildProtocol(self, addr):
31 router = Router()
32 proto = self.protocol(router)
33 proto.factory = self
34 default = router.bindRoute(proto, None)
35 default.connectTo(None)
36 return proto
37
38
39
40def connect(proto, router, receiver):
41 route = router.bindRoute(receiver)
42 d = proto.callRemote(NewRoute, name=route.localRouteName)
43 d.addCallback(operator.getitem, 'name')
44 d.addCallback(lambda name: route.connectTo(name))
45 def connectionFailed(err):
46 route.unbind()
47 return err
48 d.addErrback(connectionFailed)
49 return d
500
=== removed file 'Epsilon/doc/stylesheet.css'
--- Epsilon/doc/stylesheet.css 2008-08-25 16:20:43 +0000
+++ Epsilon/doc/stylesheet.css 1970-01-01 00:00:00 +0000
@@ -1,129 +0,0 @@
1body
2{
3 margin-left: 2em;
4 margin-right: 2em;
5 border: 0px;
6 padding: 0px;
7 font-family: sans-serif;
8}
9
10pre
11{
12 padding: 1em;
13 font-family: Neep Alt, Courier New, Courier;
14 font-size: 12pt;
15 border: thin black solid;
16}
17
18.python
19{
20 background-color: #dddddd;
21}
22
23.py-listing, .html-listing, .listing
24{
25 margin: 1ex;
26 border: thin solid black;
27 background-color: #eee;
28}
29
30.py-listing pre, .html-listing pre, .listing pre
31{
32 margin: 0px;
33 border: none;
34 border-bottom: thin solid black;
35}
36
37.py-listing .python
38{
39 margin-top: 0;
40 margin-bottom: 0;
41 border: none;
42 border-bottom: thin solid black;
43}
44
45.py-src-comment
46{
47 color: #1111CC
48}
49
50.py-src-keyword
51{
52 color: #3333CC;
53 font-weight: bold;
54}
55
56.py-src-parameter
57{
58 color: #000066;
59 font-weight: bold;
60}
61
62.py-src-identifier
63{
64 color: #CC0000
65}
66
67.py-src-string
68{
69 color: #115511
70}
71
72.py-src-endmarker
73{
74 display: block; /* IE hack; prevents following line from being sucked into the py-listing box. */
75}
76
77hr
78{
79 display: inline;
80}
81
82ul
83{
84 padding: 0px;
85 margin: 0px;
86 margin-left: 1em;
87 padding-left: 1em;
88 border-left: 1em;
89}
90
91li
92{
93 padding: 2px;
94}
95
96dt
97{
98 font-weight: bold;
99 margin-left: 1ex;
100}
101
102dd
103{
104 margin-bottom: 1em;
105}
106
107div.note
108{
109 background-color: #FFFFCC;
110 margin-top: 1ex;
111 margin-left: 5%;
112 margin-right: 5%;
113 padding-top: 1ex;
114 padding-left: 5%;
115 padding-right: 5%;
116 border: thin black solid;
117}
118
119.caption
120{
121 text-align: center;
122 padding-top: 0.5em;
123 padding-bottom: 0.5em;
124}
125
126.filename
127{
128 font-style: italic;
129}
1300
=== removed file 'Epsilon/doc/template.tpl'
--- Epsilon/doc/template.tpl 2008-08-25 16:20:43 +0000
+++ Epsilon/doc/template.tpl 1970-01-01 00:00:00 +0000
@@ -1,23 +0,0 @@
1<?xml version="1.0"?>
2<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
3 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
4
5<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
6 <head>
7 <title>
8 Epsilon:
9 </title>
10 <link type="text/css" rel="stylesheet" href="stylesheet.css" />
11 </head>
12
13 <body bgcolor="white">
14 <h1 class="title"></h1>
15 <div class="toc"></div>
16 <div class="body">
17
18 </div>
19
20 <p><a href="index.html">Index</a></p>
21 <span class="version">Version: </span>
22 </body>
23</html>
240
=== removed directory 'Epsilon/epsilon'
=== removed file 'Epsilon/epsilon/__init__.py'
--- Epsilon/epsilon/__init__.py 2014-01-12 10:53:44 +0000
+++ Epsilon/epsilon/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,8 +0,0 @@
1# -*- test-case-name: epsilon.test -*-
2from epsilon._version import __version__
3from twisted.python import versions
4
5def asTwistedVersion(packageName, versionString):
6 return versions.Version(packageName, *map(int, versionString.split(".")))
7
8version = asTwistedVersion("epsilon", __version__)
90
=== removed file 'Epsilon/epsilon/_version.py'
--- Epsilon/epsilon/_version.py 2014-01-15 10:21:17 +0000
+++ Epsilon/epsilon/_version.py 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1__version__ = "0.7.0"
20
=== removed file 'Epsilon/epsilon/ampauth.py'
--- Epsilon/epsilon/ampauth.py 2011-07-18 12:37:13 +0000
+++ Epsilon/epsilon/ampauth.py 1970-01-01 00:00:00 +0000
@@ -1,312 +0,0 @@
1# -*- test-case-name: epsilon.test.test_ampauth -*-
2# Copyright (c) 2008 Divmod. See LICENSE for details.
3
4"""
5This module provides integration between L{AMP<twisted.protocols.amp.AMP>} and
6L{cred<twisted.cred>}.
7"""
8
9from hashlib import sha1
10
11from zope.interface import implements
12
13from twisted.python.randbytes import secureRandom
14from twisted.cred.error import UnauthorizedLogin
15from twisted.cred.credentials import IUsernameHashedPassword, IUsernamePassword
16from twisted.cred.checkers import ICredentialsChecker
17from twisted.protocols.amp import IBoxReceiver, String, Command, AMP
18from twisted.internet.protocol import ServerFactory
19
20from epsilon.iepsilon import IOneTimePad
21from epsilon.structlike import record
22
23__metaclass__ = type
24
25
26class UnhandledCredentials(Exception):
27 """
28 L{login} was passed a credentials object which did not provide a recognized
29 credentials interface.
30 """
31
32
33
34class OTPLogin(Command):
35 """
36 Command to initiate a login attempt where a one-time pad is to be used in
37 place of username/password credentials.
38 """
39 arguments = [('pad', String())]
40
41 errors = {
42 # Invalid username or password
43 UnauthorizedLogin: 'UNAUTHORIZED_LOGIN',
44 # No IBoxReceiver avatar
45 NotImplementedError: 'NOT_IMPLEMENTED_ERROR'}
46
47
48
49class PasswordLogin(Command):
50 """
51 Command to initiate a username/password-based login attempt. The response
52 to this command is a challenge which must be responded to based on the
53 correct password associated with the username given to this command.
54 """
55 arguments = [('username', String())]
56 response = [('challenge', String())]
57
58
59
60def _calcResponse(challenge, nonce, password):
61 """
62 Compute the response to the given challenge.
63
64 @type challenge: C{str}
65 @param challenge: An arbitrary byte string, probably received in response
66 to (or generated for) the L{PasswordLogin} command.
67
68 @type nonce: C{str}
69 @param nonce: An arbitrary byte string, generated by the client to include
70 in the hash to avoid making the client an oracle.
71
72 @type password: C{str}
73 @param password: The known correct password for the account being
74 authenticated.
75
76 @rtype: C{str}
77 @return: A hash constructed from the three parameters.
78 """
79 return sha1('%s %s %s' % (challenge, nonce, password)).digest()
80
81
82
83class PasswordChallengeResponse(Command):
84 """
85 Command to respond to a challenge issued in the response to a
86 L{PasswordLogin} command and complete a username/password-based login
87 attempt.
88
89 @param cnonce: A randomly generated string used only in this response.
90 @param response: The SHA-1 hash of the challenge, cnonce, and password.
91 """
92 arguments = [('cnonce', String()),
93 ('response', String())]
94
95 errors = {
96 # Invalid username or password
97 UnauthorizedLogin: 'UNAUTHORIZED_LOGIN',
98 # No IBoxReceiver avatar
99 NotImplementedError: 'NOT_IMPLEMENTED_ERROR'}
100
101 @classmethod
102 def determineFrom(cls, challenge, password):
103 """
104 Create a nonce and use it, along with the given challenge and password,
105 to generate the parameters for a response.
106
107 @return: A C{dict} suitable to be used as the keyword arguments when
108 calling this command.
109 """
110 nonce = secureRandom(16)
111 response = _calcResponse(challenge, nonce, password)
112 return dict(cnonce=nonce, response=response)
113
114
115
116class _AMPUsernamePassword(record('username challenge nonce response')):
117 """
118 L{IUsernameHashedPassword} implementation used by L{PasswordLogin} and
119 related commands.
120 """
121 implements(IUsernameHashedPassword)
122
123 def checkPassword(self, password):
124 """
125 Check the given plaintext password against the response in this
126 credentials object.
127
128 @type password: C{str}
129 @param password: The known correct password associated with
130 C{self.username}.
131
132 @return: A C{bool}, C{True} if this credentials object agrees with the
133 given password, C{False} otherwise.
134 """
135 if isinstance(password, unicode):
136 password = password.encode('utf-8')
137 correctResponse = _calcResponse(self.challenge, self.nonce, password)
138 return correctResponse == self.response
139
140
141
142class _AMPOneTimePad(record('padValue')):
143 """
144 L{IOneTimePad} implementation used by L{OTPLogin}.
145
146 @ivar padValue: The value of the one-time pad.
147 @type padValue: C{str}
148 """
149 implements(IOneTimePad)
150
151
152
153class CredReceiver(AMP):
154 """
155 Integration between AMP and L{twisted.cred}.
156
157 This implementation is limited to a single authentication per connection.
158 A future implementation may use I{routes} to allow multiple authentications
159 over the same connection.
160
161 @ivar portal: The L{Portal} against which login will be performed. This is
162 expected to be set by the factory which creates instances of this
163 class.
164
165 @ivar logout: C{None} or a no-argument callable. This is set to the logout
166 object returned by L{Portal.login} and is set while an avatar is logged
167 in.
168
169 @ivar challenge: The C{str} which was sent as a challenge in response to
170 the L{PasswordLogin} command. If multiple L{PasswordLogin} commands
171 are sent, this is the challenge sent in response to the most recent of
172 them. It is not set before L{PasswordLogin} is received.
173
174 @ivar username: The C{str} which was received for the I{username} parameter
175 of the L{PasswordLogin} command. The lifetime is the same as that of
176 the I{challenge} attribute.
177 """
178 portal = None
179 logout = None
180
181 @PasswordLogin.responder
182 def passwordLogin(self, username):
183 """
184 Generate a new challenge for the given username.
185 """
186 self.challenge = secureRandom(16)
187 self.username = username
188 return {'challenge': self.challenge}
189
190
191 def _login(self, credentials):
192 """
193 Actually login to our portal with the given credentials.
194 """
195 d = self.portal.login(credentials, None, IBoxReceiver)
196 def cbLoggedIn((interface, avatar, logout)):
197 self.logout = logout
198 self.boxReceiver = avatar
199 self.boxReceiver.startReceivingBoxes(self.boxSender)
200 return {}
201 d.addCallback(cbLoggedIn)
202 return d
203
204
205 @PasswordChallengeResponse.responder
206 def passwordChallengeResponse(self, cnonce, response):
207 """
208 Verify the response to a challenge.
209 """
210 return self._login(_AMPUsernamePassword(
211 self.username, self.challenge, cnonce, response))
212
213
214 @OTPLogin.responder
215 def otpLogin(self, pad):
216 """
217 Verify the given pad.
218 """
219 return self._login(_AMPOneTimePad(pad))
220
221
222 def connectionLost(self, reason):
223 """
224 If a login has happened, perform a logout.
225 """
226 AMP.connectionLost(self, reason)
227 if self.logout is not None:
228 self.logout()
229 self.boxReceiver = self.logout = None
230
231
232
233class OneTimePadChecker(record('pads')):
234 """
235 Checker which validates one-time pads.
236
237 @ivar pads: Mapping between valid one-time pads and avatar IDs.
238 @type pads: C{dict}
239 """
240 implements(ICredentialsChecker)
241
242 credentialInterfaces = (IOneTimePad,)
243
244 # ICredentialsChecker
245 def requestAvatarId(self, credentials):
246 if credentials.padValue in self.pads:
247 return self.pads.pop(credentials.padValue)
248 raise UnauthorizedLogin('Unknown one-time pad')
249
250
251
252class CredAMPServerFactory(ServerFactory):
253 """
254 Server factory useful for creating L{CredReceiver} instances.
255
256 This factory takes care of associating a L{Portal} with L{CredReceiver}
257 instances it creates.
258
259 @ivar portal: The portal which will be used by L{CredReceiver} instances
260 created by this factory.
261 """
262 protocol = CredReceiver
263
264 def __init__(self, portal):
265 self.portal = portal
266
267
268 def buildProtocol(self, addr):
269 proto = ServerFactory.buildProtocol(self, addr)
270 proto.portal = self.portal
271 return proto
272
273
274
275def login(client, credentials):
276 """
277 Authenticate using the given L{AMP} instance. The protocol must be
278 connected to a server with responders for L{PasswordLogin} and
279 L{PasswordChallengeResponse}.
280
281 @param client: A connected L{AMP} instance which will be used to issue
282 authentication commands.
283
284 @param credentials: An object providing L{IUsernamePassword} which will
285 be used to authenticate this connection to the server.
286
287 @return: A L{Deferred} which fires when authentication has succeeded or
288 which fails with L{UnauthorizedLogin} if the server rejects the
289 authentication attempt.
290 """
291 if not IUsernamePassword.providedBy(credentials):
292 raise UnhandledCredentials()
293 d = client.callRemote(
294 PasswordLogin, username=credentials.username)
295 def cbChallenge(response):
296 args = PasswordChallengeResponse.determineFrom(
297 response['challenge'], credentials.password)
298 d = client.callRemote(PasswordChallengeResponse, **args)
299 return d.addCallback(lambda ignored: client)
300 d.addCallback(cbChallenge)
301 return d
302
303
304
305__all__ = [
306 'UnhandledCredentials',
307
308 'OTPLogin', 'OneTimePadChecker',
309
310 'PasswordLogin', 'PasswordChallengeResponse', 'CredReceiver',
311
312 'CredAMPServerFactory', 'login']
3130
=== removed file 'Epsilon/epsilon/amprouter.py'
--- Epsilon/epsilon/amprouter.py 2008-08-29 16:02:56 +0000
+++ Epsilon/epsilon/amprouter.py 1970-01-01 00:00:00 +0000
@@ -1,205 +0,0 @@
1# -*- test-case-name: epsilon.test.test_amprouter -*-
2# Copyright (c) 2008 Divmod. See LICENSE for details.
3
4"""
5This module provides an implementation of I{Routes}, a system for multiplexing
6multiple L{IBoxReceiver}/I{IBoxSender} pairs over a single L{AMP} connection.
7"""
8
9from itertools import count
10
11from zope.interface import implements
12
13from twisted.protocols.amp import IBoxReceiver, IBoxSender
14
15from epsilon.structlike import record
16
17__metaclass__ = type
18
19_ROUTE = '_route'
20_unspecified = object()
21
22
23class RouteNotConnected(Exception):
24 """
25 An attempt was made to send AMP boxes through a L{Route} which is not yet
26 connected to anything.
27 """
28
29
30
31class Route(record('router receiver localRouteName remoteRouteName',
32 remoteRouteName=_unspecified)):
33 """
34 Wrap up a route name and a box sender to transparently add the route name
35 to boxes sent through this box sender.
36
37 @ivar router: The L{Router} which created this route. This will be used
38 for route tear down and for its L{IBoxSender}, to send boxes.
39
40 @ivar receiver: The receiver which will be started with this object as its
41 sender.
42
43 @type localRouteName: C{unicode}
44 @ivar localRouteName: The name of this route as known by the other side of
45 the AMP connection. AMP boxes with this route are expected to be
46 routed to this object.
47
48 @type remoteRouteName: C{unicode} or L{NoneType}
49 @ivar remoteRouteName: The name of the route which will be added to all
50 boxes sent to this sender. If C{None}, no route will be added.
51 """
52 implements(IBoxSender)
53
54 def connectTo(self, remoteRouteName):
55 """
56 Set the name of the route which will be added to outgoing boxes.
57 """
58 self.remoteRouteName = remoteRouteName
59 # This route must not be started before its router is started. If
60 # sender is None, then the router is not started. When the router is
61 # started, it will start this route.
62 if self.router._sender is not None:
63 self.start()
64
65
66 def unbind(self):
67 """
68 Remove the association between this route and its router.
69 """
70 del self.router._routes[self.localRouteName]
71
72
73 def start(self):
74 """
75 Associate this object with a receiver as its L{IBoxSender}.
76 """
77 self.receiver.startReceivingBoxes(self)
78
79
80 def stop(self, reason):
81 """
82 Shut down the underlying receiver.
83 """
84 self.receiver.stopReceivingBoxes(reason)
85
86
87 def sendBox(self, box):
88 """
89 Add the route and send the box.
90 """
91 if self.remoteRouteName is _unspecified:
92 raise RouteNotConnected()
93 if self.remoteRouteName is not None:
94 box[_ROUTE] = self.remoteRouteName.encode('ascii')
95 self.router._sender.sendBox(box)
96
97
98 def unhandledError(self, failure):
99 """
100 Pass failures through to the wrapped L{IBoxSender} without
101 modification.
102 """
103 self.router._sender.unhandledError(failure)
104
105
106
107class Router:
108 """
109 An L{IBoxReceiver} implementation which demultiplexes boxes from an AMP
110 connection being used with zero, one, or more routes.
111
112 @ivar _sender: An L{IBoxSender} provider which is used to allow
113 L{IBoxReceiver}s added to this router to send boxes.
114
115 @ivar _unstarted: A C{dict} similar to C{_routes} set before
116 C{startReceivingBoxes} is called and containing all routes which have
117 been added but not yet started. These are started and moved to the
118 C{_routes} dict when the router is started.
119
120 @ivar _routes: A C{dict} mapping local route identifiers to
121 L{IBoxReceivers} associated with them. This is only initialized after
122 C{startReceivingBoxes} is called.
123
124 @ivar _routeCounter: A L{itertools.count} instance used to generate unique
125 identifiers for routes in this router.
126 """
127 implements(IBoxReceiver)
128
129 _routes = None
130 _sender = None
131
132 def __init__(self):
133 self._routeCounter = count()
134 self._unstarted = {}
135
136
137 def createRouteIdentifier(self):
138 """
139 Return a route identifier which is not yet associated with a route on
140 this dispatcher.
141
142 @rtype: C{unicode}
143 """
144 return unicode(self._routeCounter.next())
145
146
147 def bindRoute(self, receiver, routeName=_unspecified):
148 """
149 Create a new route to associate the given route name with the given
150 receiver.
151
152 @type routeName: C{unicode} or L{NoneType}
153 @param routeName: The identifier for the newly created route. If
154 C{None}, boxes with no route in them will be delivered to this
155 receiver.
156
157 @rtype: L{Route}
158 """
159 if routeName is _unspecified:
160 routeName = self.createRouteIdentifier()
161 # self._sender may yet be None; if so, this route goes into _unstarted
162 # and will have its sender set correctly in startReceivingBoxes below.
163 route = Route(self, receiver, routeName)
164 mapping = self._routes
165 if mapping is None:
166 mapping = self._unstarted
167 mapping[routeName] = route
168 return route
169
170
171 def startReceivingBoxes(self, sender):
172 """
173 Initialize route tracking objects.
174 """
175 self._sender = sender
176 for routeName, route in self._unstarted.iteritems():
177 # Any route which has been bound but which does not yet have a
178 # remote route name should not yet be started. These will be
179 # started in Route.connectTo.
180 if route.remoteRouteName is not _unspecified:
181 route.start()
182 self._routes = self._unstarted
183 self._unstarted = None
184
185
186 def ampBoxReceived(self, box):
187 """
188 Dispatch the given box to the L{IBoxReceiver} associated with the route
189 indicated by the box, or handle it directly if there is no route.
190 """
191 route = box.pop(_ROUTE, None)
192 self._routes[route].receiver.ampBoxReceived(box)
193
194
195 def stopReceivingBoxes(self, reason):
196 """
197 Stop all the L{IBoxReceiver}s which have been added to this router.
198 """
199 for routeName, route in self._routes.iteritems():
200 route.stop(reason)
201 self._routes = None
202
203
204
205__all__ = ['Router', 'Route']
2060
=== removed file 'Epsilon/epsilon/asplode.py'
--- Epsilon/epsilon/asplode.py 2005-12-02 20:28:41 +0000
+++ Epsilon/epsilon/asplode.py 1970-01-01 00:00:00 +0000
@@ -1,34 +0,0 @@
1
2import sys, os
3from datetime import date
4
5def status(x):
6 sys.stderr.write(x+'\n')
7 sys.stderr.flush()
8
9def splode(linerator, proj, capproj):
10 current = None
11 for line in linerator:
12 line = line.replace('_project_', proj)
13 line = line.replace('_Project_', capproj)
14 line = line.replace('_date_', str(date.today()))
15 ls = line.split("###file:")
16 if len(ls) > 1:
17 fname = ls[1].strip()
18 if current is not None:
19 current.close()
20 try:
21 os.makedirs(os.path.dirname(fname))
22 except:
23 pass
24 current = file(fname, 'wb')
25 status('Created: ' + fname)
26 else:
27 current.write(line)
28 current.close()
29
30def main(argv):
31 splode(sys.stdin.readlines(), 'zoop', 'Zoop')
32
33if __name__ == '__main__':
34 main(sys.argv)
350
=== removed file 'Epsilon/epsilon/caseless.py'
--- Epsilon/epsilon/caseless.py 2008-04-16 12:34:02 +0000
+++ Epsilon/epsilon/caseless.py 1970-01-01 00:00:00 +0000
@@ -1,135 +0,0 @@
1# -*- test-case-name: epsilon.test.test_caseless -*-
2"""
3Helpers for case-insensitive string handling.
4"""
5
6class Caseless(object):
7 """
8 Case-insensitive string wrapper type.
9
10 This wrapper is intended for use with strings that have case-insensitive
11 semantics, such as HTTP/MIME header values. It implements comparison-based
12 operations case-insensitively, avoiding the need to manually call C{lower}
13 where appropriate, or keep track of which strings are case-insensitive
14 throughout various function calls.
15
16 Example usage:
17
18 >>> Caseless('Spam') == Caseless('spam')
19 True
20 >>> 'spam' in Caseless('Eggs and Spam')
21 True
22
23 >>> sorted(['FOO', 'bar'], key=Caseless)
24 ['bar', 'FOO']
25
26 >>> d = {Caseless('Content-type'): Caseless('Text/Plain')}
27 >>> d[Caseless('Content-Type')].startswith('text/')
28 True
29
30 Note: String methods that return modified strings (such as
31 C{decode}/C{encode}, C{join}, C{partition}, C{replace}, C{strip}/C{split})
32 don't have an unambiguous return types with regards to case sensitivity, so
33 they are not implemented by L{Caseless}. They should be accessed on the
34 underlying cased string instead. (Excepted are methods like
35 C{lower}/C{upper}, whose return case is unambiguous.)
36
37 @ivar cased: the wrapped string-like object
38 """
39
40 def __init__(self, cased):
41 if isinstance(cased, Caseless):
42 cased = cased.cased
43 self.cased = cased
44
45
46 def __repr__(self):
47 return '%s(%r)' % (type(self).__name__, self.cased)
48
49
50 # Methods delegated to cased
51
52 def __str__(self):
53 return str(self.cased)
54
55
56 def __unicode__(self):
57 return unicode(self.cased)
58
59
60 def __len__(self):
61 return len(self.cased)
62
63
64 def __getitem__(self, key):
65 return self.cased[key]
66
67
68 def __iter__(self):
69 return iter(self.cased)
70
71
72 def lower(self):
73 return self.cased.lower()
74
75
76 def upper(self):
77 return self.cased.upper()
78
79
80 def title(self):
81 return self.cased.title()
82
83
84 def swapcase(self):
85 return self.cased.swapcase()
86
87
88 # Methods delegated to lower()
89
90 def __cmp__(self, other):
91 return cmp(self.lower(), other.lower())
92
93
94 def __hash__(self):
95 return hash(self.lower())
96
97
98 def __contains__(self, substring):
99 return substring.lower() in self.lower()
100
101
102 def startswith(self, prefix, *rest):
103 if isinstance(prefix, tuple):
104 lprefix = tuple(s.lower() for s in prefix)
105 else:
106 lprefix = prefix.lower()
107 return self.lower().startswith(lprefix, *rest)
108
109
110 def endswith(self, suffix, *rest):
111 if isinstance(suffix, tuple):
112 lsuffix = tuple(s.lower() for s in suffix)
113 else:
114 lsuffix = suffix.lower()
115 return self.lower().endswith(lsuffix, *rest)
116
117
118 def count(self, substring, *rest):
119 return self.lower().count(substring.lower(), *rest)
120
121
122 def find(self, substring, *rest):
123 return self.lower().find(substring.lower(), *rest)
124
125
126 def index(self, substring, *rest):
127 return self.lower().index(substring.lower(), *rest)
128
129
130 def rfind(self, substring, *rest):
131 return self.lower().rfind(substring.lower(), *rest)
132
133
134 def rindex(self, substring, *rest):
135 return self.lower().rindex(substring.lower(), *rest)
1360
=== removed file 'Epsilon/epsilon/cooperator.py'
--- Epsilon/epsilon/cooperator.py 2009-05-22 13:21:37 +0000
+++ Epsilon/epsilon/cooperator.py 1970-01-01 00:00:00 +0000
@@ -1,32 +0,0 @@
1
2from twisted.application.service import Service
3from twisted.internet.task import SchedulerStopped, Cooperator, coiterate
4
5def iterateInReactor(i, delay=None):
6 """
7 Cooperatively iterate over the given iterator.
8
9 @see: L{twisted.internet.task.coiterate}.
10 """
11 return coiterate(i)
12
13
14class SchedulingService(Service):
15 """
16 Simple L{IService} implementation.
17 """
18 def __init__(self):
19 self.coop = Cooperator(started=False)
20
21 def addIterator(self, iterator):
22 return self.coop.coiterate(iterator)
23
24 def startService(self):
25 self.coop.start()
26
27 def stopService(self):
28 self.coop.stop()
29
30__all__ = [
31 'SchedulerStopped', 'Cooperator',
32 'SchedulingService', 'iterateInReactor']
330
=== removed file 'Epsilon/epsilon/descriptor.py'
--- Epsilon/epsilon/descriptor.py 2008-02-08 14:07:46 +0000
+++ Epsilon/epsilon/descriptor.py 1970-01-01 00:00:00 +0000
@@ -1,147 +0,0 @@
1# -*- test-case-name: epsilon.test.test_descriptor -*-
2
3"""
4Provides an 'attribute' class for one-use descriptors.
5"""
6
7attribute = None
8
9class _MetaAttribute(type):
10 def __new__(meta, name, bases, dict):
11 # for reals, yo.
12 for kw in ['get', 'set', 'delete']:
13 if kw in dict:
14 dict[kw] = staticmethod(dict[kw])
15 secretClass = type.__new__(meta, name, bases, dict)
16 if attribute is None:
17 return secretClass
18 return secretClass()
19
20class attribute(object):
21 """
22 Convenience class for providing one-shot descriptors, similar to
23 'property'. For example:
24
25 >>> from epsilon.descriptor import attribute
26 >>> class Dynamo(object):
27 ... class dynamic(attribute):
28 ... def get(self):
29 ... self.dynCount += 1
30 ... return self.dynCount
31 ... def set(self, value):
32 ... self.dynCount += value
33 ... dynCount = 0
34 ...
35 >>> d = Dynamo()
36 >>> d.dynamic
37 1
38 >>> d.dynamic
39 2
40 >>> d.dynamic = 6
41 >>> d.dynamic
42 9
43 >>> d.dynamic
44 10
45 >>> del d.dynamic
46 Traceback (most recent call last):
47 ...
48 AttributeError: attribute cannot be removed
49 """
50
51 __metaclass__ = _MetaAttribute
52
53 def __get__(self, oself, type):
54 """
55 Private implementation of descriptor interface.
56 """
57 if oself is None:
58 return self
59 return self.get(oself)
60
61 def __set__(self, oself, value):
62 """
63 Private implementation of descriptor interface.
64 """
65 return self.set(oself, value)
66
67 def __delete__(self, oself):
68 """
69 Private implementation of descriptor interface.
70 """
71 return self.delete(oself)
72
73 def set(self, value):
74 """
75 Implement this method to provide attribute setting. Default behavior
76 is that attributes are not settable.
77 """
78 raise AttributeError('read only attribute')
79
80 def get(self):
81 """
82 Implement this method to provide attribute retrieval. Default behavior
83 is that unset attributes do not have any value.
84 """
85 raise AttributeError('attribute has no value')
86
87 def delete(self):
88 """
89 Implement this method to provide attribute deletion. Default behavior
90 is that attributes cannot be deleted.
91 """
92 raise AttributeError('attribute cannot be removed')
93
94
95
96def requiredAttribute(requiredAttributeName):
97 """
98 Utility for defining attributes on base classes/mixins which require their
99 values to be supplied by their derived classes. C{None} is a common, but
100 almost never suitable default value for these kinds of attributes, as it
101 may cause operations in the derived class to fail silently in peculiar
102 ways. If a C{requiredAttribute} is accessed before having its value
103 changed, a C{AttributeError} will be raised with a helpful error message.
104
105 @param requiredAttributeName: The name of the required attribute.
106 @type requiredAttributeName: C{str}
107
108 Example:
109 >>> from epsilon.descriptor import requiredAttribute
110 ...
111 >>> class FooTestMixin:
112 ... expectedResult = requiredAttribute('expectedResult')
113 ...
114 >>> class BrokenFooTestCase(TestCase, FooTestMixin):
115 ... pass
116 ...
117 >>> brokenFoo = BrokenFooTestCase()
118 >>> print brokenFoo.expectedResult
119 Traceback (most recent call last):
120 ...
121 AttributeError: Required attribute 'expectedResult' has not been
122 changed from its default value on '<BrokenFooTestCase
123 instance>'.
124 ...
125 >>> class WorkingFooTestCase(TestCase, FooTestMixin):
126 ... expectedResult = 7
127 ...
128 >>> workingFoo = WorkingFooTestCase()
129 >>> print workingFoo.expectedResult
130 ... 7
131 >>>
132 """
133 class RequiredAttribute(attribute):
134 def get(self):
135 if requiredAttributeName not in self.__dict__:
136 raise AttributeError(
137 ('Required attribute %r has not been changed'
138 ' from its default value on %r' % (
139 requiredAttributeName, self)))
140 return self.__dict__[requiredAttributeName]
141 def set(self, value):
142 self.__dict__[requiredAttributeName] = value
143 return RequiredAttribute
144
145
146
147__all__ = ['attribute', 'requiredAttribute']
1480
=== removed file 'Epsilon/epsilon/expose.py'
--- Epsilon/epsilon/expose.py 2008-08-13 02:55:58 +0000
+++ Epsilon/epsilon/expose.py 1970-01-01 00:00:00 +0000
@@ -1,141 +0,0 @@
1# Copright 2008 Divmod, Inc. See LICENSE file for details.
2# -*- test-case-name: epsilon.test.test_expose -*-
3
4"""
5This module provides L{Exposer}, a utility for creating decorators that expose
6methods on types for a particular purpose.
7
8The typical usage of this module is for an infrastructure layer (usually one
9that allows methods to be invoked from the network, directly or indirectly) to
10provide an explicit API for exposing those methods securely.
11
12For example, a sketch of a finger protocol implementation which could use this
13to expose the results of certain methods as finger results::
14
15 # tx_finger.py
16 fingermethod = Exposer("This object exposes finger methods.")
17 ...
18 class FingerProtocol(Protocol):
19 def __init__(self, fingerModel):
20 self.model = fingerModel
21 ...
22 def fingerQuestionReceived(self, whichUser):
23 try:
24 method = fingermethod.get(self.model, whichUser)
25 except MethodNotExposed:
26 method = lambda : "Unknown user"
27 return method()
28
29 # myfingerserver.py
30 from tx_finger import fingermethod
31 ...
32 class MyFingerModel(object):
33 @fingermethod.expose("bob")
34 def someMethod(self):
35 return "Bob is great."
36
37Assuming lots of protocol code to hook everything together, this would then
38allow you to use MyFingerModel and 'finger bob' to get the message 'Bob is
39great.'
40"""
41
42import inspect
43
44from types import FunctionType
45
46
47class MethodNotExposed(Exception):
48 """
49 The requested method was not exposed for the purpose requested. More
50 specifically, L{Exposer.get} was used to retrieve a key from an object
51 which does not expose that key with that exposer.
52 """
53
54
55class NameRequired(Exception):
56 """
57 L{Exposer.expose} was used to decorate a non-function object without having
58 a key explicitly specified.
59 """
60
61
62
63class Exposer(object):
64 """
65 This is an object that can expose and retrieve methods on classes.
66
67 @ivar _exposed: a dict mapping exposed keys to exposed function objects.
68 """
69
70 def __init__(self, doc):
71 """
72 Create an exposer.
73 """
74 self.__doc__ = doc
75 self._exposed = {}
76
77
78 def expose(self, key=None):
79 """
80 Expose the decorated method for this L{Exposer} with the given key. A
81 method which is exposed will be able to be retrieved by this
82 L{Exposer}'s C{get} method with that key. If no key is provided, the
83 key is the method name of the exposed method.
84
85 Use like so::
86
87 class MyClass:
88 @someExposer.expose()
89 def foo(): ...
90
91 or::
92
93 class MyClass:
94 @someExposer.expose('foo')
95 def unrelatedMethodName(): ...
96
97 @param key: a hashable object, used by L{Exposer.get} to look up the
98 decorated method later. If None, the key is the exposed method's name.
99
100 @return: a 1-argument callable which records its input as exposed, then
101 returns it.
102 """
103 def decorator(function):
104 rkey = key
105 if rkey is None:
106 if isinstance(function, FunctionType):
107 rkey = function.__name__
108 else:
109 raise NameRequired()
110 if rkey not in self._exposed:
111 self._exposed[rkey] = []
112 self._exposed[rkey].append(function)
113 return function
114 return decorator
115
116
117 def get(self, obj, key):
118 """
119 Retrieve 'key' from an instance of a class which previously exposed it.
120
121 @param key: a hashable object, previously passed to L{Exposer.expose}.
122
123 @return: the object which was exposed with the given name on obj's key.
124
125 @raise MethodNotExposed: when the key in question was not exposed with
126 this exposer.
127 """
128 if key not in self._exposed:
129 raise MethodNotExposed()
130 rightFuncs = self._exposed[key]
131 T = obj.__class__
132 seen = {}
133 for subT in inspect.getmro(T):
134 for name, value in subT.__dict__.items():
135 for rightFunc in rightFuncs:
136 if value is rightFunc:
137 if name in seen:
138 raise MethodNotExposed()
139 return value.__get__(obj, T)
140 seen[name] = True
141 raise MethodNotExposed()
1420
=== removed file 'Epsilon/epsilon/extime.py'
--- Epsilon/epsilon/extime.py 2009-11-16 19:09:42 +0000
+++ Epsilon/epsilon/extime.py 1970-01-01 00:00:00 +0000
@@ -1,980 +0,0 @@
1# -*- test-case-name: epsilon.test.test_extime -*-
2"""
3Extended date/time formatting and miscellaneous functionality.
4
5See the class 'Time' for details.
6"""
7
8import datetime
9import re
10
11from email.Utils import parsedate_tz
12
13_EPOCH = datetime.datetime.utcfromtimestamp(0)
14
15
16class InvalidPrecision(Exception):
17 """
18 L{Time.asHumanly} was passed an invalid precision value.
19 """
20
21
22
23def sanitizeStructTime(struct):
24 """
25 Convert struct_time tuples with possibly invalid values to valid
26 ones by substituting the closest valid value.
27 """
28 maxValues = (9999, 12, 31, 23, 59, 59)
29 minValues = (1, 1, 1, 0, 0, 0)
30 newstruct = []
31 for value, maxValue, minValue in zip(struct[:6], maxValues, minValues):
32 newstruct.append(max(minValue, min(value, maxValue)))
33 return tuple(newstruct) + struct[6:]
34
35def _timedeltaToSignHrMin(offset):
36 """
37 Return a (sign, hour, minute) triple for the offset described by timedelta.
38
39 sign is a string, either "+" or "-". In the case of 0 offset, sign is "+".
40 """
41 minutes = round((offset.days * 3600000000 * 24
42 + offset.seconds * 1000000
43 + offset.microseconds)
44 / 60000000.0)
45 if minutes < 0:
46 sign = '-'
47 minutes = -minutes
48 else:
49 sign = '+'
50 return (sign, minutes // 60, minutes % 60)
51
52def _timedeltaToSeconds(offset):
53 """
54 Convert a datetime.timedelta instance to simply a number of seconds.
55
56 For example, you can specify purely second intervals with timedelta's
57 constructor:
58
59 >>> td = datetime.timedelta(seconds=99999999)
60
61 but then you can't get them out again:
62
63 >>> td.seconds
64 35199
65
66 This allows you to:
67
68 >>> import epsilon.extime
69 >>> epsilon.extime._timedeltaToSeconds(td)
70 99999999.0
71
72 @param offset: a L{datetime.timedelta} representing an interval that we
73 wish to know the total number of seconds for.
74
75 @return: a number of seconds
76 @rtype: float
77 """
78 return ((offset.days * 60*60*24) +
79 (offset.seconds) +
80 (offset.microseconds * 1e-6))
81
82class FixedOffset(datetime.tzinfo):
83 _zeroOffset = datetime.timedelta()
84
85 def __init__(self, hours, minutes):
86 self.offset = datetime.timedelta(minutes = hours * 60 + minutes)
87
88 def utcoffset(self, dt):
89 return self.offset
90
91 def tzname(self, dt):
92 return _timedeltaToSignHrMin(self.offset)
93
94 def dst(self, tz):
95 return self._zeroOffset
96
97 def __repr__(self):
98 return '<%s.%s object at 0x%x offset %r>' % (
99 self.__module__, type(self).__name__, id(self), self.offset)
100
101
102
103class Time(object):
104 """An object representing a well defined instant in time.
105
106 A Time object unambiguously addresses some time, independent of timezones,
107 contorted base-60 counting schemes, leap seconds, and the effects of
108 general relativity. It provides methods for returning a representation of
109 this time in various ways that a human or a programmer might find more
110 useful in various applications.
111
112 Every Time instance has an attribute 'resolution'. This can be ignored, or
113 the instance can be considered to address a span of time. This resolution
114 is determined by the value used to initalize the instance, or the
115 resolution of the internal representation, whichever is greater. It is
116 mostly useful when using input formats that allow the specification of
117 whole days or weeks. For example, ISO 8601 allows one to state a time as,
118 "2005-W03", meaning "the third week of 2005". In this case the resolution
119 is set to one week. Other formats are considered to express only an instant
120 in time, such as a POSIX timestamp, because the resolution of the time is
121 limited only by the hardware's representation of a real number.
122
123 Timezones are significant only for instances with a resolution greater than
124 one day. When the timezone is insignificant, the result of methods like
125 asISO8601TimeAndDate is the same for any given tzinfo parameter. Sort order
126 is determined by the start of the period in UTC. For example, "today" sorts
127 after "midnight today, central Europe", and before "midnight today, US
128 Eastern". For applications that need to store a mix of timezone dependent
129 and independent instances, it may be wise to store them separately, since
130 the time between the start and end of today in the local timezone may not
131 include the start of today in UTC, and thus not independent instances
132 addressing the whole day. In other words, the desired sort order (the one
133 where just "Monday" sorts before any more precise time in "Monday", and
134 after any in "Sunday") of Time instances is dependant on the timezone
135 context.
136
137 Date arithmetic and boolean operations operate on instants in time, not
138 periods. In this case, the start of the period is used as the value, and
139 the result has a resolution of 0.
140
141 For containment tests with the 'in' operator, the period addressed by the
142 instance is used.
143
144 The methods beginning with 'from' are constructors to create instances from
145 various formats. Some of them are textual formats, and others are other
146 time types commonly found in Python code.
147
148 Likewise, methods beginning with 'as' return the represented time in
149 various formats. Some of these methods should try to reflect the resolution
150 of the instance. However, they don't yet.
151
152 For formats with both a constructor and a formatter, d == fromFu(d.asFu())
153
154 @type resolution: datetime.timedelta
155 @ivar resolution: the length of the period to which this instance could
156 refer. For example, "Today, 13:38" could refer to any time between 13:38
157 until but not including 13:39. In this case resolution would be
158 timedelta(minutes=1).
159 """
160
161 # the instance variable _time is the internal representation of time. It
162 # is a naive datetime object which is always UTC. A UTC tzinfo would be
163 # great, if one existed, and anyway it complicates pickling.
164
165
166 class Precision(object):
167 MINUTES = object()
168 SECONDS = object()
169
170
171 _timeFormat = {
172 Precision.MINUTES: '%I:%M %p',
173 Precision.SECONDS: '%I:%M:%S %p'}
174
175 rfc2822Weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
176
177 rfc2822Months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
178 'Sep', 'Oct', 'Nov', 'Dec']
179
180 resolution = datetime.timedelta.resolution
181
182 #
183 # Methods to create new instances
184 #
185
186 def __init__(self):
187 """Return a new Time instance representing the time now.
188
189 See also the fromFu methods to create new instances from other types of
190 initializers.
191 """
192 self._time = datetime.datetime.utcnow()
193
194
195 def _fromWeekday(klass, match, tzinfo, now):
196 weekday = klass.weekdays.index(match.group('weekday').lower())
197 dtnow = now.asDatetime().replace(
198 hour=0, minute=0, second=0, microsecond=0)
199 daysInFuture = (weekday - dtnow.weekday()) % len(klass.weekdays)
200 if daysInFuture == 0:
201 daysInFuture = 7
202 self = klass.fromDatetime(dtnow + datetime.timedelta(days=daysInFuture))
203 assert self.asDatetime().weekday() == weekday
204 self.resolution = datetime.timedelta(days=1)
205 return self
206
207
208 def _fromTodayOrTomorrow(klass, match, tzinfo, now):
209 dtnow = now.asDatetime().replace(
210 hour=0, minute=0, second=0, microsecond=0)
211 when = match.group(0).lower()
212 if when == 'tomorrow':
213 dtnow += datetime.timedelta(days=1)
214 elif when == 'yesterday':
215 dtnow -= datetime.timedelta(days=1)
216 else:
217 assert when == 'today'
218 self = klass.fromDatetime(dtnow)
219 self.resolution = datetime.timedelta(days=1)
220 return self
221
222
223 def _fromTime(klass, match, tzinfo, now):
224 minute = int(match.group('minute'))
225 hour = int(match.group('hour'))
226 ampm = (match.group('ampm') or '').lower()
227 if ampm:
228 if not 1 <= hour <= 12:
229 raise ValueError, 'hour %i is not in 1..12' % (hour,)
230 if hour == 12 and ampm == 'am':
231 hour = 0
232 elif ampm == 'pm':
233 hour += 12
234 if not 0 <= hour <= 23:
235 raise ValueError, 'hour %i is not in 0..23' % (hour,)
236
237 dtnow = now.asDatetime(tzinfo).replace(second=0, microsecond=0)
238 dtthen = dtnow.replace(hour=hour, minute=minute)
239 if dtthen < dtnow:
240 dtthen += datetime.timedelta(days=1)
241
242 self = klass.fromDatetime(dtthen)
243 self.resolution = datetime.timedelta(minutes=1)
244 return self
245
246
247 def _fromNoonOrMidnight(klass, match, tzinfo, now):
248 when = match.group(0).lower()
249 if when == 'noon':
250 hour = 12
251 else:
252 assert when == 'midnight'
253 hour = 0
254 dtnow = now.asDatetime(tzinfo).replace(
255 minute=0, second=0, microsecond=0)
256 dtthen = dtnow.replace(hour=hour)
257 if dtthen < dtnow:
258 dtthen += datetime.timedelta(days=1)
259
260 self = klass.fromDatetime(dtthen)
261 self.resolution = datetime.timedelta(minutes=1)
262 return self
263
264 def _fromNow(klass, match, tzinfo, now):
265 # coerce our 'now' argument to an instant
266 return now + datetime.timedelta(0)
267
268 weekdays = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday',
269 'saturday', 'sunday']
270
271 humanlyPatterns = [
272 (re.compile(r"""
273 \b
274 ((next|this)\s+)?
275 (?P<weekday>
276 monday
277 | tuesday
278 | wednesday
279 | thursday
280 | friday
281 | saturday
282 | sunday
283 )
284 \b
285 """, re.IGNORECASE | re.VERBOSE),
286 _fromWeekday),
287 (re.compile(r"\b(today|tomorrow|yesterday)\b", re.IGNORECASE),
288 _fromTodayOrTomorrow),
289 (re.compile(r"""
290 \b
291 (?P<hour>\d{1,2}):(?P<minute>\d{2})
292 (\s*(?P<ampm>am|pm))?
293 \b
294 """, re.IGNORECASE | re.VERBOSE),
295 _fromTime),
296 (re.compile(r"\b(noon|midnight)\b", re.IGNORECASE),
297 _fromNoonOrMidnight),
298 (re.compile(r"\b(now)\b", re.IGNORECASE),
299 _fromNow),
300 ]
301
302 _fromWeekday = classmethod(_fromWeekday)
303 _fromTodayOrTomorrow = classmethod(_fromTodayOrTomorrow)
304 _fromTime = classmethod(_fromTime)
305 _fromNoonOrMidnight = classmethod(_fromNoonOrMidnight)
306 _fromNow = classmethod(_fromNow)
307
308
309 def fromHumanly(klass, humanStr, tzinfo=None, now=None):
310 """Return a new Time instance from a string a human might type.
311
312 @param humanStr: the string to be parsed.
313
314 @param tzinfo: A tzinfo instance indicating the timezone to assume if
315 none is specified in humanStr. If None, assume UTC.
316
317 @param now: A Time instance to be considered "now" for when
318 interpreting relative dates like "tomorrow". If None, use the real now.
319
320 Total crap now, it just supports weekdays, "today" and "tomorrow" for
321 now. This is pretty insufficient and useless, but good enough for some
322 demo functionality, or something.
323 """
324 humanStr = humanStr.strip()
325 if now is None:
326 now = Time()
327 if tzinfo is None:
328 tzinfo = FixedOffset(0, 0)
329
330 for pattern, creator in klass.humanlyPatterns:
331 match = pattern.match(humanStr)
332 if not match \
333 or match.span()[1] != len(humanStr):
334 continue
335 try:
336 return creator(klass, match, tzinfo, now)
337 except ValueError:
338 continue
339 raise ValueError, 'could not parse date: %r' % (humanStr,)
340
341 fromHumanly = classmethod(fromHumanly)
342
343
344 iso8601pattern = re.compile(r"""
345 ^ (?P<year> \d{4})
346 (
347 # a year may optionally be followed by one of:
348 # - a month
349 # - a week
350 # - a specific day, and an optional time
351 # a specific day is one of:
352 # - a month and day
353 # - week and weekday
354 # - a day of the year
355 (
356 -? (?P<month1> \d{2})
357 |
358 -? W (?P<week1> \d{2})
359 |
360 (
361 -? (?P<month2> \d{2})
362 -? (?P<day> \d{2})
363 |
364 -? W (?P<week2> \d{2})
365 -? (?P<weekday> \d)
366 |
367 -? (?P<dayofyear> \d{3})
368 )
369 (
370 T (?P<hour> \d{2})
371 (
372 :? (?P<minute> \d{2})
373 (
374 :? (?P<second> \d{2})
375 (
376 [\.,] (?P<fractionalsec> \d+)
377 )?
378 )?
379 )?
380 (
381 (?P<zulu> Z)
382 |
383 (?P<tzhour> [+\-]\d{2})
384 (
385 :? (?P<tzmin> \d{2})
386 )?
387 )?
388 )?
389 )?
390 )? $""", re.VERBOSE)
391
392
393 def fromISO8601TimeAndDate(klass, iso8601string, tzinfo=None):
394 """Return a new Time instance from a string formated as in ISO 8601.
395
396 If the given string contains no timezone, it is assumed to be in the
397 timezone specified by the parameter `tzinfo`, or UTC if tzinfo is None.
398 An input string with an explicit timezone will always override tzinfo.
399
400 If the given iso8601string does not contain all parts of the time, they
401 will default to 0 in the timezone given by `tzinfo`.
402
403 WARNING: this function is incomplete. ISO is dumb and their standards
404 are not free. Only a subset of all valid ISO 8601 dates are parsed,
405 because I can't find a formal description of the format. However,
406 common ones should work.
407 """
408
409 def calculateTimezone():
410 if groups['zulu'] == 'Z':
411 return FixedOffset(0, 0)
412 else:
413 tzhour = groups.pop('tzhour')
414 tzmin = groups.pop('tzmin')
415 if tzhour is not None:
416 return FixedOffset(int(tzhour), int(tzmin or 0))
417 return tzinfo or FixedOffset(0, 0)
418
419 def coerceGroups():
420 groups['month'] = groups['month1'] or groups['month2']
421 groups['week'] = groups['week1'] or groups['week2']
422 # don't include fractional seconds, because it's not an integer.
423 defaultTo0 = ['hour', 'minute', 'second']
424 defaultTo1 = ['month', 'day', 'week', 'weekday', 'dayofyear']
425 if groups['fractionalsec'] is None:
426 groups['fractionalsec'] = '0'
427 for key in defaultTo0:
428 if groups[key] is None:
429 groups[key] = 0
430 for key in defaultTo1:
431 if groups[key] is None:
432 groups[key] = 1
433 groups['fractionalsec'] = float('.'+groups['fractionalsec'])
434 for key in defaultTo0 + defaultTo1 + ['year']:
435 groups[key] = int(groups[key])
436
437 for group, min, max in [
438 # some years have only 52 weeks
439 ('week', 1, 53),
440 ('weekday', 1, 7),
441 ('month', 1, 12),
442 ('day', 1, 31),
443 ('hour', 0, 24),
444 ('minute', 0, 59),
445
446 # Sometime in the 22nd century AD, two leap seconds will be
447 # required every year. In the 25th century AD, four every
448 # year. We'll ignore that for now though because it would be
449 # tricky to get right and we certainly don't need it for our
450 # target applications. In other words, post-singularity
451 # Martian users, please do not rely on this code for
452 # compatibility with Greater Galactic Protectorate of Earth
453 # date/time formatting! Apologies, but no library I know of in
454 # Python is sufficient for processing their dates and times
455 # without ADA bindings to get the radiation-safety zone counter
456 # correct. -glyph
457
458 ('second', 0, 61),
459 # don't forget leap years
460 ('dayofyear', 1, 366)]:
461 if not min <= groups[group] <= max:
462 raise ValueError, '%s must be in %i..%i' % (group, min, max)
463
464 def determineResolution():
465 if match.group('fractionalsec') is not None:
466 return max(datetime.timedelta.resolution,
467 datetime.timedelta(
468 microseconds=1 * 10 ** -len(
469 match.group('fractionalsec')) * 1000000))
470
471 for testGroup, resolution in [
472 ('second', datetime.timedelta(seconds=1)),
473 ('minute', datetime.timedelta(minutes=1)),
474 ('hour', datetime.timedelta(hours=1)),
475 ('weekday', datetime.timedelta(days=1)),
476 ('dayofyear', datetime.timedelta(days=1)),
477 ('day', datetime.timedelta(days=1)),
478 ('week1', datetime.timedelta(weeks=1)),
479 ('week2', datetime.timedelta(weeks=1))]:
480 if match.group(testGroup) is not None:
481 return resolution
482
483 if match.group('month1') is not None \
484 or match.group('month2') is not None:
485 if self._time.month == 12:
486 return datetime.timedelta(days=31)
487 nextMonth = self._time.replace(month=self._time.month+1)
488 return nextMonth - self._time
489 else:
490 nextYear = self._time.replace(year=self._time.year+1)
491 return nextYear - self._time
492
493 def calculateDtime(tzinfo):
494 """Calculate a datetime for the start of the addressed period."""
495
496 if match.group('week1') is not None \
497 or match.group('week2') is not None:
498 if not 0 < groups['week'] <= 53:
499 raise ValueError(
500 'week must be in 1..53 (was %i)' % (groups['week'],))
501 dtime = datetime.datetime(
502 groups['year'],
503 1,
504 4,
505 groups['hour'],
506 groups['minute'],
507 groups['second'],
508 int(round(groups['fractionalsec'] * 1000000)),
509 tzinfo=tzinfo
510 )
511 dtime -= datetime.timedelta(days = dtime.weekday())
512 dtime += datetime.timedelta(
513 days = (groups['week']-1) * 7 + groups['weekday'] - 1)
514 if dtime.isocalendar() != (
515 groups['year'], groups['week'], groups['weekday']):
516 # actually the problem could be an error in my logic, but
517 # nothing should cause this but requesting week 53 of a
518 # year with 52 weeks.
519 raise ValueError('year %04i has no week %02i' %
520 (groups['year'], groups['week']))
521 return dtime
522
523 if match.group('dayofyear') is not None:
524 dtime = datetime.datetime(
525 groups['year'],
526 1,
527 1,
528 groups['hour'],
529 groups['minute'],
530 groups['second'],
531 int(round(groups['fractionalsec'] * 1000000)),
532 tzinfo=tzinfo
533 )
534 dtime += datetime.timedelta(days=groups['dayofyear']-1)
535 if dtime.year != groups['year']:
536 raise ValueError(
537 'year %04i has no day of year %03i' %
538 (groups['year'], groups['dayofyear']))
539 return dtime
540
541 else:
542 return datetime.datetime(
543 groups['year'],
544 groups['month'],
545 groups['day'],
546 groups['hour'],
547 groups['minute'],
548 groups['second'],
549 int(round(groups['fractionalsec'] * 1000000)),
550 tzinfo=tzinfo
551 )
552
553
554 match = klass.iso8601pattern.match(iso8601string)
555 if match is None:
556 raise ValueError(
557 '%r could not be parsed as an ISO 8601 date and time' %
558 (iso8601string,))
559
560 groups = match.groupdict()
561 coerceGroups()
562 if match.group('hour') is not None:
563 timezone = calculateTimezone()
564 else:
565 timezone = None
566 self = klass.fromDatetime(calculateDtime(timezone))
567 self.resolution = determineResolution()
568 return self
569
570 fromISO8601TimeAndDate = classmethod(fromISO8601TimeAndDate)
571
572 def fromStructTime(klass, structTime, tzinfo=None):
573 """Return a new Time instance from a time.struct_time.
574
575 If tzinfo is None, structTime is in UTC. Otherwise, tzinfo is a
576 datetime.tzinfo instance coresponding to the timezone in which
577 structTime is.
578
579 Many of the functions in the standard time module return these things.
580 This will also work with a plain 9-tuple, for parity with the time
581 module. The last three elements, or tm_wday, tm_yday, and tm_isdst are
582 ignored.
583 """
584 dtime = datetime.datetime(tzinfo=tzinfo, *structTime[:6])
585 self = klass.fromDatetime(dtime)
586 self.resolution = datetime.timedelta(seconds=1)
587 return self
588
589 fromStructTime = classmethod(fromStructTime)
590
591 def fromDatetime(klass, dtime):
592 """Return a new Time instance from a datetime.datetime instance.
593
594 If the datetime instance does not have an associated timezone, it is
595 assumed to be UTC.
596 """
597 self = klass.__new__(klass)
598 if dtime.tzinfo is not None:
599 self._time = dtime.astimezone(FixedOffset(0, 0)).replace(tzinfo=None)
600 else:
601 self._time = dtime
602 self.resolution = datetime.timedelta.resolution
603 return self
604
605 fromDatetime = classmethod(fromDatetime)
606
607 def fromPOSIXTimestamp(klass, secs):
608 """Return a new Time instance from seconds since the POSIX epoch.
609
610 The POSIX epoch is midnight Jan 1, 1970 UTC. According to POSIX, leap
611 seconds don't exist, so one UTC day is exactly 86400 seconds, even if
612 it wasn't.
613
614 @param secs: a number of seconds, represented as an integer, long or
615 float.
616 """
617 self = klass.fromDatetime(_EPOCH + datetime.timedelta(seconds=secs))
618 self.resolution = datetime.timedelta()
619 return self
620
621 fromPOSIXTimestamp = classmethod(fromPOSIXTimestamp)
622
623 def fromRFC2822(klass, rfc822string):
624 """
625 Return a new Time instance from a string formated as described in RFC 2822.
626
627 @type rfc822string: str
628
629 @raise ValueError: if the timestamp is not formatted properly (or if
630 certain obsoleted elements of the specification are used).
631
632 @return: a new L{Time}
633 """
634
635 # parsedate_tz is going to give us a "struct_time plus", a 10-tuple
636 # containing the 9 values a struct_time would, i.e.: (tm_year, tm_mon,
637 # tm_day, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst), plus a
638 # bonus "offset", which is an offset (in _seconds_, of all things).
639
640 maybeStructTimePlus = parsedate_tz(rfc822string)
641
642 if maybeStructTimePlus is None:
643 raise ValueError, 'could not parse RFC 2822 date %r' % (rfc822string,)
644 structTimePlus = sanitizeStructTime(maybeStructTimePlus)
645 offsetInSeconds = structTimePlus[-1]
646 if offsetInSeconds is None:
647 offsetInSeconds = 0
648 self = klass.fromStructTime(
649 structTimePlus,
650 FixedOffset(
651 hours=0,
652 minutes=offsetInSeconds // 60))
653 self.resolution = datetime.timedelta(seconds=1)
654 return self
655
656 fromRFC2822 = classmethod(fromRFC2822)
657
658 #
659 # Methods to produce various formats
660 #
661
662 def asPOSIXTimestamp(self):
663 """Return this time as a timestamp as specified by POSIX.
664
665 This timestamp is the count of the number of seconds since Midnight,
666 Jan 1 1970 UTC, ignoring leap seconds.
667 """
668 mytimedelta = self._time - _EPOCH
669 return _timedeltaToSeconds(mytimedelta)
670
671 def asDatetime(self, tzinfo=None):
672 """Return this time as an aware datetime.datetime instance.
673
674 The returned datetime object has the specified tzinfo, or a tzinfo
675 describing UTC if the tzinfo parameter is None.
676 """
677 if tzinfo is None:
678 tzinfo = FixedOffset(0, 0)
679
680 if not self.isTimezoneDependent():
681 return self._time.replace(tzinfo=tzinfo)
682 else:
683 return self._time.replace(tzinfo=FixedOffset(0, 0)).astimezone(tzinfo)
684
685 def asNaiveDatetime(self, tzinfo=None):
686 """Return this time as a naive datetime.datetime instance.
687
688 The returned datetime object has its tzinfo set to None, but is in the
689 timezone given by the tzinfo parameter, or UTC if the parameter is
690 None.
691 """
692 return self.asDatetime(tzinfo).replace(tzinfo=None)
693
694 def asRFC2822(self, tzinfo=None, includeDayOfWeek=True):
695 """Return this Time formatted as specified in RFC 2822.
696
697 RFC 2822 specifies the format of email messages.
698
699 RFC 2822 says times in email addresses should reflect the local
700 timezone. If tzinfo is a datetime.tzinfo instance, the returned
701 formatted string will reflect that timezone. Otherwise, the timezone
702 will be '-0000', which RFC 2822 defines as UTC, but with an unknown
703 local timezone.
704
705 RFC 2822 states that the weekday is optional. The parameter
706 includeDayOfWeek indicates whether or not to include it.
707 """
708 dtime = self.asDatetime(tzinfo)
709
710 if tzinfo is None:
711 rfcoffset = '-0000'
712 else:
713 rfcoffset = '%s%02i%02i' % _timedeltaToSignHrMin(dtime.utcoffset())
714
715 rfcstring = ''
716 if includeDayOfWeek:
717 rfcstring += self.rfc2822Weekdays[dtime.weekday()] + ', '
718
719 rfcstring += '%i %s %4i %02i:%02i:%02i %s' % (
720 dtime.day,
721 self.rfc2822Months[dtime.month - 1],
722 dtime.year,
723 dtime.hour,
724 dtime.minute,
725 dtime.second,
726 rfcoffset)
727
728 return rfcstring
729
730 def asISO8601TimeAndDate(self, includeDelimiters=True, tzinfo=None,
731 includeTimezone=True):
732 """Return this time formatted as specified by ISO 8861.
733
734 ISO 8601 allows optional dashes to delimit dates and colons to delimit
735 times. The parameter includeDelimiters (default True) defines the
736 inclusion of these delimiters in the output.
737
738 If tzinfo is a datetime.tzinfo instance, the output time will be in the
739 timezone given. If it is None (the default), then the timezone string
740 will not be included in the output, and the time will be in UTC.
741
742 The includeTimezone parameter coresponds to the inclusion of an
743 explicit timezone. The default is True.
744 """
745 if not self.isTimezoneDependent():
746 tzinfo = None
747 dtime = self.asDatetime(tzinfo)
748
749 if includeDelimiters:
750 dateSep = '-'
751 timeSep = ':'
752 else:
753 dateSep = timeSep = ''
754
755 if includeTimezone:
756 if tzinfo is None:
757 timezone = '+00%s00' % (timeSep,)
758 else:
759 sign, hour, min = _timedeltaToSignHrMin(dtime.utcoffset())
760 timezone = '%s%02i%s%02i' % (sign, hour, timeSep, min)
761 else:
762 timezone = ''
763
764 microsecond = ('%06i' % (dtime.microsecond,)).rstrip('0')
765 if microsecond:
766 microsecond = '.' + microsecond
767
768 parts = [
769 ('%04i' % (dtime.year,), datetime.timedelta(days=366)),
770 ('%s%02i' % (dateSep, dtime.month), datetime.timedelta(days=31)),
771 ('%s%02i' % (dateSep, dtime.day), datetime.timedelta(days=1)),
772 ('T', datetime.timedelta(hours=1)),
773 ('%02i' % (dtime.hour,), datetime.timedelta(hours=1)),
774 ('%s%02i' % (timeSep, dtime.minute), datetime.timedelta(minutes=1)),
775 ('%s%02i' % (timeSep, dtime.second), datetime.timedelta(seconds=1)),
776 (microsecond, datetime.timedelta(microseconds=1)),
777 (timezone, datetime.timedelta(hours=1))
778 ]
779
780 formatted = ''
781 for part, minResolution in parts:
782 if self.resolution <= minResolution:
783 formatted += part
784
785 return formatted
786
787 def asStructTime(self, tzinfo=None):
788 """Return this time represented as a time.struct_time.
789
790 tzinfo is a datetime.tzinfo instance coresponding to the desired
791 timezone of the output. If is is the default None, UTC is assumed.
792 """
793 dtime = self.asDatetime(tzinfo)
794 if tzinfo is None:
795 return dtime.utctimetuple()
796 else:
797 return dtime.timetuple()
798
799 def asHumanly(self, tzinfo=None, now=None, precision=Precision.MINUTES):
800 """Return this time as a short string, tailored to the current time.
801
802 Parts of the date that can be assumed are omitted. Consequently, the
803 output string depends on the current time. This is the format used for
804 displaying dates in most user visible places in the quotient web UI.
805
806 By default, the current time is determined by the system clock. The
807 current time used for formatting the time can be changed by providing a
808 Time instance as the parameter 'now'.
809
810 @param precision: The smallest unit of time that will be represented
811 in the returned string. Valid values are L{Time.Precision.MINUTES} and
812 L{Time.Precision.SECONDS}.
813
814 @raise InvalidPrecision: if the specified precision is not either
815 L{Time.Precision.MINUTES} or L{Time.Precision.SECONDS}.
816 """
817 try:
818 timeFormat = Time._timeFormat[precision]
819 except KeyError:
820 raise InvalidPrecision(
821 'Use Time.Precision.MINUTES or Time.Precision.SECONDS')
822
823 if now is None:
824 now = Time().asDatetime(tzinfo)
825 else:
826 now = now.asDatetime(tzinfo)
827 dtime = self.asDatetime(tzinfo)
828
829 # Same day?
830 if dtime.date() == now.date():
831 if self.isAllDay():
832 return 'all day'
833 return dtime.strftime(timeFormat).lower()
834 else:
835 res = str(dtime.date().day) + dtime.strftime(' %b') # day + month
836 # Different year?
837 if not dtime.date().year == now.date().year:
838 res += dtime.strftime(' %Y')
839 if not self.isAllDay():
840 res += dtime.strftime(', %s' % (timeFormat,)).lower()
841 return res
842
843 #
844 # methods to return related times
845 #
846
847 def getBounds(self, tzinfo=None):
848 """
849 Return a pair describing the bounds of self.
850
851 This returns a pair (min, max) of Time instances. It is not quite the
852 same as (self, self + self.resolution). This is because timezones are
853 insignificant for instances with a resolution greater or equal to 1
854 day.
855
856 To illustrate the problem, consider a Time instance::
857
858 T = Time.fromHumanly('today', tzinfo=anything)
859
860 This will return an equivalent instance independent of the tzinfo used.
861 The hour, minute, and second of this instance are 0, and its resolution
862 is one day.
863
864 Now say we have a sorted list of times, and we want to get all times
865 for 'today', where whoever said 'today' is in a timezone that's 5 hours
866 ahead of UTC. The start of 'today' in this timezone is UTC 05:00. The
867 example instance T above is before this, but obviously it is today.
868
869 The min and max times this returns are such that all potentially
870 matching instances are within this range. However, this range might
871 contain unmatching instances.
872
873 As an example of this, if 'today' is April first 2005, then
874 Time.fromISO8601TimeAndDate('2005-04-01T00:00:00') sorts in the same
875 place as T from above, but is not in the UTC+5 'today'.
876
877 TIME IS FUN!
878 """
879 if self.resolution >= datetime.timedelta(days=1) \
880 and tzinfo is not None:
881 time = self._time.replace(tzinfo=tzinfo)
882 else:
883 time = self._time
884
885 return (
886 min(self.fromDatetime(time), self.fromDatetime(self._time)),
887 max(self.fromDatetime(time + self.resolution),
888 self.fromDatetime(self._time + self.resolution))
889 )
890
891 def oneDay(self):
892 """Return a Time instance representing the day of the start of self.
893
894 The returned new instance will be set to midnight of the day containing
895 the first instant of self in the specified timezone, and have a
896 resolution of datetime.timedelta(days=1).
897 """
898 day = self.__class__.fromDatetime(self.asDatetime().replace(
899 hour=0, minute=0, second=0, microsecond=0))
900 day.resolution = datetime.timedelta(days=1)
901 return day
902
903 #
904 # useful predicates
905 #
906
907 def isAllDay(self):
908 """Return True iff this instance represents exactly all day."""
909 return self.resolution == datetime.timedelta(days=1)
910
911 def isTimezoneDependent(self):
912 """Return True iff timezone is relevant for this instance.
913
914 Timezone is only relevent for instances with a resolution better than
915 one day.
916 """
917 return self.resolution < datetime.timedelta(days=1)
918
919 #
920 # other magic methods
921 #
922
923 def __cmp__(self, other):
924 if not isinstance(other, Time):
925 raise TypeError("Cannot meaningfully compare %r with %r" % (self, other))
926 return cmp(self._time, other._time)
927
928 def __eq__(self, other):
929 if isinstance(other, Time):
930 return cmp(self._time, other._time) == 0
931 return False
932
933 def __ne__(self, other):
934 return not (self == other)
935
936 def __repr__(self):
937 return 'extime.Time.fromDatetime(%r)' % (self._time,)
938
939 __str__ = asISO8601TimeAndDate
940
941 def __contains__(self, other):
942 """Test if another Time instance is entirely within the period addressed by this one."""
943 if not isinstance(other, Time):
944 raise TypeError(
945 '%r is not a Time instance; can not test for containment'
946 % (other,))
947 if other._time < self._time:
948 return False
949 if self._time + self.resolution < other._time + other.resolution:
950 return False
951 return True
952
953 def __add__(self, addend):
954 if not isinstance(addend, datetime.timedelta):
955 raise TypeError, 'expected a datetime.timedelta instance'
956 return Time.fromDatetime(self._time + addend)
957
958 def __sub__(self, subtrahend):
959 """
960 Implement subtraction of an interval or another time from this one.
961
962 @type subtrahend: L{datetime.timedelta} or L{Time}
963
964 @param subtrahend: The object to be subtracted from this one.
965
966 @rtype: L{datetime.timedelta} or L{Time}
967
968 @return: If C{subtrahend} is a L{datetime.timedelta}, the result is
969 a L{Time} instance which is offset from this one by that amount. If
970 C{subtrahend} is a L{Time}, the result is a L{datetime.timedelta}
971 instance which gives the difference between it and this L{Time}
972 instance.
973 """
974 if isinstance(subtrahend, datetime.timedelta):
975 return Time.fromDatetime(self._time - subtrahend)
976
977 if isinstance(subtrahend, Time):
978 return self.asDatetime() - subtrahend.asDatetime()
979
980 return NotImplemented
9810
=== removed file 'Epsilon/epsilon/hotfix.py'
--- Epsilon/epsilon/hotfix.py 2009-05-22 13:03:43 +0000
+++ Epsilon/epsilon/hotfix.py 1970-01-01 00:00:00 +0000
@@ -1,81 +0,0 @@
1
2import inspect
3
4class NoSuchHotfix(Exception):
5 """
6 Man you must be pretty stupid.
7 """
8
9_alreadyInstalled = set()
10def require(packageName, fixName):
11 if (packageName, fixName) in _alreadyInstalled:
12 return
13
14 if (packageName, fixName) == ('twisted', 'filepath_copyTo'):
15 from twisted.python import filepath
16 if filepath.FilePath('a') != filepath.FilePath('a'):
17 from epsilon.hotfixes import filepath_copyTo
18 filepath_copyTo.install()
19 elif (packageName, fixName) == ('twisted', 'timeoutmixin_calllater'):
20 from twisted.protocols import policies
21 if not hasattr(policies.TimeoutMixin, 'callLater'):
22 from epsilon.hotfixes import timeoutmixin_calllater
23 timeoutmixin_calllater.install()
24 elif (packageName, fixName) == ('twisted', 'delayedcall_seconds'):
25 from twisted.internet import base
26 args = inspect.getargs(base.DelayedCall.__init__.func_code)[0]
27 if 'seconds' not in args:
28 from epsilon.hotfixes import delayedcall_seconds
29 delayedcall_seconds.install()
30 elif (packageName, fixName) == ('twisted', 'deferredgenerator_tfailure'):
31 from twisted.internet import defer
32 result = []
33 def test():
34 d = defer.waitForDeferred(defer.succeed(1))
35 yield d
36 result.append(d.getResult())
37 defer.deferredGenerator(test)()
38 if result == [1]:
39 from epsilon.hotfixes import deferredgenerator_tfailure
40 deferredgenerator_tfailure.install()
41 else:
42 assert result == [None]
43 elif (packageName, fixName) == ("twisted", "proto_helpers_stringtransport"):
44 from twisted.test.proto_helpers import StringTransport
45 st = StringTransport()
46 try:
47 st.write(u'foo')
48 except TypeError, e:
49 pass
50 else:
51 from epsilon.hotfixes import proto_helpers_stringtransport
52 proto_helpers_stringtransport.install()
53 elif (packageName, fixName) == ("twisted", "internet_task_Clock"):
54 from twisted.internet.task import Clock
55 from twisted.internet import base
56 from twisted import version
57 from epsilon.hotfixes import internet_task_clock
58 if internet_task_clock.clockIsBroken():
59 internet_task_clock.install()
60 elif (packageName, fixName) == ("twisted", "trial_assertwarns"):
61 from twisted.trial.unittest import TestCase
62 if not hasattr(TestCase, "failUnlessWarns"):
63 from epsilon.hotfixes import trial_assertwarns
64 trial_assertwarns.install()
65 elif (packageName, fixName) == ("twisted", "plugin_package_paths"):
66 try:
67 from twisted.plugin import pluginPackagePaths
68 except ImportError:
69 from epsilon.hotfixes import plugin_package_paths
70 plugin_package_paths.install()
71 elif (packageName, fixName) == ("twisted", "loopbackasync_reentrancy"):
72 # This one is really hard to detect reasonably. Invoking the code
73 # involves triggering the reactor, which it would be good to avoid.
74 from twisted import version
75 if (version.major, version.minor) < (8, 2):
76 from epsilon.hotfixes import loopbackasync_reentrancy
77 loopbackasync_reentrancy.install()
78 else:
79 raise NoSuchHotfix(packageName, fixName)
80
81 _alreadyInstalled.add((packageName, fixName))
820
=== removed directory 'Epsilon/epsilon/hotfixes'
=== removed file 'Epsilon/epsilon/hotfixes/__init__.py'
=== removed file 'Epsilon/epsilon/hotfixes/deferredgenerator_tfailure.py'
--- Epsilon/epsilon/hotfixes/deferredgenerator_tfailure.py 2006-05-22 15:35:56 +0000
+++ Epsilon/epsilon/hotfixes/deferredgenerator_tfailure.py 1970-01-01 00:00:00 +0000
@@ -1,59 +0,0 @@
1
2from twisted.python import failure
3from twisted.internet import defer
4
5def getResult(self):
6 if isinstance(self.result, failure.Failure):
7 self.result.raiseException()
8 return self.result
9
10
11def _deferGenerator(g, deferred=None):
12 """
13 See L{waitForDeferred}.
14 """
15 result = None
16 while 1:
17 if deferred is None:
18 deferred = defer.Deferred()
19 try:
20 result = g.next()
21 except StopIteration:
22 deferred.callback(result)
23 return deferred
24 except:
25 deferred.errback()
26 return deferred
27
28 # Deferred.callback(Deferred) raises an error; we catch this case
29 # early here and give a nicer error message to the user in case
30 # they yield a Deferred. Perhaps eventually these semantics may
31 # change.
32 if isinstance(result, defer.Deferred):
33 return defer.fail(TypeError("Yield waitForDeferred(d), not d!"))
34
35 if isinstance(result, defer.waitForDeferred):
36 waiting = [True, None]
37 # Pass vars in so they don't get changed going around the loop
38 def gotResult(r, waiting=waiting, result=result):
39 result.result = r
40 if waiting[0]:
41 waiting[0] = False
42 waiting[1] = r
43 else:
44 _deferGenerator(g, deferred)
45 result.d.addBoth(gotResult)
46 if waiting[0]:
47 # Haven't called back yet, set flag so that we get reinvoked
48 # and return from the loop
49 waiting[0] = False
50 return deferred
51 result = None # waiting[1]
52
53
54def install():
55 getResult.__module__ = 'twisted.internet.defer'
56 defer.waitForDeferred.getResult = getResult
57
58 _deferGenerator.__module__ = 'twisted.internet.defer'
59 defer._deferGenerator = _deferGenerator
600
=== removed file 'Epsilon/epsilon/hotfixes/delayedcall_seconds.py'
--- Epsilon/epsilon/hotfixes/delayedcall_seconds.py 2006-05-19 15:23:46 +0000
+++ Epsilon/epsilon/hotfixes/delayedcall_seconds.py 1970-01-01 00:00:00 +0000
@@ -1,160 +0,0 @@
1
2import traceback
3
4from zope.interface import implements
5
6from twisted.persisted import styles
7from twisted.internet.interfaces import IDelayedCall
8from twisted.internet import error, base
9from twisted.python import reflect
10
11class DelayedCall(styles.Ephemeral):
12
13 implements(IDelayedCall)
14 # enable .debug to record creator call stack, and it will be logged if
15 # an exception occurs while the function is being run
16 debug = False
17 _str = None
18
19 def __init__(self, time, func, args, kw, cancel, reset, seconds=None):
20 self.time, self.func, self.args, self.kw = time, func, args, kw
21 self.resetter = reset
22 self.canceller = cancel
23 self.seconds = seconds
24 self.cancelled = self.called = 0
25 self.delayed_time = 0
26 if self.debug:
27 self.creator = traceback.format_stack()[:-2]
28
29 def getTime(self):
30 """Return the time at which this call will fire
31
32 @rtype: C{float}
33 @return: The number of seconds after the epoch at which this call is
34 scheduled to be made.
35 """
36 return self.time + self.delayed_time
37
38 def cancel(self):
39 """Unschedule this call
40
41 @raise AlreadyCancelled: Raised if this call has already been
42 unscheduled.
43
44 @raise AlreadyCalled: Raised if this call has already been made.
45 """
46 if self.cancelled:
47 raise error.AlreadyCancelled
48 elif self.called:
49 raise error.AlreadyCalled
50 else:
51 self.canceller(self)
52 self.cancelled = 1
53 if self.debug:
54 self._str = str(self)
55 del self.func, self.args, self.kw
56
57 def reset(self, secondsFromNow):
58 """Reschedule this call for a different time
59
60 @type secondsFromNow: C{float}
61 @param secondsFromNow: The number of seconds from the time of the
62 C{reset} call at which this call will be scheduled.
63
64 @raise AlreadyCancelled: Raised if this call has been cancelled.
65 @raise AlreadyCalled: Raised if this call has already been made.
66 """
67 if self.cancelled:
68 raise error.AlreadyCancelled
69 elif self.called:
70 raise error.AlreadyCalled
71 else:
72 if self.seconds is None:
73 new_time = base.seconds() + secondsFromNow
74 else:
75 new_time = self.seconds() + secondsFromNow
76 if new_time < self.time:
77 self.delayed_time = 0
78 self.time = new_time
79 self.resetter(self)
80 else:
81 self.delayed_time = new_time - self.time
82
83 def delay(self, secondsLater):
84 """Reschedule this call for a later time
85
86 @type secondsLater: C{float}
87 @param secondsLater: The number of seconds after the originally
88 scheduled time for which to reschedule this call.
89
90 @raise AlreadyCancelled: Raised if this call has been cancelled.
91 @raise AlreadyCalled: Raised if this call has already been made.
92 """
93 if self.cancelled:
94 raise error.AlreadyCancelled
95 elif self.called:
96 raise error.AlreadyCalled
97 else:
98 self.delayed_time += secondsLater
99 if self.delayed_time < 0:
100 self.activate_delay()
101 self.resetter(self)
102
103 def activate_delay(self):
104 self.time += self.delayed_time
105 self.delayed_time = 0
106
107 def active(self):
108 """Determine whether this call is still pending
109
110 @rtype: C{bool}
111 @return: True if this call has not yet been made or cancelled,
112 False otherwise.
113 """
114 return not (self.cancelled or self.called)
115
116 def __le__(self, other):
117 return self.time <= other.time
118
119 def __str__(self):
120 if self._str is not None:
121 return self._str
122 if hasattr(self, 'func'):
123 if hasattr(self.func, 'func_name'):
124 func = self.func.func_name
125 if hasattr(self.func, 'im_class'):
126 func = self.func.im_class.__name__ + '.' + func
127 else:
128 func = reflect.safe_repr(self.func)
129 else:
130 func = None
131
132 if self.seconds is None:
133 now = base.seconds()
134 else:
135 now = self.seconds()
136 L = ["<DelayedCall %s [%ss] called=%s cancelled=%s" % (
137 id(self), self.time - now, self.called, self.cancelled)]
138 if func is not None:
139 L.extend((" ", func, "("))
140 if self.args:
141 L.append(", ".join([reflect.safe_repr(e) for e in self.args]))
142 if self.kw:
143 L.append(", ")
144 if self.kw:
145 L.append(", ".join(['%s=%s' % (k, reflect.safe_repr(v)) for (k, v) in self.kw.iteritems()]))
146 L.append(")")
147
148 if self.debug:
149 L.append("\n\ntraceback at creation: \n\n%s" % (' '.join(self.creator)))
150 L.append('>')
151
152 return "".join(L)
153
154
155def install():
156 global DelayedCall
157
158 base.DelayedCall.__dict__ = DelayedCall.__dict__
159 base.DelayedCall.__dict__['__module__'] = 'twisted.internet.base'
160 DelayedCall = base.DelayedCall
1610
=== removed file 'Epsilon/epsilon/hotfixes/filepath_copyTo.py'
--- Epsilon/epsilon/hotfixes/filepath_copyTo.py 2005-12-06 07:36:12 +0000
+++ Epsilon/epsilon/hotfixes/filepath_copyTo.py 1970-01-01 00:00:00 +0000
@@ -1,403 +0,0 @@
1# -*- test-case-name: twisted.test.test_paths -*-
2# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3# See LICENSE for details.
4
5from __future__ import generators
6
7import os
8import errno
9import base64
10import random
11import sha
12
13from os.path import isabs, exists, normpath, abspath, splitext
14from os.path import basename, dirname
15from os.path import join as joinpath
16from os import sep as slash
17from os import listdir, utime, stat
18from os import remove
19
20from stat import ST_MODE, ST_MTIME, ST_ATIME, ST_CTIME, ST_SIZE
21
22from stat import S_ISREG, S_ISDIR, S_ISLNK
23
24try:
25 from os.path import islink
26except ImportError:
27 def islink(path):
28 return False
29
30try:
31 from os import urandom as randomBytes
32except ImportError:
33 def randomBytes(n):
34 randomData = [random.randrange(256) for n in xrange(n)]
35 return ''.join(map(chr, randomData))
36
37try:
38 from base64 import urlsafe_b64encode as armor
39except ImportError:
40 def armor(s):
41 return s.encode('hex')
42
43class InsecurePath(Exception):
44 pass
45
46def _secureEnoughString():
47 """
48 Create a pseudorandom, 16-character string for use in secure filenames.
49 """
50 return armor(sha.new(randomBytes(64)).digest())[:16]
51
52class FilePath:
53 """I am a path on the filesystem that only permits 'downwards' access.
54
55 Instantiate me with a pathname (for example,
56 FilePath('/home/myuser/public_html')) and I will attempt to only provide
57 access to files which reside inside that path. I may be a path to a file,
58 a directory, or a file which does not exist.
59
60 The correct way to use me is to instantiate me, and then do ALL filesystem
61 access through me. In other words, do not import the 'os' module; if you
62 need to open a file, call my 'open' method. If you need to list a
63 directory, call my 'path' method.
64
65 Even if you pass me a relative path, I will convert that to an absolute
66 path internally.
67
68 @type alwaysCreate: C{bool}
69 @ivar alwaysCreate: When opening this file, only succeed if the file does not
70 already exist.
71 """
72
73 # __slots__ = 'path abs'.split()
74
75 statinfo = None
76
77 def __init__(self, path, alwaysCreate=False):
78 self.path = abspath(path)
79 self.alwaysCreate = alwaysCreate
80
81 def __getstate__(self):
82 d = self.__dict__.copy()
83 if d.has_key('statinfo'):
84 del d['statinfo']
85 return d
86
87 def child(self, path):
88 norm = normpath(path)
89 if slash in norm:
90 raise InsecurePath("%r contains one or more directory separators" % (path,))
91 newpath = abspath(joinpath(self.path, norm))
92 if not newpath.startswith(self.path):
93 raise InsecurePath("%r is not a child of %s" % (newpath, self.path))
94 return self.clonePath(newpath)
95
96 def preauthChild(self, path):
97 """
98 Use me if `path' might have slashes in it, but you know they're safe.
99
100 (NOT slashes at the beginning. It still needs to be a _child_).
101 """
102 newpath = abspath(joinpath(self.path, normpath(path)))
103 if not newpath.startswith(self.path):
104 raise InsecurePath("%s is not a child of %s" % (newpath, self.path))
105 return self.clonePath(newpath)
106
107 def childSearchPreauth(self, *paths):
108 """Return my first existing child with a name in 'paths'.
109
110 paths is expected to be a list of *pre-secured* path fragments; in most
111 cases this will be specified by a system administrator and not an
112 arbitrary user.
113
114 If no appropriately-named children exist, this will return None.
115 """
116 p = self.path
117 for child in paths:
118 jp = joinpath(p, child)
119 if exists(jp):
120 return self.clonePath(jp)
121
122 def siblingExtensionSearch(self, *exts):
123 """Attempt to return a path with my name, given multiple possible
124 extensions.
125
126 Each extension in exts will be tested and the first path which exists
127 will be returned. If no path exists, None will be returned. If '' is
128 in exts, then if the file referred to by this path exists, 'self' will
129 be returned.
130
131 The extension '*' has a magic meaning, which means "any path that
132 begins with self.path+'.' is acceptable".
133 """
134 p = self.path
135 for ext in exts:
136 if not ext and self.exists():
137 return self
138 if ext == '*':
139 basedot = basename(p)+'.'
140 for fn in listdir(dirname(p)):
141 if fn.startswith(basedot):
142 return self.clonePath(joinpath(dirname(p), fn))
143 p2 = p + ext
144 if exists(p2):
145 return self.clonePath(p2)
146
147 def siblingExtension(self, ext):
148 return self.clonePath(self.path+ext)
149
150 def open(self, mode='r'):
151 if self.alwaysCreate:
152 assert 'a' not in mode, "Appending not supported when alwaysCreate == True"
153 return self.create()
154 return open(self.path, mode+'b')
155
156 # stat methods below
157
158 def restat(self, reraise=True):
159 try:
160 self.statinfo = stat(self.path)
161 except OSError:
162 self.statinfo = 0
163 if reraise:
164 raise
165
166 def getsize(self):
167 st = self.statinfo
168 if not st:
169 self.restat()
170 st = self.statinfo
171 return st[ST_SIZE]
172
173 def getmtime(self):
174 st = self.statinfo
175 if not st:
176 self.restat()
177 st = self.statinfo
178 return st[ST_MTIME]
179
180 def getctime(self):
181 st = self.statinfo
182 if not st:
183 self.restat()
184 st = self.statinfo
185 return st[ST_CTIME]
186
187 def getatime(self):
188 st = self.statinfo
189 if not st:
190 self.restat()
191 st = self.statinfo
192 return st[ST_ATIME]
193
194 def exists(self):
195 if self.statinfo:
196 return True
197 elif self.statinfo is None:
198 self.restat(False)
199 return self.exists()
200 else:
201 return False
202
203 def isdir(self):
204 st = self.statinfo
205 if not st:
206 self.restat(False)
207 st = self.statinfo
208 if not st:
209 return False
210 return S_ISDIR(st[ST_MODE])
211
212 def isfile(self):
213 st = self.statinfo
214 if not st:
215 self.restat(False)
216 st = self.statinfo
217 if not st:
218 return False
219 return S_ISREG(st[ST_MODE])
220
221 def islink(self):
222 st = self.statinfo
223 if not st:
224 self.restat(False)
225 st = self.statinfo
226 if not st:
227 return False
228 return S_ISLNK(st[ST_MODE])
229
230 def isabs(self):
231 return isabs(self.path)
232
233 def listdir(self):
234 return listdir(self.path)
235
236 def splitext(self):
237 return splitext(self.path)
238
239 def __repr__(self):
240 return 'FilePath(%r)' % self.path
241
242 def touch(self):
243 try:
244 self.open('a').close()
245 except IOError:
246 pass
247 utime(self.path, None)
248
249 def remove(self):
250 if self.isdir():
251 for child in self.children():
252 child.remove()
253 os.rmdir(self.path)
254 else:
255 os.remove(self.path)
256 self.restat(False)
257
258 def makedirs(self):
259 return os.makedirs(self.path)
260
261 def globChildren(self, pattern):
262 """
263 Assuming I am representing a directory, return a list of
264 FilePaths representing my children that match the given
265 pattern.
266 """
267 import glob
268 path = self.path[-1] == '/' and self.path + pattern or slash.join([self.path, pattern])
269 return map(self.clonePath, glob.glob(path))
270
271 def basename(self):
272 return basename(self.path)
273
274 def dirname(self):
275 return dirname(self.path)
276
277 def parent(self):
278 return self.clonePath(self.dirname())
279
280 def setContent(self, content, ext='.new'):
281 sib = self.siblingExtension(ext)
282 sib.open('w').write(content)
283 os.rename(sib.path, self.path)
284
285 def getContent(self):
286 return self.open().read()
287
288 # new in 2.2.0
289
290 def __cmp__(self, other):
291 if not isinstance(other, FilePath):
292 return NotImplemented
293 return cmp(self.path, other.path)
294
295 def createDirectory(self):
296 os.mkdir(self.path)
297
298 def requireCreate(self, val=1):
299 self.alwaysCreate = val
300
301 def create(self):
302 """Exclusively create a file, only if this file previously did not exist.
303 """
304 fdint = os.open(self.path, (os.O_EXCL |
305 os.O_CREAT |
306 os.O_RDWR))
307
308 # XXX TODO: 'name' attribute of returned files is not mutable or
309 # settable via fdopen, so this file is slighly less functional than the
310 # one returned from 'open' by default. send a patch to Python...
311
312 return os.fdopen(fdint, 'w+b')
313
314 def temporarySibling(self):
315 """
316 Create a path naming a temporary sibling of this path in a secure fashion.
317 """
318 sib = self.parent().child(_secureEnoughString() + self.basename())
319 sib.requireCreate()
320 return sib
321
322 def children(self):
323 return map(self.child, self.listdir())
324
325 def walk(self):
326 yield self
327 if self.isdir():
328 for c in self.children():
329 for subc in c.walk():
330 yield subc
331
332 _chunkSize = 2 ** 2 ** 2 ** 2
333
334 def copyTo(self, destination):
335 # XXX TODO: *thorough* audit and documentation of the exact desired
336 # semantics of this code. Right now the behavior of existent
337 # destination symlinks is convenient, and quite possibly correct, but
338 # its security properties need to be explained.
339 if self.isdir():
340 if not destination.exists():
341 destination.createDirectory()
342 for child in self.children():
343 destChild = destination.child(child.basename())
344 child.copyTo(destChild)
345 elif self.isfile():
346 writefile = destination.open('w')
347 readfile = self.open()
348 while 1:
349 # XXX TODO: optionally use os.open, os.read and O_DIRECT and
350 # use os.fstatvfs to determine chunk sizes and make
351 # *****sure**** copy is page-atomic; the following is good
352 # enough for 99.9% of everybody and won't take a week to audit
353 # though.
354 chunk = readfile.read(self._chunkSize)
355 writefile.write(chunk)
356 if len(chunk) < self._chunkSize:
357 break
358 writefile.close()
359 readfile.close()
360 else:
361 # If you see the following message because you want to copy
362 # symlinks, fifos, block devices, character devices, or unix
363 # sockets, please feel free to add support to do sensible things in
364 # reaction to those types!
365 raise NotImplementedError(
366 "Only copying of files and directories supported")
367
368 def moveTo(self, destination):
369 try:
370 os.rename(self.path, destination.path)
371 self.restat(False)
372 except OSError, ose:
373 if ose.errno == errno.EXDEV:
374 # man 2 rename, ubuntu linux 5.10 "breezy":
375
376 # oldpath and newpath are not on the same mounted filesystem.
377 # (Linux permits a filesystem to be mounted at multiple
378 # points, but rename(2) does not work across different mount
379 # points, even if the same filesystem is mounted on both.)
380
381 # that means it's time to copy trees of directories!
382 secsib = destination.secureSibling()
383 self.copyTo(secsib) # slow
384 secsib.moveTo(destination) # visible
385
386 # done creating new stuff. let's clean me up.
387 mysecsib = self.secureSibling()
388 self.moveTo(mysecsib) # visible
389 mysecsib.remove() # slow
390 else:
391 raise
392
393
394FilePath.clonePath = FilePath
395
396
397def install():
398 global FilePath
399
400 from twisted.python import filepath
401 filepath.FilePath.__dict__ = FilePath.__dict__
402 filepath.FilePath.__dict__['__module__'] = 'twisted.python.filepath'
403 FilePath = filepath.FilePath
4040
=== removed file 'Epsilon/epsilon/hotfixes/internet_task_clock.py'
--- Epsilon/epsilon/hotfixes/internet_task_clock.py 2007-06-22 20:06:46 +0000
+++ Epsilon/epsilon/hotfixes/internet_task_clock.py 1970-01-01 00:00:00 +0000
@@ -1,38 +0,0 @@
1"""
2Fix from Twisted r20480.
3"""
4from twisted.internet.task import Clock
5from twisted.internet import base
6
7def callLater(self, when, what, *a, **kw):
8 """
9 Copied from twisted.internet.task.Clock, r20480. Fixes the bug
10 where the wrong DelayedCall would sometimes be returned.
11 """
12 dc = base.DelayedCall(self.seconds() + when,
13 what, a, kw,
14 self.calls.remove,
15 lambda c: None,
16 self.seconds)
17 self.calls.append(dc)
18 self.calls.sort(lambda a, b: cmp(a.getTime(), b.getTime()))
19 return dc
20
21def clockIsBroken():
22 """
23 Returns whether twisted.internet.task.Clock has the bug that
24 returns the wrong DelayedCall or not.
25 """
26 clock = Clock()
27 dc1 = clock.callLater(10, lambda: None)
28 dc2 = clock.callLater(1, lambda: None)
29 if dc1 is dc2:
30 return True
31 else:
32 return False
33
34def install():
35 """
36 Insert the fixed callLater method.
37 """
38 Clock.callLater = callLater
390
=== removed file 'Epsilon/epsilon/hotfixes/loopbackasync_reentrancy.py'
--- Epsilon/epsilon/hotfixes/loopbackasync_reentrancy.py 2008-08-28 14:40:39 +0000
+++ Epsilon/epsilon/hotfixes/loopbackasync_reentrancy.py 1970-01-01 00:00:00 +0000
@@ -1,26 +0,0 @@
1
2"""
3Fix from Twisted r23970
4"""
5
6from twisted.internet.task import deferLater
7from twisted.protocols.loopback import _loopbackAsyncBody
8
9def _loopbackAsyncContinue(ignored, server, serverToClient, client, clientToServer):
10 # Clear the Deferred from each message queue, since it has already fired
11 # and cannot be used again.
12 clientToServer._notificationDeferred = serverToClient._notificationDeferred = None
13
14 # Schedule some more byte-pushing to happen. This isn't done
15 # synchronously because no actual transport can re-enter dataReceived as
16 # a result of calling write, and doing this synchronously could result
17 # in that.
18 from twisted.internet import reactor
19 return deferLater(
20 reactor, 0,
21 _loopbackAsyncBody, server, serverToClient, client, clientToServer)
22
23
24def install():
25 from twisted.protocols import loopback
26 loopback._loopbackAsyncContinue = _loopbackAsyncContinue
270
=== removed file 'Epsilon/epsilon/hotfixes/plugin_package_paths.py'
--- Epsilon/epsilon/hotfixes/plugin_package_paths.py 2008-08-07 14:03:07 +0000
+++ Epsilon/epsilon/hotfixes/plugin_package_paths.py 1970-01-01 00:00:00 +0000
@@ -1,42 +0,0 @@
1# Copyright (c) 2007 Twisted Matrix Laboratories.
2# Copyright (c) 2008 Divmod.
3# See LICENSE for details.
4
5
6
7import sys, os
8
9def pluginPackagePaths(name):
10 """
11 Return a list of additional directories which should be searched for
12 modules to be included as part of the named plugin package.
13
14 @type name: C{str}
15 @param name: The fully-qualified Python name of a plugin package, eg
16 C{'twisted.plugins'}.
17
18 @rtype: C{list} of C{str}
19 @return: The absolute paths to other directories which may contain plugin
20 modules for the named plugin package.
21 """
22 package = name.split('.')
23 # Note that this may include directories which do not exist. It may be
24 # preferable to remove such directories at this point, rather than allow
25 # them to be searched later on.
26 #
27 # Note as well that only '__init__.py' will be considered to make a
28 # directory a package (and thus exclude it from this list). This means
29 # that if you create a master plugin package which has some other kind of
30 # __init__ (eg, __init__.pyc) it will be incorrectly treated as a
31 # supplementary plugin directory.
32 return [
33 os.path.abspath(os.path.join(x, *package))
34 for x
35 in sys.path
36 if
37 not os.path.exists(os.path.join(x, *package + ['__init__.py']))]
38
39
40def install():
41 import twisted.plugin
42 twisted.plugin.pluginPackagePaths = pluginPackagePaths
430
=== removed file 'Epsilon/epsilon/hotfixes/proto_helpers_stringtransport.py'
--- Epsilon/epsilon/hotfixes/proto_helpers_stringtransport.py 2006-06-22 20:48:07 +0000
+++ Epsilon/epsilon/hotfixes/proto_helpers_stringtransport.py 1970-01-01 00:00:00 +0000
@@ -1,11 +0,0 @@
1from twisted.test import proto_helpers
2
3class StringTransport:
4
5 def write(self, data):
6 if isinstance(data, unicode): # no, really, I mean it
7 raise TypeError("Data must not be unicode")
8 self.io.write(data)
9
10def install():
11 proto_helpers.StringTransport.__dict__['write'] = StringTransport.__dict__['write']
120
=== removed file 'Epsilon/epsilon/hotfixes/timeoutmixin_calllater.py'
--- Epsilon/epsilon/hotfixes/timeoutmixin_calllater.py 2006-05-19 15:23:46 +0000
+++ Epsilon/epsilon/hotfixes/timeoutmixin_calllater.py 1970-01-01 00:00:00 +0000
@@ -1,60 +0,0 @@
1
2from twisted.internet import reactor
3
4class TimeoutMixin:
5 """Mixin for protocols which wish to timeout connections
6
7 @cvar timeOut: The number of seconds after which to timeout the connection.
8 """
9 timeOut = None
10
11 __timeoutCall = None
12
13 def callLater(self, period, func):
14 return reactor.callLater(period, func)
15
16
17 def resetTimeout(self):
18 """Reset the timeout count down"""
19 if self.__timeoutCall is not None and self.timeOut is not None:
20 self.__timeoutCall.reset(self.timeOut)
21
22 def setTimeout(self, period):
23 """Change the timeout period
24
25 @type period: C{int} or C{NoneType}
26 @param period: The period, in seconds, to change the timeout to, or
27 C{None} to disable the timeout.
28 """
29 prev = self.timeOut
30 self.timeOut = period
31
32 if self.__timeoutCall is not None:
33 if period is None:
34 self.__timeoutCall.cancel()
35 self.__timeoutCall = None
36 else:
37 self.__timeoutCall.reset(period)
38 elif period is not None:
39 self.__timeoutCall = self.callLater(period, self.__timedOut)
40
41 return prev
42
43 def __timedOut(self):
44 self.__timeoutCall = None
45 self.timeoutConnection()
46
47 def timeoutConnection(self):
48 """Called when the connection times out.
49 Override to define behavior other than dropping the connection.
50 """
51 self.transport.loseConnection()
52
53
54def install():
55 global TimeoutMixin
56
57 from twisted.protocols import policies
58 policies.TimeoutMixin.__dict__ = TimeoutMixin.__dict__
59 policies.TimeoutMixin.__dict__['module'] = 'twisted.protocols.policies'
60 TimeoutMixin = policies.TimeoutMixin
610
=== removed file 'Epsilon/epsilon/hotfixes/trial_assertwarns.py'
--- Epsilon/epsilon/hotfixes/trial_assertwarns.py 2008-05-13 19:40:37 +0000
+++ Epsilon/epsilon/hotfixes/trial_assertwarns.py 1970-01-01 00:00:00 +0000
@@ -1,61 +0,0 @@
1
2"""
3failUnlessWarns assertion from twisted.trial in Twisted 8.0.
4"""
5
6import warnings
7
8def failUnlessWarns(self, category, message, filename, f,
9 *args, **kwargs):
10 """
11 Fail if the given function doesn't generate the specified warning when
12 called. It calls the function, checks the warning, and forwards the
13 result of the function if everything is fine.
14
15 @param category: the category of the warning to check.
16 @param message: the output message of the warning to check.
17 @param filename: the filename where the warning should come from.
18 @param f: the function which is supposed to generate the warning.
19 @type f: any callable.
20 @param args: the arguments to C{f}.
21 @param kwargs: the keywords arguments to C{f}.
22
23 @return: the result of the original function C{f}.
24 """
25 warningsShown = []
26 def warnExplicit(*args):
27 warningsShown.append(args)
28
29 origExplicit = warnings.warn_explicit
30 try:
31 warnings.warn_explicit = warnExplicit
32 result = f(*args, **kwargs)
33 finally:
34 warnings.warn_explicit = origExplicit
35
36 if not warningsShown:
37 self.fail("No warnings emitted")
38 first = warningsShown[0]
39 for other in warningsShown[1:]:
40 if other[:2] != first[:2]:
41 self.fail("Can't handle different warnings")
42 gotMessage, gotCategory, gotFilename, lineno = first[:4]
43 self.assertEqual(gotMessage, message)
44 self.assertIdentical(gotCategory, category)
45
46 # Use starts with because of .pyc/.pyo issues.
47 self.failUnless(
48 filename.startswith(gotFilename),
49 'Warning in %r, expected %r' % (gotFilename, filename))
50
51 # It would be nice to be able to check the line number as well, but
52 # different configurations actually end up reporting different line
53 # numbers (generally the variation is only 1 line, but that's enough
54 # to fail the test erroneously...).
55 # self.assertEqual(lineno, xxx)
56
57 return result
58
59def install():
60 from twisted.trial.unittest import TestCase
61 TestCase.failUnlessWarns = TestCase.assertWarns = failUnlessWarns
620
=== removed file 'Epsilon/epsilon/iepsilon.py'
--- Epsilon/epsilon/iepsilon.py 2008-11-10 20:26:55 +0000
+++ Epsilon/epsilon/iepsilon.py 1970-01-01 00:00:00 +0000
@@ -1,25 +0,0 @@
1# Copyright (c) 2008 Divmod. See LICENSE for details.
2
3"""
4Epsilon interfaces.
5"""
6from zope.interface import Attribute
7
8from twisted.cred.credentials import ICredentials
9
10
11class IOneTimePad(ICredentials):
12 """
13 A type of opaque credential for authenticating users, which can be used
14 only a single time.
15
16 This interface should also be responsible for authenticating. See #2784.
17 """
18 padValue = Attribute(
19 """
20 C{str} giving the value of the one-time pad. The value will be
21 compared by a L{twisted.cred.checkers.ICredentialsChecker} (e.g.
22 L{epsilon.ampauth.OneTimePadChecker}) against all valid one-time pads.
23 If there is a match, login will be successful and the pad will be
24 invalidated (further attempts to use it will fail).
25 """)
260
=== removed file 'Epsilon/epsilon/juice.py'
--- Epsilon/epsilon/juice.py 2009-07-06 11:51:08 +0000
+++ Epsilon/epsilon/juice.py 1970-01-01 00:00:00 +0000
@@ -1,1009 +0,0 @@
1# -*- test-case-name: epsilon.test.test_juice -*-
2# Copyright 2005 Divmod, Inc. See LICENSE file for details
3
4__metaclass__ = type
5
6import warnings, pprint
7
8from twisted.internet.main import CONNECTION_LOST
9from twisted.internet.defer import Deferred, maybeDeferred, fail
10from twisted.internet.protocol import ServerFactory, ClientFactory
11from twisted.internet.ssl import Certificate
12from twisted.python.failure import Failure
13from twisted.python import log, filepath
14
15from epsilon.liner import LineReceiver
16from epsilon import extime
17
18ASK = '_ask'
19ANSWER = '_answer'
20COMMAND = '_command'
21ERROR = '_error'
22ERROR_CODE = '_error_code'
23ERROR_DESCRIPTION = '_error_description'
24LENGTH = '_length'
25BODY = 'body'
26
27debug = False
28
29class JuiceBox(dict):
30 """ I am a packet in the JUICE protocol. """
31
32 def __init__(self, __body='', **kw):
33 self.update(kw)
34 if __body:
35 assert isinstance(__body, str), "body must be a string: %r" % ( repr(__body),)
36 self['body'] = __body
37
38 def body():
39 def get(self):
40 warnings.warn("body attribute of boxes is now just a regular field",
41 stacklevel=2)
42 return self['body']
43 def set(self, newbody):
44 warnings.warn("body attribute of boxes is now just a regular field",
45 stacklevel=2)
46 self['body'] = newbody
47 return get,set
48 body = property(*body())
49
50 def copy(self):
51 newBox = self.__class__()
52 newBox.update(self)
53 return newBox
54
55 def serialize(self,
56 delimiter='\r\n',
57 escaped='\r\n '):
58 assert LENGTH not in self
59
60 L = []
61 for (k, v) in self.iteritems():
62 if k == BODY:
63 k = LENGTH
64 v = str(len(self[BODY]))
65 L.append(k.replace('_', '-').title())
66 L.append(': ')
67 L.append(v.replace(delimiter, escaped))
68 L.append(delimiter)
69
70 L.append(delimiter)
71 if BODY in self:
72 L.append(self[BODY])
73
74 bytes = ''.join(L)
75 return bytes
76
77 def sendTo(self, proto):
78 """
79 Serialize and send this box to a Juice instance. By the time it is
80 being sent, several keys are required. I must have exactly ONE of::
81
82 -ask
83 -answer
84 -error
85
86 If the '-ask' header is set, then the '-command' header must also be
87 set.
88 """
89 proto.sendPacket(self)
90
91# juice.Box => JuiceBox
92
93Box = JuiceBox
94
95class TLSBox(JuiceBox):
96 def __repr__(self):
97 return 'TLS(**%s)' % (super(TLSBox, self).__repr__(),)
98
99
100 def __init__(self, __certificate, __verify=None, __sslstarted=None, **kw):
101 super(TLSBox, self).__init__(**kw)
102 self.certificate = __certificate
103 self.verify = __verify
104 self.sslstarted = __sslstarted
105
106 def sendTo(self, proto):
107 super(TLSBox, self).sendTo(proto)
108 if self.verify is None:
109 proto.startTLS(self.certificate)
110 else:
111 proto.startTLS(self.certificate, self.verify)
112 if self.sslstarted is not None:
113 self.sslstarted()
114
115class QuitBox(JuiceBox):
116 def __repr__(self):
117 return 'Quit(**%s)' % (super(QuitBox, self).__repr__(),)
118
119
120 def sendTo(self, proto):
121 super(QuitBox, self).sendTo(proto)
122 proto.transport.loseConnection()
123
124class _SwitchBox(JuiceBox):
125 def __repr__(self):
126 return 'Switch(**%s)' % (super(_SwitchBox, self).__repr__(),)
127
128
129 def __init__(self, __proto, **kw):
130 super(_SwitchBox, self).__init__(**kw)
131 self.innerProto = __proto
132
133 def sendTo(self, proto):
134 super(_SwitchBox, self).sendTo(proto)
135 proto._switchTo(self.innerProto)
136
137
138
139class NegotiateBox(JuiceBox):
140 def __repr__(self):
141 return 'Negotiate(**%s)' % (super(NegotiateBox, self).__repr__(),)
142
143
144 def sendTo(self, proto):
145 super(NegotiateBox, self).sendTo(proto)
146 proto._setProtocolVersion(int(self['version']))
147
148
149
150class JuiceError(Exception):
151 pass
152
153class RemoteJuiceError(JuiceError):
154 """
155 This error indicates that something went wrong on the remote end of the
156 connection, and the error was serialized and transmitted to you.
157 """
158 def __init__(self, errorCode, description, fatal=False):
159 """Create a remote error with an error code and description.
160 """
161 Exception.__init__(self, "Remote[%s]: %s" % (errorCode, description))
162 self.errorCode = errorCode
163 self.description = description
164 self.fatal = fatal
165
166class UnhandledRemoteJuiceError(RemoteJuiceError):
167 def __init__(self, description):
168 errorCode = "UNHANDLED"
169 RemoteJuiceError.__init__(self, errorCode, description)
170
171class JuiceBoxError(JuiceError):
172 pass
173
174class MalformedJuiceBox(JuiceBoxError):
175 pass
176
177class UnhandledCommand(JuiceError):
178 pass
179
180
181class IncompatibleVersions(JuiceError):
182 pass
183
184class _Transactor:
185 def __init__(self, store, callable):
186 self.store = store
187 self.callable = callable
188
189 def __call__(self, box):
190 return self.store.transact(self.callable, box)
191
192 def __repr__(self):
193 return '<Transaction in: %s of: %s>' % (self.store, self.callable)
194
195class DispatchMixin:
196 baseDispatchPrefix = 'juice_'
197 autoDispatchPrefix = 'command_'
198
199 wrapper = None
200
201 def _auto(self, aCallable, proto, namespace=None):
202 if aCallable is None:
203 return None
204 command = aCallable.command
205 if namespace not in command.namespaces:
206 # if you're in the wrong namespace, you are very likely not allowed
207 # to invoke the command you are trying to invoke. some objects
208 # have commands exposed in a separate namespace for security
209 # reasons, since the security model is a role : namespace mapping.
210 log.msg('WRONG NAMESPACE: %r, %r' % (namespace, command.namespaces))
211 return None
212 def doit(box):
213 kw = stringsToObjects(box, command.arguments, proto)
214 for name, extraArg in command.extra:
215 kw[name] = extraArg.fromTransport(proto.transport)
216# def checkIsDict(result):
217# if not isinstance(result, dict):
218# raise RuntimeError("%r returned %r, not dictionary" % (
219# aCallable, result))
220# return result
221 def checkKnownErrors(error):
222 key = error.trap(*command.allErrors)
223 code = command.allErrors[key]
224 desc = str(error.value)
225 return Failure(RemoteJuiceError(
226 code, desc, error in command.fatalErrors))
227 return maybeDeferred(aCallable, **kw).addCallback(
228 command.makeResponse, proto).addErrback(
229 checkKnownErrors)
230 return doit
231
232 def _wrap(self, aCallable):
233 if aCallable is None:
234 return None
235 wrap = self.wrapper
236 if wrap is not None:
237 return wrap(aCallable)
238 else:
239 return aCallable
240
241 def normalizeCommand(self, cmd):
242 """Return the canonical form of a command.
243 """
244 return cmd.upper().strip().replace('-', '_')
245
246 def lookupFunction(self, proto, name, namespace):
247 """Return a callable to invoke when executing the named command.
248 """
249 # Try to find a method to be invoked in a transaction first
250 # Otherwise fallback to a "regular" method
251 fName = self.autoDispatchPrefix + name
252 fObj = getattr(self, fName, None)
253 if fObj is not None:
254 # pass the namespace along
255 return self._auto(fObj, proto, namespace)
256
257 assert namespace is None, 'Old-style parsing'
258 # Fall back to simplistic command dispatching - we probably want to get
259 # rid of this eventually, there's no reason to do extra work and write
260 # fewer docs all the time.
261 fName = self.baseDispatchPrefix + name
262 return getattr(self, fName, None)
263
264 def dispatchCommand(self, proto, cmd, box, namespace=None):
265 fObj = self.lookupFunction(proto, self.normalizeCommand(cmd), namespace)
266 if fObj is None:
267 return fail(UnhandledCommand(cmd))
268 return maybeDeferred(self._wrap(fObj), box)
269
270PYTHON_KEYWORDS = [
271 'and', 'del', 'for', 'is', 'raise', 'assert', 'elif', 'from', 'lambda',
272 'return', 'break', 'else', 'global', 'not', 'try', 'class', 'except',
273 'if', 'or', 'while', 'continue', 'exec', 'import', 'pass', 'yield',
274 'def', 'finally', 'in', 'print']
275
276def normalizeKey(key):
277 lkey = key.lower().replace('-', '_')
278 if lkey in PYTHON_KEYWORDS:
279 return lkey.title()
280 return lkey
281
282
283def parseJuiceHeaders(lines):
284 """
285 Create a JuiceBox from a list of header lines.
286
287 @param lines: a list of lines.
288 """
289 b = JuiceBox()
290 bodylen = 0
291 key = None
292 for L in lines:
293 if L[0] == ' ':
294 # continuation
295 assert key is not None
296 b[key] += '\r\n'+L[1:]
297 continue
298 parts = L.split(': ', 1)
299 if len(parts) != 2:
300 raise MalformedJuiceBox("Wrong number of parts: %r" % (L,))
301 key, value = parts
302 key = normalizeKey(key)
303 b[key] = value
304 return int(b.pop(LENGTH, 0)), b
305
306class JuiceParserBase(DispatchMixin):
307
308 def __init__(self):
309 self._outstandingRequests = {}
310
311 def _puke(self, failure):
312 log.msg("Juice server or network failure "
313 "unhandled by client application:")
314 log.err(failure)
315 log.msg(
316 "Dropping connection! "
317 "To avoid, add errbacks to ALL remote commands!")
318 if self.transport is not None:
319 self.transport.loseConnection()
320
321 _counter = 0L
322
323 def _nextTag(self):
324 self._counter += 1
325 return '%x' % (self._counter,)
326
327 def failAllOutgoing(self, reason):
328 OR = self._outstandingRequests.items()
329 self._outstandingRequests = None # we can never send another request
330 for key, value in OR:
331 value.errback(reason)
332
333 def juiceBoxReceived(self, box):
334 if debug:
335 log.msg("Juice receive: %s" % pprint.pformat(dict(box.iteritems())))
336
337 if ANSWER in box:
338 question = self._outstandingRequests.pop(box[ANSWER])
339 question.addErrback(self._puke)
340 self._wrap(question.callback)(box)
341 elif ERROR in box:
342 question = self._outstandingRequests.pop(box[ERROR])
343 question.addErrback(self._puke)
344 self._wrap(question.errback)(
345 Failure(RemoteJuiceError(box[ERROR_CODE],
346 box[ERROR_DESCRIPTION])))
347 elif COMMAND in box:
348 cmd = box[COMMAND]
349 def sendAnswer(answerBox):
350 if ASK not in box:
351 return
352 if self.transport is None:
353 return
354 answerBox[ANSWER] = box[ASK]
355 answerBox.sendTo(self)
356 def sendError(error):
357 if ASK not in box:
358 return error
359 if error.check(RemoteJuiceError):
360 code = error.value.errorCode
361 desc = error.value.description
362 if error.value.fatal:
363 errorBox = QuitBox()
364 else:
365 errorBox = JuiceBox()
366 else:
367 errorBox = QuitBox()
368 log.err(error) # here is where server-side logging happens
369 # if the error isn't handled
370 code = 'UNHANDLED'
371 desc = "Unhandled Remote System Exception "
372 errorBox[ERROR] = box[ASK]
373 errorBox[ERROR_DESCRIPTION] = desc
374 errorBox[ERROR_CODE] = code
375 if self.transport is not None:
376 errorBox.sendTo(self)
377 return None # intentionally stop the error here: don't log the
378 # traceback if it's handled, do log it (earlier) if
379 # it isn't
380 self.dispatchCommand(self, cmd, box).addCallbacks(sendAnswer, sendError
381 ).addErrback(self._puke)
382 else:
383 raise RuntimeError(
384 "Empty packet received over connection-oriented juice: %r" % (box,))
385
386 def sendBoxCommand(self, command, box, requiresAnswer=True):
387 """
388 Send a command across the wire with the given C{juice.Box}.
389
390 Returns a Deferred which fires with the response C{juice.Box} when it
391 is received, or fails with a C{juice.RemoteJuiceError} if an error is
392 received.
393
394 If the Deferred fails and the error is not handled by the caller of
395 this method, the failure will be logged and the connection dropped.
396 """
397 if self._outstandingRequests is None:
398 return fail(CONNECTION_LOST)
399 box[COMMAND] = command
400 tag = self._nextTag()
401 if requiresAnswer:
402 box[ASK] = tag
403 result = self._outstandingRequests[tag] = Deferred()
404 else:
405 result = None
406 box.sendTo(self)
407 return result
408
409
410
411
412
413
414class Argument:
415 optional = False
416
417 def __init__(self, optional=False):
418 self.optional = optional
419
420 def retrieve(self, d, name):
421 if self.optional:
422 value = d.get(name)
423 if value is not None:
424 del d[name]
425 else:
426 value = d.pop(name)
427 return value
428
429 def fromBox(self, name, strings, objects, proto):
430 st = self.retrieve(strings, name)
431 if self.optional and st is None:
432 objects[name] = None
433 else:
434 objects[name] = self.fromStringProto(st, proto)
435
436 def toBox(self, name, strings, objects, proto):
437 obj = self.retrieve(objects, name)
438 if self.optional and obj is None:
439 # strings[name] = None
440 return
441 else:
442 strings[name] = self.toStringProto(obj, proto)
443
444 def fromStringProto(self, inString, proto):
445 return self.fromString(inString)
446
447 def toStringProto(self, inObject, proto):
448 return self.toString(inObject)
449
450 def fromString(self, inString):
451 raise NotImplementedError()
452
453 def toString(self, inObject):
454 raise NotImplementedError()
455
456class JuiceList(Argument):
457 def __init__(self, subargs):
458 self.subargs = subargs
459
460 def fromStringProto(self, inString, proto):
461 boxes = parseString(inString)
462 values = [stringsToObjects(box, self.subargs, proto)
463 for box in boxes]
464 return values
465
466 def toStringProto(self, inObject, proto):
467 return ''.join([objectsToStrings(
468 objects, self.subargs, Box(), proto
469 ).serialize() for objects in inObject])
470
471class ListOf(Argument):
472 def __init__(self, subarg, delimiter=', '):
473 self.subarg = subarg
474 self.delimiter = delimiter
475
476 def fromStringProto(self, inString, proto):
477 strings = inString.split(self.delimiter)
478 L = [self.subarg.fromStringProto(string, proto)
479 for string in strings]
480 return L
481
482 def toStringProto(self, inObject, proto):
483 L = []
484 for inSingle in inObject:
485 outString = self.subarg.toStringProto(inSingle, proto)
486 assert self.delimiter not in outString
487 L.append(outString)
488 return self.delimiter.join(L)
489
490class Integer(Argument):
491 fromString = int
492 def toString(self, inObject):
493 return str(int(inObject))
494
495class String(Argument):
496 def toString(self, inObject):
497 return inObject
498 def fromString(self, inString):
499 return inString
500
501class EncodedString(Argument):
502
503 def __init__(self, encoding):
504 self.encoding = encoding
505
506 def toString(self, inObject):
507 return inObject.encode(self.encoding)
508
509 def fromString(self, inString):
510 return inString.decode(self.encoding)
511
512# Temporary backwards compatibility for Exponent
513
514Body = String
515
516class Unicode(String):
517 def toString(self, inObject):
518 # assert isinstance(inObject, unicode)
519 return String.toString(self, inObject.encode('utf-8'))
520
521 def fromString(self, inString):
522 # assert isinstance(inString, str)
523 return String.fromString(self, inString).decode('utf-8')
524
525class Path(Unicode):
526 def fromString(self, inString):
527 return filepath.FilePath(Unicode.fromString(self, inString))
528
529 def toString(self, inObject):
530 return Unicode.toString(self, inObject.path)
531
532
533class Float(Argument):
534 fromString = float
535 toString = str
536
537class Base64Binary(Argument):
538 def toString(self, inObject):
539 return inObject.encode('base64').replace('\n', '')
540 def fromString(self, inString):
541 return inString.decode('base64')
542
543class Time(Argument):
544 def toString(self, inObject):
545 return inObject.asISO8601TimeAndDate()
546 def fromString(self, inString):
547 return extime.Time.fromISO8601TimeAndDate(inString)
548
549class ExtraArg:
550 def fromTransport(self, inTransport):
551 raise NotImplementedError()
552
553class Peer(ExtraArg):
554 def fromTransport(self, inTransport):
555 return inTransport.getQ2QPeer()
556
557class PeerDomain(ExtraArg):
558 def fromTransport(self, inTransport):
559 return inTransport.getQ2QPeer().domain
560
561class PeerUser(ExtraArg):
562 def fromTransport(self, inTransport):
563 return inTransport.getQ2QPeer().resource
564
565class Host(ExtraArg):
566 def fromTransport(self, inTransport):
567 return inTransport.getQ2QHost()
568
569class HostDomain(ExtraArg):
570 def fromTransport(self, inTransport):
571 return inTransport.getQ2QHost().domain
572
573class HostUser(ExtraArg):
574 def fromTransport(self, inTransport):
575 return inTransport.getQ2QHost().resource
576
577
578
579class Boolean(Argument):
580 def fromString(self, inString):
581 if inString == 'True':
582 return True
583 elif inString == 'False':
584 return False
585 else:
586 raise RuntimeError("Bad boolean value: %r" % (inString,))
587
588 def toString(self, inObject):
589 if inObject:
590 return 'True'
591 else:
592 return 'False'
593
594class Command:
595 class __metaclass__(type):
596 def __new__(cls, name, bases, attrs):
597 re = attrs['reverseErrors'] = {}
598 er = attrs['allErrors'] = {}
599 for v, k in attrs.get('errors',{}).iteritems():
600 re[k] = v
601 er[v] = k
602 for v, k in attrs.get('fatalErrors',{}).iteritems():
603 re[k] = v
604 er[v] = k
605 return type.__new__(cls, name, bases, attrs)
606
607 arguments = []
608 response = []
609 extra = []
610 namespaces = [None] # This is set to [None] on purpose: None means
611 # "no namespace", not "empty list". "empty
612 # list" will make your command invalid in _all_
613 # namespaces, effectively uncallable.
614 errors = {}
615 fatalErrors = {}
616
617 commandType = Box
618 responseType = Box
619
620 def commandName():
621 def get(self):
622 return self.__class__.__name__
623 raise NotImplementedError("Missing command name")
624 return get,
625 commandName = property(*commandName())
626
627 def __init__(self, **kw):
628 self.structured = kw
629 givenArgs = [normalizeKey(k) for k in kw.keys()]
630 forgotten = []
631 for name, arg in self.arguments:
632 if normalizeKey(name) not in givenArgs and not arg.optional:
633 forgotten.append(normalizeKey(name))
634# for v in kw.itervalues():
635# if v is None:
636# from pprint import pformat
637# raise RuntimeError("ARGH: %s" % pformat(kw))
638 if forgotten:
639 if len(forgotten) == 1:
640 plural = 'an argument'
641 else:
642 plural = 'some arguments'
643 raise RuntimeError("You forgot %s to %r: %s" % (
644 plural, self.commandName, ', '.join(forgotten)))
645 forgotten = []
646
647 def makeResponse(cls, objects, proto):
648 try:
649 return objectsToStrings(objects, cls.response, cls.responseType(), proto)
650 except:
651 log.msg("Exception in %r.makeResponse" % (cls,))
652 raise
653 makeResponse = classmethod(makeResponse)
654
655 def do(self, proto, namespace=None, requiresAnswer=True):
656 if namespace is not None:
657 cmd = namespace + ":" + self.commandName
658 else:
659 cmd = self.commandName
660 def _massageError(error):
661 error.trap(RemoteJuiceError)
662 rje = error.value
663 return Failure(self.reverseErrors.get(rje.errorCode, UnhandledRemoteJuiceError)(rje.description))
664
665 d = proto.sendBoxCommand(
666 cmd, objectsToStrings(self.structured, self.arguments, self.commandType(),
667 proto),
668 requiresAnswer)
669
670 if requiresAnswer:
671 d.addCallback(stringsToObjects, self.response, proto)
672 d.addCallback(self.addExtra, proto.transport)
673 d.addErrback(_massageError)
674
675 return d
676
677 def addExtra(self, d, transport):
678 for name, extraArg in self.extra:
679 d[name] = extraArg.fromTransport(transport)
680 return d
681
682
683class ProtocolSwitchCommand(Command):
684 """Use this command to switch from something Juice-derived to a different
685 protocol mid-connection. This can be useful to use juice as the
686 connection-startup negotiation phase. Since TLS is a different layer
687 entirely, you can use Juice to negotiate the security parameters of your
688 connection, then switch to a different protocol, and the connection will
689 remain secured.
690 """
691
692 def __init__(self, __protoToSwitchToFactory, **kw):
693 self.protoToSwitchToFactory = __protoToSwitchToFactory
694 super(ProtocolSwitchCommand, self).__init__(**kw)
695
696 def makeResponse(cls, innerProto, proto):
697 return _SwitchBox(innerProto)
698
699 makeResponse = classmethod(makeResponse)
700
701 def do(self, proto, namespace=None):
702 d = super(ProtocolSwitchCommand, self).do(proto)
703 proto._lock()
704 def switchNow(ign):
705 innerProto = self.protoToSwitchToFactory.buildProtocol(proto.transport.getPeer())
706 proto._switchTo(innerProto, self.protoToSwitchToFactory)
707 return ign
708 def die(ign):
709 proto.transport.loseConnection()
710 return ign
711 def handle(ign):
712 self.protoToSwitchToFactory.clientConnectionFailed(None, Failure(CONNECTION_LOST))
713 return ign
714 return d.addCallbacks(switchNow, handle).addErrback(die)
715
716class Negotiate(Command):
717 commandName = 'Negotiate'
718
719 arguments = [('versions', ListOf(Integer()))]
720 response = [('version', Integer())]
721
722 responseType = NegotiateBox
723
724
725class Juice(LineReceiver, JuiceParserBase):
726 """
727 JUICE (JUice Is Concurrent Events) is a simple connection-oriented
728 request/response protocol. Packets, or "boxes", are collections of
729 RFC2822-inspired headers, plus a body. Note that this is NOT a literal
730 interpretation of any existing RFC, 822, 2822 or otherwise, but a simpler
731 version that does not do line continuations, does not specify any
732 particular format for header values, dispatches semantic meanings of most
733 headers on the -Command header rather than giving them global meaning, and
734 allows multiple sets of headers (messages, or JuiceBoxes) on a connection.
735
736 All headers whose names begin with a dash ('-') are reserved for use by the
737 protocol. All others are for application use - their meaning depends on
738 the value of the "-Command" header.
739 """
740
741 protocolName = 'juice-base'
742
743 hostCertificate = None
744
745 MAX_LENGTH = 1024 * 1024
746
747 isServer = property(lambda self: self._issueGreeting,
748 doc="""
749 True if this is a juice server, e.g. it is going to
750 issue or has issued a server greeting upon
751 connection.
752 """)
753
754 isClient = property(lambda self: not self._issueGreeting,
755 doc="""
756 True if this is a juice server, e.g. it is not going to
757 issue or did not issue a server greeting upon
758 connection.
759 """)
760
761 def __init__(self, issueGreeting):
762 """
763 @param issueGreeting: whether to issue a greeting when connected. This
764 should be set on server-side Juice protocols.
765 """
766 JuiceParserBase.__init__(self)
767 self._issueGreeting = issueGreeting
768
769 def __repr__(self):
770 return '<%s %s/%s at 0x%x>' % (self.__class__.__name__, self.isClient and 'client' or 'server', self.innerProtocol, id(self))
771
772 __locked = False
773
774 def _lock(self):
775 """ Lock this Juice instance so that no further Juice traffic may be sent.
776 This is used when sending a request to switch underlying protocols.
777 You probably want to subclass ProtocolSwitchCommand rather than calling
778 this directly.
779 """
780 self.__locked = True
781
782 innerProtocol = None
783
784 def _switchTo(self, newProto, clientFactory=None):
785 """ Switch this Juice instance to a new protocol. You need to do this
786 'simultaneously' on both ends of a connection; the easiest way to do
787 this is to use a subclass of ProtocolSwitchCommand.
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches