Merge lp:~exarkun/divmod.org/remove-epsilon-1325289 into lp:divmod.org
- remove-epsilon-1325289
- Merge into trunk
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tristan Seligmann | Approve | ||
Review via email: mp+224912@code.launchpad.net |
Commit message
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
1 | === modified file 'Divmod.pth' |
2 | --- Divmod.pth 2014-06-08 12:14:35 +0000 |
3 | +++ Divmod.pth 2014-06-28 16:02:12 +0000 |
4 | @@ -1,7 +1,6 @@ |
5 | -# -*- test-case-name: axiom,combinator,epsilon,xmantissa,xquotient,reverend,sine,hyperbola -*- |
6 | +# -*- test-case-name: axiom,combinator,xmantissa,xquotient,reverend,sine,hyperbola -*- |
7 | Axiom |
8 | Combinator |
9 | -Epsilon |
10 | Mantissa |
11 | Quotient |
12 | Reverend |
13 | |
14 | === removed directory 'Epsilon' |
15 | === removed file 'Epsilon/.coveragerc' |
16 | --- Epsilon/.coveragerc 2014-01-13 11:18:20 +0000 |
17 | +++ Epsilon/.coveragerc 1970-01-01 00:00:00 +0000 |
18 | @@ -1,8 +0,0 @@ |
19 | -[run] |
20 | -branch = True |
21 | -source = |
22 | - epsilon |
23 | - |
24 | -[report] |
25 | -exclude_lines = |
26 | - pragma: no cover |
27 | |
28 | === removed file 'Epsilon/LICENSE' |
29 | --- Epsilon/LICENSE 2005-12-10 22:31:51 +0000 |
30 | +++ Epsilon/LICENSE 1970-01-01 00:00:00 +0000 |
31 | @@ -1,20 +0,0 @@ |
32 | -Copyright (c) 2005 Divmod Inc. |
33 | - |
34 | -Permission is hereby granted, free of charge, to any person obtaining |
35 | -a copy of this software and associated documentation files (the |
36 | -"Software"), to deal in the Software without restriction, including |
37 | -without limitation the rights to use, copy, modify, merge, publish, |
38 | -distribute, sublicense, and/or sell copies of the Software, and to |
39 | -permit persons to whom the Software is furnished to do so, subject to |
40 | -the following conditions: |
41 | - |
42 | -The above copyright notice and this permission notice shall be |
43 | -included in all copies or substantial portions of the Software. |
44 | - |
45 | -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
46 | -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
47 | -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
48 | -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
49 | -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
50 | -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
51 | -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
52 | \ No newline at end of file |
53 | |
54 | === removed file 'Epsilon/MANIFEST.in' |
55 | --- Epsilon/MANIFEST.in 2014-01-15 10:08:27 +0000 |
56 | +++ Epsilon/MANIFEST.in 1970-01-01 00:00:00 +0000 |
57 | @@ -1,3 +0,0 @@ |
58 | -include LICENSE |
59 | -include NAME.txt |
60 | -include NEWS.txt |
61 | |
62 | === removed file 'Epsilon/NAME.txt' |
63 | --- Epsilon/NAME.txt 2005-08-27 23:09:07 +0000 |
64 | +++ Epsilon/NAME.txt 1970-01-01 00:00:00 +0000 |
65 | @@ -1,13 +0,0 @@ |
66 | - |
67 | -See: http://mathworld.wolfram.com/Epsilon.html |
68 | - |
69 | -The constant 'epsilon' is a value that is as close as possible to zero without |
70 | -being zero. It is frequently used by computer scientists to refer to values |
71 | -which are negligeable. |
72 | - |
73 | -Divmod Epsilon is named for that because it is a small body of code upon which |
74 | -all of our other projects depend. It has no particular theme associated with |
75 | -it, except to remain small and lightweight, and enforce certain conventions and |
76 | -to provide common conveniences that do not belong in any lower level of |
77 | -infrastructure. |
78 | - |
79 | |
80 | === removed file 'Epsilon/NEWS.txt' |
81 | --- Epsilon/NEWS.txt 2014-01-15 10:21:17 +0000 |
82 | +++ Epsilon/NEWS.txt 1970-01-01 00:00:00 +0000 |
83 | @@ -1,113 +0,0 @@ |
84 | -0.7.0 (2014-01-15): |
85 | - Major: |
86 | - |
87 | - - Only Python 2.6 and 2.7 are supported now. 2.4, 2.5 is deprecated. |
88 | - - setup.py now uses setuptools, and stores its dependencies. This |
89 | - means you no longer need to manually install dependencies. |
90 | - - setup.py no longer requires Twisted for egg_info, making it easier |
91 | - to install Epsilon using pip. |
92 | - - Significant improvements to PyPy support. PyPy is now a supported |
93 | - platform, with CI support. |
94 | - - epsilon.release is now removed. It relied on a bunch of machinery |
95 | - specific to divmod that no longer existed. |
96 | - - epsilon.sslverify is now removed. Use twisted.internet.ssl instead. |
97 | - - epsilon.asTwistedVersion takes a string version ("1.2.3") and |
98 | - turns it into a twisted.python.versions.Version. |
99 | - |
100 | - Minor: |
101 | - |
102 | - - Several deprecation warnings have been cleaned up. |
103 | - |
104 | -0.6.0 (2009-11-25): |
105 | - - Disable loopback hotfix on Twisted 8.2 and newer. |
106 | - - Remove the implementation of Cooperator and use Twisted's implementation |
107 | - instead. |
108 | - - Use Twisted's deferLater implementation. |
109 | - - Add a service for communicating via stdio. |
110 | - - Add a `precision` argument to `Time.asHumanly` to control the precision |
111 | - of the returned string. |
112 | - |
113 | -0.5.12 (2008-12-09): |
114 | - - Added support for AMP authentication via one-time pads. |
115 | - |
116 | -0.5.11 (2008-10-02): |
117 | - - epsilon.amprouter added, providing support for multiplexing |
118 | - unrelated AMP communications over the same connection. |
119 | - |
120 | -0.5.10 (2008-08-12): |
121 | - - Added the epsilon.caseless module, with case-insensitive string |
122 | - wrappers. |
123 | - - Better repr() for epsilon.structlike.record added. |
124 | - - epsilon.juice now uses twisted.internet.ssl instead of epsilon.sslverify. |
125 | - |
126 | -0.5.9 (2008-01-18): |
127 | - |
128 | -0.5.8 (2007-11-27): |
129 | - - extime.Time.asHumanly() no longer shows a time of day for all-day timestamps. |
130 | - |
131 | -0.5.7 (2007-04-27): |
132 | - - view.SlicedView added, allowing slicing and indexing of large |
133 | - sequences without copying. |
134 | - |
135 | -0.5.6 (2006-11-20): |
136 | - - Added a --quiet option to Epsilon's certcreate and use it in a few unit |
137 | - tests to avoid spewing garbage during test runs. |
138 | - |
139 | -0.5.5 (2006-10-21): |
140 | - - extime.Time now accepts RFC2822-like dates with invalid fields: it |
141 | - rounds them to the nearest valid value. |
142 | - |
143 | -0.5.4 (2006-10-17): |
144 | - - extime.Time now accepts RFC2822-like dates with no timezone. |
145 | - |
146 | -0.5.3 (2006-09-20): |
147 | - - structlike.Record now raises TypeError on unexpected args. |
148 | - |
149 | -0.5.2 (2006-09-12): |
150 | - - extime.Time now avoids time_t overflow bugs. |
151 | - |
152 | -0.5.1 (2006-06-22): |
153 | - - Added hotfix for twisted.test.proto_helpers.StringTransport. |
154 | - |
155 | -0.5.0 (2006-06-12): |
156 | - - Replaced '%y' with '%Y' in Time.asHumanly() output - the year is now |
157 | - four digits, rather than two. |
158 | - - Added new 'epsilon.structlike' functionality for simple record. |
159 | - - All uses of defer.wait and deferredResult were removed from the tests. |
160 | - - Added epsilon.juice, an asynchronous messaging protocol slated for |
161 | - inclusion in Twisted. Improved a few features, such as the repr() of |
162 | - JuiceBox instances. This was moved from Vertex. |
163 | - - Added epsilon.sslverify, a set of utilities for dealing with PyOpenSSL |
164 | - using simple high-level objects, performing operations such as signing and |
165 | - verifying certificates. This was also moved from Vertex, and slated for |
166 | - inclusion in Twisted. |
167 | - - Added epsilon.spewer, a prettier version of the spewer in |
168 | - twisted.python.util. |
169 | - - Added "benchmark" tool for measuring and reporting run-times of python |
170 | - programs. |
171 | - |
172 | -0.4.0 (2005-12-20): |
173 | - - Disabled crazy sys.modules hackery in test_setuphelper |
174 | - - Added module for creating a directory structure from a string template |
175 | - - Added support for 'now' to Time.fromHumanly() |
176 | - - Added a structured "hotfix" system to abstract and formalize monkey |
177 | - patches and version testing logic away from code which requires it. |
178 | - |
179 | -0.3.2 (2005-11-05): |
180 | - - Added automatic support for Twisted plugins to autosetup |
181 | - |
182 | -0.3.1 (2005-11-02): |
183 | - - Removed bogus dependency on Axiom. |
184 | - |
185 | -0.3.0 (2005-11-02): |
186 | - - Added SchedulingService, an IService implementation, to epsilon.cooperator |
187 | - - Added autosetup, a utility to actually include files in distutils releases, |
188 | - to epsilon.setuphelper |
189 | - |
190 | -0.2.1 (2005-10-25): |
191 | - - Added 'short()' to epsilon.versions.Version |
192 | - - fixed setup.py to use epsilon.version.short() rather than static string. |
193 | - |
194 | -0.2.0 (2005-10-25): |
195 | - - Added epsilon.modal.ModalType, metaclass for writing classes that |
196 | - behave in some respects like state machines |
197 | |
198 | === removed file 'Epsilon/README' |
199 | --- Epsilon/README 2006-06-14 11:54:41 +0000 |
200 | +++ Epsilon/README 1970-01-01 00:00:00 +0000 |
201 | @@ -1,9 +0,0 @@ |
202 | - |
203 | -Divmod Epsilon |
204 | -============== |
205 | - |
206 | -Epsilon is a set of utility modules, commonly used by all Divmod projects. |
207 | - |
208 | -This is intended mainly as a support package for code used by Divmod projects, |
209 | -and not as an external library. However, it contains many useful modules and |
210 | -you can feel free to use them! |
211 | |
212 | === removed directory 'Epsilon/bin' |
213 | === removed file 'Epsilon/bin/benchmark' |
214 | --- Epsilon/bin/benchmark 2006-05-19 15:23:46 +0000 |
215 | +++ Epsilon/bin/benchmark 1970-01-01 00:00:00 +0000 |
216 | @@ -1,4 +0,0 @@ |
217 | -#!/usr/bin/python |
218 | - |
219 | -from epsilon.scripts import benchmark |
220 | -benchmark.main() |
221 | |
222 | === removed file 'Epsilon/bin/certcreate' |
223 | --- Epsilon/bin/certcreate 2006-06-08 19:22:15 +0000 |
224 | +++ Epsilon/bin/certcreate 1970-01-01 00:00:00 +0000 |
225 | @@ -1,5 +0,0 @@ |
226 | -#!/usr/bin/python |
227 | -# Copyright 2005 Divmod, Inc. See LICENSE file for details |
228 | - |
229 | -from epsilon.scripts import certcreate |
230 | -certcreate.main() |
231 | |
232 | === removed directory 'Epsilon/doc' |
233 | === removed file 'Epsilon/doc/amp-auth.xhtml' |
234 | --- Epsilon/doc/amp-auth.xhtml 2008-11-27 07:37:39 +0000 |
235 | +++ Epsilon/doc/amp-auth.xhtml 1970-01-01 00:00:00 +0000 |
236 | @@ -1,115 +0,0 @@ |
237 | -<?xml version="1.0"?> |
238 | -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
239 | - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
240 | - |
241 | -<html xmlns="http://www.w3.org/1999/xhtml"> |
242 | - <head> |
243 | - <title>AMP Authentication</title> |
244 | - </head> |
245 | - <body> |
246 | - <h1>AMP Authentication</h1> |
247 | - |
248 | - <h2>Overview</h2> |
249 | - |
250 | - <p> |
251 | - <code class="API">epsilon.ampauth</code> integrates Twisted Cred with |
252 | - <code>twisted.protocols.amp</code>, providing support for selecting a |
253 | - <code>IBoxReceiver</code> based on the result of a Cred login. |
254 | - </p> |
255 | - |
256 | - <p> |
257 | - Readers should familiarize themselves with the following concepts in |
258 | - order to understand all sections of this document: |
259 | - </p> |
260 | - |
261 | - <ul> |
262 | - <li> |
263 | - Twisted <a |
264 | - href="http://twistedmatrix.com/projects/core/documentation/howto/clients.html">TCP |
265 | - clients</a> and <a |
266 | - href="http://twistedmatrix.com/projects/core/documentation/howto/servers.html">TCP |
267 | - servers</a> |
268 | - </li> |
269 | - <li> |
270 | - <a href="http://twistedmatrix.com/projects/core/documentation/howto/defer.html"> |
271 | - Using Deferreds |
272 | - </a> |
273 | - </li> |
274 | - <li> |
275 | - Twisted <code class="API" base="twisted.protocols.amp">AMP</code> |
276 | - </li> |
277 | - <li> |
278 | - <a href="http://twistedmatrix.com/projects/core/documentation/howto/cred.html"> |
279 | - Twisted Cred |
280 | - </a> |
281 | - </li> |
282 | - </ul> |
283 | - |
284 | - <h2>Servers</h2> |
285 | - |
286 | - <p> |
287 | - <code class="API" base="epsilon.ampauth">CredAMPServerFactory</code> |
288 | - is a factory for the <code class="API" |
289 | - base="epsilon.ampauth">CredReceiver</code> protocol, an |
290 | - <code>AMP</code> subclass which implements responders for commands |
291 | - which allow a client to prove their identity. It uses a |
292 | - <code>Portal</code> to handle these commands and retrieve an <code |
293 | - class="API" base="twisted.protocols.amp">IBoxReceiver</code> which |
294 | - will be used to handle all further AMP boxes it receives. |
295 | - </p> |
296 | - |
297 | - <a href="listings/amp/auth_server.py" class="py-listing"> |
298 | - AMP server with authentication |
299 | - </a> |
300 | - |
301 | - <p> |
302 | - <code>Add</code> and <code>Adder</code> together define a simple AMP |
303 | - protocol for adding two integers together. <code>AdditionRealm</code> |
304 | - provides the necessary integration between this AMP protocol and Cred, |
305 | - creating new <code>Adder</code> instances whenever an |
306 | - <code>IBoxReceiver</code> is requested - which will be whenever a client |
307 | - attempts to authenticate itself to the server. |
308 | - </p> |
309 | - |
310 | - <h2>Clients</h2> |
311 | - |
312 | - <p> |
313 | - AMP clients can authenticate with an AMP server using <code class="API" |
314 | - base="epsilon.ampauth">login</code>. <code>login</code> takes a |
315 | - connected AMP instance and a credentials object as arguments and returns |
316 | - a <code>Deferred</code> which fires when authentication finishes. |
317 | - </p> |
318 | - |
319 | - <a href="listings/amp/auth_client.py" class="py-listing"> |
320 | - Authenticating AMP client |
321 | - </a> |
322 | - |
323 | - <p> |
324 | - The TCP connection is set up as usual, and the <code>Add</code> command |
325 | - is also issued in the usual way. The only change from a normal AMP |
326 | - client is the use of <code>login</code> after a connection has been set |
327 | - up but before any commands are issued. |
328 | - </p> |
329 | - |
330 | - <h2>One-Time Pad Authentication</h2> |
331 | - |
332 | - <p> |
333 | - <code class="API">epsilon.ampauth</code> includes an <code class="API" |
334 | - base="twisted.cred.checkers">CredentialsChecker</code> for validating |
335 | - one-time pads: <code class="API" |
336 | - base="epsilon.ampauth">OneTimePadChecker</code>. If this checker is |
337 | - registered with the portal, clients may use the <code class="API" |
338 | - base="epsilon.ampauth">OTPLogin</code> command to authenticate. |
339 | - </p> |
340 | - |
341 | - <a href="listings/amp/amp_auth_server.py" class="py-listing"> |
342 | - AMP server with OTP authentication |
343 | - </a> |
344 | - |
345 | - <p></p> |
346 | - |
347 | - <a href="listings/amp/amp_auth_client.py" class="py-listing"> |
348 | - OTP-authenticating AMP client |
349 | - </a> |
350 | - </body> |
351 | -</html> |
352 | |
353 | === removed file 'Epsilon/doc/amp-routes.xhtml' |
354 | --- Epsilon/doc/amp-routes.xhtml 2008-08-29 16:02:56 +0000 |
355 | +++ Epsilon/doc/amp-routes.xhtml 1970-01-01 00:00:00 +0000 |
356 | @@ -1,180 +0,0 @@ |
357 | -<?xml version="1.0"?> |
358 | -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
359 | - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
360 | - |
361 | -<html xmlns="http://www.w3.org/1999/xhtml"> |
362 | - <head> |
363 | - <title>AMP Routes</title> |
364 | - </head> |
365 | - <body> |
366 | - <h1>AMP Routes</h1> |
367 | - |
368 | - <h2>Overview</h2> |
369 | - |
370 | - <p> |
371 | - Normally, an AMP connection is between two <code class="API" |
372 | - base="twisted.protocols.amp">AMP</code> instances; each instance receives |
373 | - all AMP boxes sent by the other side and handles them by interpreting |
374 | - them as commands, responses to commands, or in some other way. This |
375 | - typically means that the logic for handling boxes on each side of the |
376 | - connection is completely defined by a single object. Sometimes it is |
377 | - useful to allow multiple objects, perhaps of different types, to |
378 | - participate in defining this logic. |
379 | - </p> |
380 | - |
381 | - <p> |
382 | - <code>epsilon.amprouter</code> implements utilities which allow an |
383 | - arbitrary number of objects, providers of <code>IBoxReceiver</code> (for |
384 | - example, instances of <code>AMP</code>), to define how received AMP boxes |
385 | - are interpreted. This is useful to multiplex unrelated <code>AMP</code> |
386 | - instances over a single TCP connection, to split up a single AMP protocol |
387 | - into multiple simpler protocols, and for many other purposes. |
388 | - </p> |
389 | - |
390 | - <p> |
391 | - Readers should familiarize themselves with the following concepts in |
392 | - order to understand all sections of this document: |
393 | - </p> |
394 | - |
395 | - <ul> |
396 | - <li> |
397 | - Twisted <a |
398 | - href="http://twistedmatrix.com/projects/core/documentation/howto/clients.html">TCP |
399 | - clients</a> and <a |
400 | - href="http://twistedmatrix.com/projects/core/documentation/howto/servers.html">TCP |
401 | - servers</a> |
402 | - </li> |
403 | - <li> |
404 | - <a href="http://twistedmatrix.com/projects/core/documentation/howto/defer.html"> |
405 | - Using Deferreds |
406 | - </a> |
407 | - </li> |
408 | - <li> |
409 | - Twisted <code class="API" base="twisted.protocols.amp">AMP</code> |
410 | - </li> |
411 | - </ul> |
412 | - |
413 | - <h2>Routers</h2> |
414 | - |
415 | - <p> |
416 | - When working with routes, the object primarily of interest will be a <a |
417 | - class="API" base="epsilon.amprouter">Router</a> instance. Each AMP |
418 | - client and server will have a <code>Router</code> instance which they can |
419 | - use to create new routes. They will use its <code class="API" |
420 | - base="epsilon.amprouter">Router.bindRoute</code> method to set up |
421 | - whatever routes they require. |
422 | - </p> |
423 | - |
424 | - <h2>Servers</h2> |
425 | - |
426 | - <p> |
427 | - <code>epsilon.amprouter</code> does not define a command for creating new |
428 | - routes because different applications have different requirements for how |
429 | - new routes are set up. An application may want to negotiate about the |
430 | - <code>IBoxReceiver</code> implementation which is associated with a |
431 | - route, it may want to supply initial arguments to that object, it may |
432 | - want to do version negotiation, and so on. The first thing an |
433 | - application using routes must do, then, is to define a way to create new |
434 | - routes. Consider the following example which allows routes to be created |
435 | - with a <code>NewRoute</code> AMP command and associates them with a |
436 | - parameterized <code>IBoxReceiver</code> implementation. |
437 | - </p> |
438 | - |
439 | - <a href="listings/amp/route_setup.py" class="py-listing"> |
440 | - Creation of new routes |
441 | - </a> |
442 | - |
443 | - <p> |
444 | - <code>AMPRouteServerFactory.buildProtocol</code> creates new |
445 | - <code>RoutingAMP</code> instances, each with a new <code>Router</code>. |
446 | - The <code>Router</code> instance will become the <code>RoutingAMP</code> |
447 | - instance's <code>boxReceiver</code> attribute. This is important for two |
448 | - reasons. First, it allows the router to work by causing all AMP boxes |
449 | - received from the connection to be delivered to the router to be |
450 | - dispatched appropriately. Second, it gives the <code>RoutingAMP</code> |
451 | - instance a reference to the <code>Router</code> instance; this is |
452 | - necessary so that new routes can be created. |
453 | - </p> |
454 | - |
455 | - <p> |
456 | - After creating the <code>Router</code> and <code>RoutingAMP</code>, |
457 | - <code>buildProtocol</code> also sets up the <code>RoutingAMP</code> |
458 | - instance to be the default receiver by binding it to the |
459 | - <code>None</code>. All AMP boxes without routing information will be |
460 | - delivered to the default receiver. This is important because it allows |
461 | - the <code>NewRoute</code> command to be handled by the |
462 | - <code>RoutingAMP</code> instance. |
463 | - </p> |
464 | - |
465 | - <p> |
466 | - <code>RoutingAMP</code>'s <code>NewRoute</code> responder uses |
467 | - <code>self.boxReceiver</code>, the <code>Router</code> instance provided |
468 | - by the factory, to <em>bind</em> the return value of |
469 | - <code>self.factory.routeProtocol()</code> to a new route. Then, it |
470 | - connects the route to the identifier specified in the |
471 | - <code>NewRoute</code> command. Finally, it returns the identifier of the |
472 | - route it has just created. Once this has happened, the route is |
473 | - completely set up on the server. |
474 | - </p> |
475 | - |
476 | - <p> |
477 | - Finally, the <code>connect</code> function wraps up the necessary calls |
478 | - to routing methods and a use of the <code>NewRoute</code> command to form |
479 | - the client side of the setup. |
480 | - </p> |
481 | - |
482 | - <p> |
483 | - First, let's look at an example of using <code>AMPRouteServerFactory</code> and |
484 | - <code>RoutingAMP</code> to run a server. |
485 | - </p> |
486 | - |
487 | - <a href="listings/amp/route_server.py" class="py-listing"> |
488 | - Routed counters server |
489 | - </a> |
490 | - |
491 | - <p> |
492 | - In this example, a simple counting protocol is hooked up to the server. |
493 | - Each route which is created is associated with a new instance of this |
494 | - protocol. The protocol does just one simple thing, it keeps track of how |
495 | - many times the <code>Count</code> command is issued to it and returns |
496 | - this value in the response to that command. |
497 | - </p> |
498 | - |
499 | - <p> |
500 | - Next we'll look at how a client can connect to this server, create new |
501 | - routes, and issue commands over them. |
502 | - </p> |
503 | - |
504 | - <h2>Clients</h2> |
505 | - |
506 | - <p> |
507 | - Just as servers must, clients must first set up a route before they can |
508 | - send boxes over it. A client uses the same methods as the server, |
509 | - <code>Router.bindRoute</code> and <code>Route.connectTo</code>, to set up |
510 | - a new route. Here's an example which makes one TCP connection to an AMP |
511 | - server, sets up three routes, and then issues multiple commands over each |
512 | - of them. |
513 | - </p> |
514 | - |
515 | - <a href="listings/amp/route_client.py" class="py-listing"> |
516 | - Routed counters client |
517 | - </a> |
518 | - |
519 | - <p> |
520 | - Note first how <code>main</code> creates an <code>AMP</code> with a |
521 | - <code>Router</code> instance. Note also how <code>makeRoutes</code> |
522 | - binds and connects the protocol to the default route. This mirrors the |
523 | - route setup which was done on the server and is necessary for the same |
524 | - reasons. |
525 | - </p> |
526 | - |
527 | - <p> |
528 | - Once an AMP connection is set up and the default route is bound, |
529 | - <code>makeRoutes</code> uses the previously defined <code>connect</code> |
530 | - function to establish three new routes. Each route is associated with a |
531 | - <code>CountClient</code> instance which will issue several count commands |
532 | - and report the results. The results of each command are tracked so that |
533 | - when they have all been received the client can exit. |
534 | - </p> |
535 | - </body> |
536 | -</html> |
537 | |
538 | === removed file 'Epsilon/doc/index.xhtml' |
539 | --- Epsilon/doc/index.xhtml 2008-08-29 16:02:56 +0000 |
540 | +++ Epsilon/doc/index.xhtml 1970-01-01 00:00:00 +0000 |
541 | @@ -1,21 +0,0 @@ |
542 | -<?xml version="1.0"?> |
543 | -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
544 | - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
545 | - |
546 | -<html xmlns="http://www.w3.org/1999/xhtml"> |
547 | - <head> |
548 | - <title>Index</title> |
549 | - </head> |
550 | - <body> |
551 | - <h1>Index</h1> |
552 | - |
553 | - <ul class="toc"> |
554 | - <li> |
555 | - <a href="amp-auth.xhtml">AMP Authentication</a> |
556 | - </li> |
557 | - <li> |
558 | - <a href="amp-routes.xhtml">AMP Routes</a> |
559 | - </li> |
560 | - </ul> |
561 | - </body> |
562 | -</html> |
563 | |
564 | === removed directory 'Epsilon/doc/listings' |
565 | === removed directory 'Epsilon/doc/listings/amp' |
566 | === removed file 'Epsilon/doc/listings/amp/amp_auth_client.py' |
567 | --- Epsilon/doc/listings/amp/amp_auth_client.py 2008-11-27 07:37:39 +0000 |
568 | +++ Epsilon/doc/listings/amp/amp_auth_client.py 1970-01-01 00:00:00 +0000 |
569 | @@ -1,42 +0,0 @@ |
570 | -# Copyright (c) 2008 Divmod. See LICENSE for details. |
571 | - |
572 | -""" |
573 | -An AMP client which connects to and authenticates with an AMP server using OTP, |
574 | -then issues a command. |
575 | -""" |
576 | - |
577 | -from twisted.internet.protocol import ClientCreator |
578 | -from twisted.cred.credentials import UsernamePassword |
579 | -from twisted.protocols.amp import AMP |
580 | - |
581 | -from epsilon.react import react |
582 | -from epsilon.ampauth import OTPLogin |
583 | - |
584 | -from auth_server import Add |
585 | - |
586 | - |
587 | -def add(proto): |
588 | - return proto.callRemote(Add, left=17, right=33) |
589 | - |
590 | - |
591 | -def display(result): |
592 | - print result |
593 | - |
594 | - |
595 | -def otpLogin(client): |
596 | - client.callRemote(OTPLogin, pad='pad') |
597 | - return client |
598 | - |
599 | - |
600 | -def main(reactor): |
601 | - cc = ClientCreator(reactor, AMP) |
602 | - d = cc.connectTCP('localhost', 7805) |
603 | - d.addCallback(otpLogin) |
604 | - d.addCallback(add) |
605 | - d.addCallback(display) |
606 | - return d |
607 | - |
608 | - |
609 | -if __name__ == '__main__': |
610 | - from twisted.internet import reactor |
611 | - react(reactor, main, []) |
612 | |
613 | === removed file 'Epsilon/doc/listings/amp/amp_auth_server.py' |
614 | --- Epsilon/doc/listings/amp/amp_auth_server.py 2008-11-27 07:37:39 +0000 |
615 | +++ Epsilon/doc/listings/amp/amp_auth_server.py 1970-01-01 00:00:00 +0000 |
616 | @@ -1,76 +0,0 @@ |
617 | -# Copyright (c) 2008 Divmod. See LICENSE for details. |
618 | - |
619 | -""" |
620 | -An AMP server which requires authentication of its clients before exposing an |
621 | -addition command. |
622 | -""" |
623 | - |
624 | -from sys import stdout |
625 | - |
626 | -from twisted.python.log import startLogging, msg |
627 | -from twisted.internet import reactor |
628 | -from twisted.cred.portal import Portal |
629 | -from twisted.protocols.amp import IBoxReceiver, Command, Integer, AMP |
630 | - |
631 | -from epsilon.ampauth import CredAMPServerFactory, OneTimePadChecker |
632 | - |
633 | - |
634 | -class Add(Command): |
635 | - """ |
636 | - An example of an application-defined command which should be made available |
637 | - to clients after they successfully authenticate. |
638 | - """ |
639 | - arguments = [("left", Integer()), |
640 | - ("right", Integer())] |
641 | - |
642 | - response = [("sum", Integer())] |
643 | - |
644 | - |
645 | - |
646 | -class Adder(AMP): |
647 | - """ |
648 | - An example of an application-defined AMP protocol, the responders defined |
649 | - by which should only be available to clients after they have successfully |
650 | - authenticated. |
651 | - """ |
652 | - def __init__(self, avatarId): |
653 | - AMP.__init__(self) |
654 | - self.avatarId = avatarId |
655 | - |
656 | - |
657 | - @Add.responder |
658 | - def add(self, left, right): |
659 | - msg("Adding %d to %d for %s" % (left, right, self.avatarId)) |
660 | - return {'sum': left + right} |
661 | - |
662 | - |
663 | - |
664 | -class AdditionRealm(object): |
665 | - """ |
666 | - An example of an application-defined realm. |
667 | - """ |
668 | - def requestAvatar(self, avatarId, mind, *interfaces): |
669 | - """ |
670 | - Create Adder avatars for any IBoxReceiver request. |
671 | - """ |
672 | - if IBoxReceiver in interfaces: |
673 | - return (IBoxReceiver, Adder(avatarId), lambda: None) |
674 | - raise NotImplementedError() |
675 | - |
676 | - |
677 | - |
678 | -def main(): |
679 | - """ |
680 | - Start the AMP server and the reactor. |
681 | - """ |
682 | - startLogging(stdout) |
683 | - checker = OneTimePadChecker({'pad': 0}) |
684 | - realm = AdditionRealm() |
685 | - factory = CredAMPServerFactory(Portal(realm, [checker])) |
686 | - reactor.listenTCP(7805, factory) |
687 | - reactor.run() |
688 | - |
689 | - |
690 | -if __name__ == '__main__': |
691 | - main() |
692 | - |
693 | |
694 | === removed file 'Epsilon/doc/listings/amp/auth_client.py' |
695 | --- Epsilon/doc/listings/amp/auth_client.py 2008-08-29 16:02:56 +0000 |
696 | +++ Epsilon/doc/listings/amp/auth_client.py 1970-01-01 00:00:00 +0000 |
697 | @@ -1,37 +0,0 @@ |
698 | -# Copyright (c) 2008 Divmod. See LICENSE for details. |
699 | - |
700 | -""" |
701 | -An AMP client which connects to and authenticates with an AMP server, then |
702 | -issues a command. |
703 | -""" |
704 | - |
705 | -from twisted.internet.protocol import ClientCreator |
706 | -from twisted.cred.credentials import UsernamePassword |
707 | -from twisted.protocols.amp import AMP |
708 | - |
709 | -from epsilon.react import react |
710 | -from epsilon.ampauth import login |
711 | - |
712 | -from auth_server import Add |
713 | - |
714 | - |
715 | -def add(proto): |
716 | - return proto.callRemote(Add, left=17, right=33) |
717 | - |
718 | - |
719 | -def display(result): |
720 | - print result |
721 | - |
722 | - |
723 | -def main(reactor): |
724 | - cc = ClientCreator(reactor, AMP) |
725 | - d = cc.connectTCP('localhost', 7805) |
726 | - d.addCallback(login, UsernamePassword("testuser", "examplepass")) |
727 | - d.addCallback(add) |
728 | - d.addCallback(display) |
729 | - return d |
730 | - |
731 | - |
732 | -if __name__ == '__main__': |
733 | - from twisted.internet import reactor |
734 | - react(reactor, main, []) |
735 | |
736 | === removed file 'Epsilon/doc/listings/amp/auth_server.py' |
737 | --- Epsilon/doc/listings/amp/auth_server.py 2008-11-05 18:50:57 +0000 |
738 | +++ Epsilon/doc/listings/amp/auth_server.py 1970-01-01 00:00:00 +0000 |
739 | @@ -1,77 +0,0 @@ |
740 | -# Copyright (c) 2008 Divmod. See LICENSE for details. |
741 | - |
742 | -""" |
743 | -An AMP server which requires authentication of its clients before exposing an |
744 | -addition command. |
745 | -""" |
746 | - |
747 | -from sys import stdout |
748 | - |
749 | -from twisted.python.log import startLogging, msg |
750 | -from twisted.internet import reactor |
751 | -from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse |
752 | -from twisted.cred.portal import Portal |
753 | -from twisted.protocols.amp import IBoxReceiver, Command, Integer, AMP |
754 | - |
755 | -from epsilon.ampauth import CredAMPServerFactory |
756 | - |
757 | - |
758 | -class Add(Command): |
759 | - """ |
760 | - An example of an application-defined command which should be made available |
761 | - to clients after they successfully authenticate. |
762 | - """ |
763 | - arguments = [("left", Integer()), |
764 | - ("right", Integer())] |
765 | - |
766 | - response = [("sum", Integer())] |
767 | - |
768 | - |
769 | - |
770 | -class Adder(AMP): |
771 | - """ |
772 | - An example of an application-defined AMP protocol, the responders defined |
773 | - by which should only be available to clients after they have successfully |
774 | - authenticated. |
775 | - """ |
776 | - def __init__(self, avatarId): |
777 | - AMP.__init__(self) |
778 | - self.avatarId = avatarId |
779 | - |
780 | - |
781 | - @Add.responder |
782 | - def add(self, left, right): |
783 | - msg("Adding %d to %d for %s" % (left, right, self.avatarId)) |
784 | - return {'sum': left + right} |
785 | - |
786 | - |
787 | - |
788 | -class AdditionRealm(object): |
789 | - """ |
790 | - An example of an application-defined realm. |
791 | - """ |
792 | - def requestAvatar(self, avatarId, mind, *interfaces): |
793 | - """ |
794 | - Create Adder avatars for any IBoxReceiver request. |
795 | - """ |
796 | - if IBoxReceiver in interfaces: |
797 | - return (IBoxReceiver, Adder(avatarId), lambda: None) |
798 | - raise NotImplementedError() |
799 | - |
800 | - |
801 | - |
802 | -def main(): |
803 | - """ |
804 | - Start the AMP server and the reactor. |
805 | - """ |
806 | - startLogging(stdout) |
807 | - checker = InMemoryUsernamePasswordDatabaseDontUse() |
808 | - checker.addUser("testuser", "examplepass") |
809 | - realm = AdditionRealm() |
810 | - factory = CredAMPServerFactory(Portal(realm, [checker])) |
811 | - reactor.listenTCP(7805, factory) |
812 | - reactor.run() |
813 | - |
814 | - |
815 | -if __name__ == '__main__': |
816 | - main() |
817 | |
818 | === removed file 'Epsilon/doc/listings/amp/route_client.py' |
819 | --- Epsilon/doc/listings/amp/route_client.py 2008-08-29 16:02:56 +0000 |
820 | +++ Epsilon/doc/listings/amp/route_client.py 1970-01-01 00:00:00 +0000 |
821 | @@ -1,60 +0,0 @@ |
822 | -# Copyright (c) 2008 Divmod. See LICENSE for details. |
823 | - |
824 | -import random |
825 | - |
826 | -from twisted.internet.defer import Deferred, gatherResults |
827 | -from twisted.internet.protocol import ClientCreator |
828 | -from twisted.protocols.amp import AMP |
829 | - |
830 | -from epsilon.react import react |
831 | -from epsilon.amprouter import Router |
832 | - |
833 | -from route_setup import connect |
834 | -from route_server import Count |
835 | - |
836 | - |
837 | -def display(value, id): |
838 | - print id, value |
839 | - |
840 | - |
841 | -class CountClient(AMP): |
842 | - def __init__(self, identifier): |
843 | - AMP.__init__(self) |
844 | - self.identifier = identifier |
845 | - self.finished = Deferred() |
846 | - |
847 | - def startReceivingBoxes(self, sender): |
848 | - AMP.startReceivingBoxes(self, sender) |
849 | - |
850 | - counts = [] |
851 | - for i in range(random.randrange(1, 5)): |
852 | - d = self.callRemote(Count) |
853 | - d.addCallback(display, self.identifier) |
854 | - counts.append(d) |
855 | - gatherResults(counts).chainDeferred(self.finished) |
856 | - |
857 | - |
858 | - |
859 | -def makeRoutes(proto, router): |
860 | - router.bindRoute(proto, None).connectTo(None) |
861 | - |
862 | - finish = [] |
863 | - for i in range(3): |
864 | - client = CountClient(i) |
865 | - finish.append(connect(proto, router, client)) |
866 | - finish.append(client.finished) |
867 | - return gatherResults(finish) |
868 | - |
869 | - |
870 | - |
871 | -def main(reactor): |
872 | - router = Router() |
873 | - cc = ClientCreator(reactor, AMP, router) |
874 | - d = cc.connectTCP('localhost', 7805) |
875 | - d.addCallback(makeRoutes, router) |
876 | - return d |
877 | - |
878 | - |
879 | -if __name__ == '__main__': |
880 | - from twisted.internet import reactor |
881 | - react(reactor, main, []) |
882 | |
883 | === removed file 'Epsilon/doc/listings/amp/route_server.py' |
884 | --- Epsilon/doc/listings/amp/route_server.py 2008-11-05 18:50:57 +0000 |
885 | +++ Epsilon/doc/listings/amp/route_server.py 1970-01-01 00:00:00 +0000 |
886 | @@ -1,37 +0,0 @@ |
887 | -# Copyright (c) 2008 Divmod. See LICENSE for details. |
888 | - |
889 | -from sys import stdout |
890 | - |
891 | -from twisted.python.log import startLogging |
892 | -from twisted.protocols.amp import Integer, Command, AMP |
893 | -from twisted.internet import reactor |
894 | - |
895 | -from route_setup import AMPRouteServerFactory |
896 | - |
897 | - |
898 | -class Count(Command): |
899 | - response = [('value', Integer())] |
900 | - |
901 | - |
902 | - |
903 | -class Counter(AMP): |
904 | - _valueCounter = 0 |
905 | - |
906 | - @Count.responder |
907 | - def count(self): |
908 | - self._valueCounter += 1 |
909 | - return {'value': self._valueCounter} |
910 | - |
911 | - |
912 | - |
913 | -def main(): |
914 | - startLogging(stdout) |
915 | - serverFactory = AMPRouteServerFactory() |
916 | - serverFactory.routeProtocol = Counter |
917 | - reactor.listenTCP(7805, serverFactory) |
918 | - reactor.run() |
919 | - |
920 | - |
921 | - |
922 | -if __name__ == '__main__': |
923 | - main() |
924 | |
925 | === removed file 'Epsilon/doc/listings/amp/route_setup.py' |
926 | --- Epsilon/doc/listings/amp/route_setup.py 2008-08-29 16:02:56 +0000 |
927 | +++ Epsilon/doc/listings/amp/route_setup.py 1970-01-01 00:00:00 +0000 |
928 | @@ -1,49 +0,0 @@ |
929 | -# Copyright (c) 2008 Divmod. See LICENSE for details. |
930 | - |
931 | -import operator |
932 | - |
933 | -from twisted.internet.protocol import ServerFactory |
934 | -from twisted.protocols.amp import Unicode, Command, AMP |
935 | - |
936 | -from epsilon.amprouter import Router |
937 | - |
938 | - |
939 | -class NewRoute(Command): |
940 | - arguments = [('name', Unicode())] |
941 | - response = [('name', Unicode())] |
942 | - |
943 | - |
944 | - |
945 | -class RoutingAMP(AMP): |
946 | - @NewRoute.responder |
947 | - def newRoute(self, name): |
948 | - route = self.boxReceiver.bindRoute(self.factory.routeProtocol()) |
949 | - route.connectTo(name) |
950 | - return {'name': route.localRouteName} |
951 | - |
952 | - |
953 | - |
954 | -class AMPRouteServerFactory(ServerFactory): |
955 | - protocol = RoutingAMP |
956 | - routeProtocol = None |
957 | - |
958 | - def buildProtocol(self, addr): |
959 | - router = Router() |
960 | - proto = self.protocol(router) |
961 | - proto.factory = self |
962 | - default = router.bindRoute(proto, None) |
963 | - default.connectTo(None) |
964 | - return proto |
965 | - |
966 | - |
967 | - |
968 | -def connect(proto, router, receiver): |
969 | - route = router.bindRoute(receiver) |
970 | - d = proto.callRemote(NewRoute, name=route.localRouteName) |
971 | - d.addCallback(operator.getitem, 'name') |
972 | - d.addCallback(lambda name: route.connectTo(name)) |
973 | - def connectionFailed(err): |
974 | - route.unbind() |
975 | - return err |
976 | - d.addErrback(connectionFailed) |
977 | - return d |
978 | |
979 | === removed file 'Epsilon/doc/stylesheet.css' |
980 | --- Epsilon/doc/stylesheet.css 2008-08-25 16:20:43 +0000 |
981 | +++ Epsilon/doc/stylesheet.css 1970-01-01 00:00:00 +0000 |
982 | @@ -1,129 +0,0 @@ |
983 | -body |
984 | -{ |
985 | - margin-left: 2em; |
986 | - margin-right: 2em; |
987 | - border: 0px; |
988 | - padding: 0px; |
989 | - font-family: sans-serif; |
990 | -} |
991 | - |
992 | -pre |
993 | -{ |
994 | - padding: 1em; |
995 | - font-family: Neep Alt, Courier New, Courier; |
996 | - font-size: 12pt; |
997 | - border: thin black solid; |
998 | -} |
999 | - |
1000 | -.python |
1001 | -{ |
1002 | - background-color: #dddddd; |
1003 | -} |
1004 | - |
1005 | -.py-listing, .html-listing, .listing |
1006 | -{ |
1007 | - margin: 1ex; |
1008 | - border: thin solid black; |
1009 | - background-color: #eee; |
1010 | -} |
1011 | - |
1012 | -.py-listing pre, .html-listing pre, .listing pre |
1013 | -{ |
1014 | - margin: 0px; |
1015 | - border: none; |
1016 | - border-bottom: thin solid black; |
1017 | -} |
1018 | - |
1019 | -.py-listing .python |
1020 | -{ |
1021 | - margin-top: 0; |
1022 | - margin-bottom: 0; |
1023 | - border: none; |
1024 | - border-bottom: thin solid black; |
1025 | -} |
1026 | - |
1027 | -.py-src-comment |
1028 | -{ |
1029 | - color: #1111CC |
1030 | -} |
1031 | - |
1032 | -.py-src-keyword |
1033 | -{ |
1034 | - color: #3333CC; |
1035 | - font-weight: bold; |
1036 | -} |
1037 | - |
1038 | -.py-src-parameter |
1039 | -{ |
1040 | - color: #000066; |
1041 | - font-weight: bold; |
1042 | -} |
1043 | - |
1044 | -.py-src-identifier |
1045 | -{ |
1046 | - color: #CC0000 |
1047 | -} |
1048 | - |
1049 | -.py-src-string |
1050 | -{ |
1051 | - color: #115511 |
1052 | -} |
1053 | - |
1054 | -.py-src-endmarker |
1055 | -{ |
1056 | - display: block; /* IE hack; prevents following line from being sucked into the py-listing box. */ |
1057 | -} |
1058 | - |
1059 | -hr |
1060 | -{ |
1061 | - display: inline; |
1062 | -} |
1063 | - |
1064 | -ul |
1065 | -{ |
1066 | - padding: 0px; |
1067 | - margin: 0px; |
1068 | - margin-left: 1em; |
1069 | - padding-left: 1em; |
1070 | - border-left: 1em; |
1071 | -} |
1072 | - |
1073 | -li |
1074 | -{ |
1075 | - padding: 2px; |
1076 | -} |
1077 | - |
1078 | -dt |
1079 | -{ |
1080 | - font-weight: bold; |
1081 | - margin-left: 1ex; |
1082 | -} |
1083 | - |
1084 | -dd |
1085 | -{ |
1086 | - margin-bottom: 1em; |
1087 | -} |
1088 | - |
1089 | -div.note |
1090 | -{ |
1091 | - background-color: #FFFFCC; |
1092 | - margin-top: 1ex; |
1093 | - margin-left: 5%; |
1094 | - margin-right: 5%; |
1095 | - padding-top: 1ex; |
1096 | - padding-left: 5%; |
1097 | - padding-right: 5%; |
1098 | - border: thin black solid; |
1099 | -} |
1100 | - |
1101 | -.caption |
1102 | -{ |
1103 | - text-align: center; |
1104 | - padding-top: 0.5em; |
1105 | - padding-bottom: 0.5em; |
1106 | -} |
1107 | - |
1108 | -.filename |
1109 | -{ |
1110 | - font-style: italic; |
1111 | -} |
1112 | |
1113 | === removed file 'Epsilon/doc/template.tpl' |
1114 | --- Epsilon/doc/template.tpl 2008-08-25 16:20:43 +0000 |
1115 | +++ Epsilon/doc/template.tpl 1970-01-01 00:00:00 +0000 |
1116 | @@ -1,23 +0,0 @@ |
1117 | -<?xml version="1.0"?> |
1118 | -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
1119 | - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
1120 | - |
1121 | -<html xmlns="http://www.w3.org/1999/xhtml" lang="en"> |
1122 | - <head> |
1123 | - <title> |
1124 | - Epsilon: |
1125 | - </title> |
1126 | - <link type="text/css" rel="stylesheet" href="stylesheet.css" /> |
1127 | - </head> |
1128 | - |
1129 | - <body bgcolor="white"> |
1130 | - <h1 class="title"></h1> |
1131 | - <div class="toc"></div> |
1132 | - <div class="body"> |
1133 | - |
1134 | - </div> |
1135 | - |
1136 | - <p><a href="index.html">Index</a></p> |
1137 | - <span class="version">Version: </span> |
1138 | - </body> |
1139 | -</html> |
1140 | |
1141 | === removed directory 'Epsilon/epsilon' |
1142 | === removed file 'Epsilon/epsilon/__init__.py' |
1143 | --- Epsilon/epsilon/__init__.py 2014-01-12 10:53:44 +0000 |
1144 | +++ Epsilon/epsilon/__init__.py 1970-01-01 00:00:00 +0000 |
1145 | @@ -1,8 +0,0 @@ |
1146 | -# -*- test-case-name: epsilon.test -*- |
1147 | -from epsilon._version import __version__ |
1148 | -from twisted.python import versions |
1149 | - |
1150 | -def asTwistedVersion(packageName, versionString): |
1151 | - return versions.Version(packageName, *map(int, versionString.split("."))) |
1152 | - |
1153 | -version = asTwistedVersion("epsilon", __version__) |
1154 | |
1155 | === removed file 'Epsilon/epsilon/_version.py' |
1156 | --- Epsilon/epsilon/_version.py 2014-01-15 10:21:17 +0000 |
1157 | +++ Epsilon/epsilon/_version.py 1970-01-01 00:00:00 +0000 |
1158 | @@ -1,1 +0,0 @@ |
1159 | -__version__ = "0.7.0" |
1160 | |
1161 | === removed file 'Epsilon/epsilon/ampauth.py' |
1162 | --- Epsilon/epsilon/ampauth.py 2011-07-18 12:37:13 +0000 |
1163 | +++ Epsilon/epsilon/ampauth.py 1970-01-01 00:00:00 +0000 |
1164 | @@ -1,312 +0,0 @@ |
1165 | -# -*- test-case-name: epsilon.test.test_ampauth -*- |
1166 | -# Copyright (c) 2008 Divmod. See LICENSE for details. |
1167 | - |
1168 | -""" |
1169 | -This module provides integration between L{AMP<twisted.protocols.amp.AMP>} and |
1170 | -L{cred<twisted.cred>}. |
1171 | -""" |
1172 | - |
1173 | -from hashlib import sha1 |
1174 | - |
1175 | -from zope.interface import implements |
1176 | - |
1177 | -from twisted.python.randbytes import secureRandom |
1178 | -from twisted.cred.error import UnauthorizedLogin |
1179 | -from twisted.cred.credentials import IUsernameHashedPassword, IUsernamePassword |
1180 | -from twisted.cred.checkers import ICredentialsChecker |
1181 | -from twisted.protocols.amp import IBoxReceiver, String, Command, AMP |
1182 | -from twisted.internet.protocol import ServerFactory |
1183 | - |
1184 | -from epsilon.iepsilon import IOneTimePad |
1185 | -from epsilon.structlike import record |
1186 | - |
1187 | -__metaclass__ = type |
1188 | - |
1189 | - |
1190 | -class UnhandledCredentials(Exception): |
1191 | - """ |
1192 | - L{login} was passed a credentials object which did not provide a recognized |
1193 | - credentials interface. |
1194 | - """ |
1195 | - |
1196 | - |
1197 | - |
1198 | -class OTPLogin(Command): |
1199 | - """ |
1200 | - Command to initiate a login attempt where a one-time pad is to be used in |
1201 | - place of username/password credentials. |
1202 | - """ |
1203 | - arguments = [('pad', String())] |
1204 | - |
1205 | - errors = { |
1206 | - # Invalid username or password |
1207 | - UnauthorizedLogin: 'UNAUTHORIZED_LOGIN', |
1208 | - # No IBoxReceiver avatar |
1209 | - NotImplementedError: 'NOT_IMPLEMENTED_ERROR'} |
1210 | - |
1211 | - |
1212 | - |
1213 | -class PasswordLogin(Command): |
1214 | - """ |
1215 | - Command to initiate a username/password-based login attempt. The response |
1216 | - to this command is a challenge which must be responded to based on the |
1217 | - correct password associated with the username given to this command. |
1218 | - """ |
1219 | - arguments = [('username', String())] |
1220 | - response = [('challenge', String())] |
1221 | - |
1222 | - |
1223 | - |
1224 | -def _calcResponse(challenge, nonce, password): |
1225 | - """ |
1226 | - Compute the response to the given challenge. |
1227 | - |
1228 | - @type challenge: C{str} |
1229 | - @param challenge: An arbitrary byte string, probably received in response |
1230 | - to (or generated for) the L{PasswordLogin} command. |
1231 | - |
1232 | - @type nonce: C{str} |
1233 | - @param nonce: An arbitrary byte string, generated by the client to include |
1234 | - in the hash to avoid making the client an oracle. |
1235 | - |
1236 | - @type password: C{str} |
1237 | - @param password: The known correct password for the account being |
1238 | - authenticated. |
1239 | - |
1240 | - @rtype: C{str} |
1241 | - @return: A hash constructed from the three parameters. |
1242 | - """ |
1243 | - return sha1('%s %s %s' % (challenge, nonce, password)).digest() |
1244 | - |
1245 | - |
1246 | - |
1247 | -class PasswordChallengeResponse(Command): |
1248 | - """ |
1249 | - Command to respond to a challenge issued in the response to a |
1250 | - L{PasswordLogin} command and complete a username/password-based login |
1251 | - attempt. |
1252 | - |
1253 | - @param cnonce: A randomly generated string used only in this response. |
1254 | - @param response: The SHA-1 hash of the challenge, cnonce, and password. |
1255 | - """ |
1256 | - arguments = [('cnonce', String()), |
1257 | - ('response', String())] |
1258 | - |
1259 | - errors = { |
1260 | - # Invalid username or password |
1261 | - UnauthorizedLogin: 'UNAUTHORIZED_LOGIN', |
1262 | - # No IBoxReceiver avatar |
1263 | - NotImplementedError: 'NOT_IMPLEMENTED_ERROR'} |
1264 | - |
1265 | - @classmethod |
1266 | - def determineFrom(cls, challenge, password): |
1267 | - """ |
1268 | - Create a nonce and use it, along with the given challenge and password, |
1269 | - to generate the parameters for a response. |
1270 | - |
1271 | - @return: A C{dict} suitable to be used as the keyword arguments when |
1272 | - calling this command. |
1273 | - """ |
1274 | - nonce = secureRandom(16) |
1275 | - response = _calcResponse(challenge, nonce, password) |
1276 | - return dict(cnonce=nonce, response=response) |
1277 | - |
1278 | - |
1279 | - |
1280 | -class _AMPUsernamePassword(record('username challenge nonce response')): |
1281 | - """ |
1282 | - L{IUsernameHashedPassword} implementation used by L{PasswordLogin} and |
1283 | - related commands. |
1284 | - """ |
1285 | - implements(IUsernameHashedPassword) |
1286 | - |
1287 | - def checkPassword(self, password): |
1288 | - """ |
1289 | - Check the given plaintext password against the response in this |
1290 | - credentials object. |
1291 | - |
1292 | - @type password: C{str} |
1293 | - @param password: The known correct password associated with |
1294 | - C{self.username}. |
1295 | - |
1296 | - @return: A C{bool}, C{True} if this credentials object agrees with the |
1297 | - given password, C{False} otherwise. |
1298 | - """ |
1299 | - if isinstance(password, unicode): |
1300 | - password = password.encode('utf-8') |
1301 | - correctResponse = _calcResponse(self.challenge, self.nonce, password) |
1302 | - return correctResponse == self.response |
1303 | - |
1304 | - |
1305 | - |
1306 | -class _AMPOneTimePad(record('padValue')): |
1307 | - """ |
1308 | - L{IOneTimePad} implementation used by L{OTPLogin}. |
1309 | - |
1310 | - @ivar padValue: The value of the one-time pad. |
1311 | - @type padValue: C{str} |
1312 | - """ |
1313 | - implements(IOneTimePad) |
1314 | - |
1315 | - |
1316 | - |
1317 | -class CredReceiver(AMP): |
1318 | - """ |
1319 | - Integration between AMP and L{twisted.cred}. |
1320 | - |
1321 | - This implementation is limited to a single authentication per connection. |
1322 | - A future implementation may use I{routes} to allow multiple authentications |
1323 | - over the same connection. |
1324 | - |
1325 | - @ivar portal: The L{Portal} against which login will be performed. This is |
1326 | - expected to be set by the factory which creates instances of this |
1327 | - class. |
1328 | - |
1329 | - @ivar logout: C{None} or a no-argument callable. This is set to the logout |
1330 | - object returned by L{Portal.login} and is set while an avatar is logged |
1331 | - in. |
1332 | - |
1333 | - @ivar challenge: The C{str} which was sent as a challenge in response to |
1334 | - the L{PasswordLogin} command. If multiple L{PasswordLogin} commands |
1335 | - are sent, this is the challenge sent in response to the most recent of |
1336 | - them. It is not set before L{PasswordLogin} is received. |
1337 | - |
1338 | - @ivar username: The C{str} which was received for the I{username} parameter |
1339 | - of the L{PasswordLogin} command. The lifetime is the same as that of |
1340 | - the I{challenge} attribute. |
1341 | - """ |
1342 | - portal = None |
1343 | - logout = None |
1344 | - |
1345 | - @PasswordLogin.responder |
1346 | - def passwordLogin(self, username): |
1347 | - """ |
1348 | - Generate a new challenge for the given username. |
1349 | - """ |
1350 | - self.challenge = secureRandom(16) |
1351 | - self.username = username |
1352 | - return {'challenge': self.challenge} |
1353 | - |
1354 | - |
1355 | - def _login(self, credentials): |
1356 | - """ |
1357 | - Actually login to our portal with the given credentials. |
1358 | - """ |
1359 | - d = self.portal.login(credentials, None, IBoxReceiver) |
1360 | - def cbLoggedIn((interface, avatar, logout)): |
1361 | - self.logout = logout |
1362 | - self.boxReceiver = avatar |
1363 | - self.boxReceiver.startReceivingBoxes(self.boxSender) |
1364 | - return {} |
1365 | - d.addCallback(cbLoggedIn) |
1366 | - return d |
1367 | - |
1368 | - |
1369 | - @PasswordChallengeResponse.responder |
1370 | - def passwordChallengeResponse(self, cnonce, response): |
1371 | - """ |
1372 | - Verify the response to a challenge. |
1373 | - """ |
1374 | - return self._login(_AMPUsernamePassword( |
1375 | - self.username, self.challenge, cnonce, response)) |
1376 | - |
1377 | - |
1378 | - @OTPLogin.responder |
1379 | - def otpLogin(self, pad): |
1380 | - """ |
1381 | - Verify the given pad. |
1382 | - """ |
1383 | - return self._login(_AMPOneTimePad(pad)) |
1384 | - |
1385 | - |
1386 | - def connectionLost(self, reason): |
1387 | - """ |
1388 | - If a login has happened, perform a logout. |
1389 | - """ |
1390 | - AMP.connectionLost(self, reason) |
1391 | - if self.logout is not None: |
1392 | - self.logout() |
1393 | - self.boxReceiver = self.logout = None |
1394 | - |
1395 | - |
1396 | - |
1397 | -class OneTimePadChecker(record('pads')): |
1398 | - """ |
1399 | - Checker which validates one-time pads. |
1400 | - |
1401 | - @ivar pads: Mapping between valid one-time pads and avatar IDs. |
1402 | - @type pads: C{dict} |
1403 | - """ |
1404 | - implements(ICredentialsChecker) |
1405 | - |
1406 | - credentialInterfaces = (IOneTimePad,) |
1407 | - |
1408 | - # ICredentialsChecker |
1409 | - def requestAvatarId(self, credentials): |
1410 | - if credentials.padValue in self.pads: |
1411 | - return self.pads.pop(credentials.padValue) |
1412 | - raise UnauthorizedLogin('Unknown one-time pad') |
1413 | - |
1414 | - |
1415 | - |
1416 | -class CredAMPServerFactory(ServerFactory): |
1417 | - """ |
1418 | - Server factory useful for creating L{CredReceiver} instances. |
1419 | - |
1420 | - This factory takes care of associating a L{Portal} with L{CredReceiver} |
1421 | - instances it creates. |
1422 | - |
1423 | - @ivar portal: The portal which will be used by L{CredReceiver} instances |
1424 | - created by this factory. |
1425 | - """ |
1426 | - protocol = CredReceiver |
1427 | - |
1428 | - def __init__(self, portal): |
1429 | - self.portal = portal |
1430 | - |
1431 | - |
1432 | - def buildProtocol(self, addr): |
1433 | - proto = ServerFactory.buildProtocol(self, addr) |
1434 | - proto.portal = self.portal |
1435 | - return proto |
1436 | - |
1437 | - |
1438 | - |
1439 | -def login(client, credentials): |
1440 | - """ |
1441 | - Authenticate using the given L{AMP} instance. The protocol must be |
1442 | - connected to a server with responders for L{PasswordLogin} and |
1443 | - L{PasswordChallengeResponse}. |
1444 | - |
1445 | - @param client: A connected L{AMP} instance which will be used to issue |
1446 | - authentication commands. |
1447 | - |
1448 | - @param credentials: An object providing L{IUsernamePassword} which will |
1449 | - be used to authenticate this connection to the server. |
1450 | - |
1451 | - @return: A L{Deferred} which fires when authentication has succeeded or |
1452 | - which fails with L{UnauthorizedLogin} if the server rejects the |
1453 | - authentication attempt. |
1454 | - """ |
1455 | - if not IUsernamePassword.providedBy(credentials): |
1456 | - raise UnhandledCredentials() |
1457 | - d = client.callRemote( |
1458 | - PasswordLogin, username=credentials.username) |
1459 | - def cbChallenge(response): |
1460 | - args = PasswordChallengeResponse.determineFrom( |
1461 | - response['challenge'], credentials.password) |
1462 | - d = client.callRemote(PasswordChallengeResponse, **args) |
1463 | - return d.addCallback(lambda ignored: client) |
1464 | - d.addCallback(cbChallenge) |
1465 | - return d |
1466 | - |
1467 | - |
1468 | - |
1469 | -__all__ = [ |
1470 | - 'UnhandledCredentials', |
1471 | - |
1472 | - 'OTPLogin', 'OneTimePadChecker', |
1473 | - |
1474 | - 'PasswordLogin', 'PasswordChallengeResponse', 'CredReceiver', |
1475 | - |
1476 | - 'CredAMPServerFactory', 'login'] |
1477 | |
1478 | === removed file 'Epsilon/epsilon/amprouter.py' |
1479 | --- Epsilon/epsilon/amprouter.py 2008-08-29 16:02:56 +0000 |
1480 | +++ Epsilon/epsilon/amprouter.py 1970-01-01 00:00:00 +0000 |
1481 | @@ -1,205 +0,0 @@ |
1482 | -# -*- test-case-name: epsilon.test.test_amprouter -*- |
1483 | -# Copyright (c) 2008 Divmod. See LICENSE for details. |
1484 | - |
1485 | -""" |
1486 | -This module provides an implementation of I{Routes}, a system for multiplexing |
1487 | -multiple L{IBoxReceiver}/I{IBoxSender} pairs over a single L{AMP} connection. |
1488 | -""" |
1489 | - |
1490 | -from itertools import count |
1491 | - |
1492 | -from zope.interface import implements |
1493 | - |
1494 | -from twisted.protocols.amp import IBoxReceiver, IBoxSender |
1495 | - |
1496 | -from epsilon.structlike import record |
1497 | - |
1498 | -__metaclass__ = type |
1499 | - |
1500 | -_ROUTE = '_route' |
1501 | -_unspecified = object() |
1502 | - |
1503 | - |
1504 | -class RouteNotConnected(Exception): |
1505 | - """ |
1506 | - An attempt was made to send AMP boxes through a L{Route} which is not yet |
1507 | - connected to anything. |
1508 | - """ |
1509 | - |
1510 | - |
1511 | - |
1512 | -class Route(record('router receiver localRouteName remoteRouteName', |
1513 | - remoteRouteName=_unspecified)): |
1514 | - """ |
1515 | - Wrap up a route name and a box sender to transparently add the route name |
1516 | - to boxes sent through this box sender. |
1517 | - |
1518 | - @ivar router: The L{Router} which created this route. This will be used |
1519 | - for route tear down and for its L{IBoxSender}, to send boxes. |
1520 | - |
1521 | - @ivar receiver: The receiver which will be started with this object as its |
1522 | - sender. |
1523 | - |
1524 | - @type localRouteName: C{unicode} |
1525 | - @ivar localRouteName: The name of this route as known by the other side of |
1526 | - the AMP connection. AMP boxes with this route are expected to be |
1527 | - routed to this object. |
1528 | - |
1529 | - @type remoteRouteName: C{unicode} or L{NoneType} |
1530 | - @ivar remoteRouteName: The name of the route which will be added to all |
1531 | - boxes sent to this sender. If C{None}, no route will be added. |
1532 | - """ |
1533 | - implements(IBoxSender) |
1534 | - |
1535 | - def connectTo(self, remoteRouteName): |
1536 | - """ |
1537 | - Set the name of the route which will be added to outgoing boxes. |
1538 | - """ |
1539 | - self.remoteRouteName = remoteRouteName |
1540 | - # This route must not be started before its router is started. If |
1541 | - # sender is None, then the router is not started. When the router is |
1542 | - # started, it will start this route. |
1543 | - if self.router._sender is not None: |
1544 | - self.start() |
1545 | - |
1546 | - |
1547 | - def unbind(self): |
1548 | - """ |
1549 | - Remove the association between this route and its router. |
1550 | - """ |
1551 | - del self.router._routes[self.localRouteName] |
1552 | - |
1553 | - |
1554 | - def start(self): |
1555 | - """ |
1556 | - Associate this object with a receiver as its L{IBoxSender}. |
1557 | - """ |
1558 | - self.receiver.startReceivingBoxes(self) |
1559 | - |
1560 | - |
1561 | - def stop(self, reason): |
1562 | - """ |
1563 | - Shut down the underlying receiver. |
1564 | - """ |
1565 | - self.receiver.stopReceivingBoxes(reason) |
1566 | - |
1567 | - |
1568 | - def sendBox(self, box): |
1569 | - """ |
1570 | - Add the route and send the box. |
1571 | - """ |
1572 | - if self.remoteRouteName is _unspecified: |
1573 | - raise RouteNotConnected() |
1574 | - if self.remoteRouteName is not None: |
1575 | - box[_ROUTE] = self.remoteRouteName.encode('ascii') |
1576 | - self.router._sender.sendBox(box) |
1577 | - |
1578 | - |
1579 | - def unhandledError(self, failure): |
1580 | - """ |
1581 | - Pass failures through to the wrapped L{IBoxSender} without |
1582 | - modification. |
1583 | - """ |
1584 | - self.router._sender.unhandledError(failure) |
1585 | - |
1586 | - |
1587 | - |
1588 | -class Router: |
1589 | - """ |
1590 | - An L{IBoxReceiver} implementation which demultiplexes boxes from an AMP |
1591 | - connection being used with zero, one, or more routes. |
1592 | - |
1593 | - @ivar _sender: An L{IBoxSender} provider which is used to allow |
1594 | - L{IBoxReceiver}s added to this router to send boxes. |
1595 | - |
1596 | - @ivar _unstarted: A C{dict} similar to C{_routes} set before |
1597 | - C{startReceivingBoxes} is called and containing all routes which have |
1598 | - been added but not yet started. These are started and moved to the |
1599 | - C{_routes} dict when the router is started. |
1600 | - |
1601 | - @ivar _routes: A C{dict} mapping local route identifiers to |
1602 | - L{IBoxReceivers} associated with them. This is only initialized after |
1603 | - C{startReceivingBoxes} is called. |
1604 | - |
1605 | - @ivar _routeCounter: A L{itertools.count} instance used to generate unique |
1606 | - identifiers for routes in this router. |
1607 | - """ |
1608 | - implements(IBoxReceiver) |
1609 | - |
1610 | - _routes = None |
1611 | - _sender = None |
1612 | - |
1613 | - def __init__(self): |
1614 | - self._routeCounter = count() |
1615 | - self._unstarted = {} |
1616 | - |
1617 | - |
1618 | - def createRouteIdentifier(self): |
1619 | - """ |
1620 | - Return a route identifier which is not yet associated with a route on |
1621 | - this dispatcher. |
1622 | - |
1623 | - @rtype: C{unicode} |
1624 | - """ |
1625 | - return unicode(self._routeCounter.next()) |
1626 | - |
1627 | - |
1628 | - def bindRoute(self, receiver, routeName=_unspecified): |
1629 | - """ |
1630 | - Create a new route to associate the given route name with the given |
1631 | - receiver. |
1632 | - |
1633 | - @type routeName: C{unicode} or L{NoneType} |
1634 | - @param routeName: The identifier for the newly created route. If |
1635 | - C{None}, boxes with no route in them will be delivered to this |
1636 | - receiver. |
1637 | - |
1638 | - @rtype: L{Route} |
1639 | - """ |
1640 | - if routeName is _unspecified: |
1641 | - routeName = self.createRouteIdentifier() |
1642 | - # self._sender may yet be None; if so, this route goes into _unstarted |
1643 | - # and will have its sender set correctly in startReceivingBoxes below. |
1644 | - route = Route(self, receiver, routeName) |
1645 | - mapping = self._routes |
1646 | - if mapping is None: |
1647 | - mapping = self._unstarted |
1648 | - mapping[routeName] = route |
1649 | - return route |
1650 | - |
1651 | - |
1652 | - def startReceivingBoxes(self, sender): |
1653 | - """ |
1654 | - Initialize route tracking objects. |
1655 | - """ |
1656 | - self._sender = sender |
1657 | - for routeName, route in self._unstarted.iteritems(): |
1658 | - # Any route which has been bound but which does not yet have a |
1659 | - # remote route name should not yet be started. These will be |
1660 | - # started in Route.connectTo. |
1661 | - if route.remoteRouteName is not _unspecified: |
1662 | - route.start() |
1663 | - self._routes = self._unstarted |
1664 | - self._unstarted = None |
1665 | - |
1666 | - |
1667 | - def ampBoxReceived(self, box): |
1668 | - """ |
1669 | - Dispatch the given box to the L{IBoxReceiver} associated with the route |
1670 | - indicated by the box, or handle it directly if there is no route. |
1671 | - """ |
1672 | - route = box.pop(_ROUTE, None) |
1673 | - self._routes[route].receiver.ampBoxReceived(box) |
1674 | - |
1675 | - |
1676 | - def stopReceivingBoxes(self, reason): |
1677 | - """ |
1678 | - Stop all the L{IBoxReceiver}s which have been added to this router. |
1679 | - """ |
1680 | - for routeName, route in self._routes.iteritems(): |
1681 | - route.stop(reason) |
1682 | - self._routes = None |
1683 | - |
1684 | - |
1685 | - |
1686 | -__all__ = ['Router', 'Route'] |
1687 | |
1688 | === removed file 'Epsilon/epsilon/asplode.py' |
1689 | --- Epsilon/epsilon/asplode.py 2005-12-02 20:28:41 +0000 |
1690 | +++ Epsilon/epsilon/asplode.py 1970-01-01 00:00:00 +0000 |
1691 | @@ -1,34 +0,0 @@ |
1692 | - |
1693 | -import sys, os |
1694 | -from datetime import date |
1695 | - |
1696 | -def status(x): |
1697 | - sys.stderr.write(x+'\n') |
1698 | - sys.stderr.flush() |
1699 | - |
1700 | -def splode(linerator, proj, capproj): |
1701 | - current = None |
1702 | - for line in linerator: |
1703 | - line = line.replace('_project_', proj) |
1704 | - line = line.replace('_Project_', capproj) |
1705 | - line = line.replace('_date_', str(date.today())) |
1706 | - ls = line.split("###file:") |
1707 | - if len(ls) > 1: |
1708 | - fname = ls[1].strip() |
1709 | - if current is not None: |
1710 | - current.close() |
1711 | - try: |
1712 | - os.makedirs(os.path.dirname(fname)) |
1713 | - except: |
1714 | - pass |
1715 | - current = file(fname, 'wb') |
1716 | - status('Created: ' + fname) |
1717 | - else: |
1718 | - current.write(line) |
1719 | - current.close() |
1720 | - |
1721 | -def main(argv): |
1722 | - splode(sys.stdin.readlines(), 'zoop', 'Zoop') |
1723 | - |
1724 | -if __name__ == '__main__': |
1725 | - main(sys.argv) |
1726 | |
1727 | === removed file 'Epsilon/epsilon/caseless.py' |
1728 | --- Epsilon/epsilon/caseless.py 2008-04-16 12:34:02 +0000 |
1729 | +++ Epsilon/epsilon/caseless.py 1970-01-01 00:00:00 +0000 |
1730 | @@ -1,135 +0,0 @@ |
1731 | -# -*- test-case-name: epsilon.test.test_caseless -*- |
1732 | -""" |
1733 | -Helpers for case-insensitive string handling. |
1734 | -""" |
1735 | - |
1736 | -class Caseless(object): |
1737 | - """ |
1738 | - Case-insensitive string wrapper type. |
1739 | - |
1740 | - This wrapper is intended for use with strings that have case-insensitive |
1741 | - semantics, such as HTTP/MIME header values. It implements comparison-based |
1742 | - operations case-insensitively, avoiding the need to manually call C{lower} |
1743 | - where appropriate, or keep track of which strings are case-insensitive |
1744 | - throughout various function calls. |
1745 | - |
1746 | - Example usage: |
1747 | - |
1748 | - >>> Caseless('Spam') == Caseless('spam') |
1749 | - True |
1750 | - >>> 'spam' in Caseless('Eggs and Spam') |
1751 | - True |
1752 | - |
1753 | - >>> sorted(['FOO', 'bar'], key=Caseless) |
1754 | - ['bar', 'FOO'] |
1755 | - |
1756 | - >>> d = {Caseless('Content-type'): Caseless('Text/Plain')} |
1757 | - >>> d[Caseless('Content-Type')].startswith('text/') |
1758 | - True |
1759 | - |
1760 | - Note: String methods that return modified strings (such as |
1761 | - C{decode}/C{encode}, C{join}, C{partition}, C{replace}, C{strip}/C{split}) |
1762 | - don't have an unambiguous return types with regards to case sensitivity, so |
1763 | - they are not implemented by L{Caseless}. They should be accessed on the |
1764 | - underlying cased string instead. (Excepted are methods like |
1765 | - C{lower}/C{upper}, whose return case is unambiguous.) |
1766 | - |
1767 | - @ivar cased: the wrapped string-like object |
1768 | - """ |
1769 | - |
1770 | - def __init__(self, cased): |
1771 | - if isinstance(cased, Caseless): |
1772 | - cased = cased.cased |
1773 | - self.cased = cased |
1774 | - |
1775 | - |
1776 | - def __repr__(self): |
1777 | - return '%s(%r)' % (type(self).__name__, self.cased) |
1778 | - |
1779 | - |
1780 | - # Methods delegated to cased |
1781 | - |
1782 | - def __str__(self): |
1783 | - return str(self.cased) |
1784 | - |
1785 | - |
1786 | - def __unicode__(self): |
1787 | - return unicode(self.cased) |
1788 | - |
1789 | - |
1790 | - def __len__(self): |
1791 | - return len(self.cased) |
1792 | - |
1793 | - |
1794 | - def __getitem__(self, key): |
1795 | - return self.cased[key] |
1796 | - |
1797 | - |
1798 | - def __iter__(self): |
1799 | - return iter(self.cased) |
1800 | - |
1801 | - |
1802 | - def lower(self): |
1803 | - return self.cased.lower() |
1804 | - |
1805 | - |
1806 | - def upper(self): |
1807 | - return self.cased.upper() |
1808 | - |
1809 | - |
1810 | - def title(self): |
1811 | - return self.cased.title() |
1812 | - |
1813 | - |
1814 | - def swapcase(self): |
1815 | - return self.cased.swapcase() |
1816 | - |
1817 | - |
1818 | - # Methods delegated to lower() |
1819 | - |
1820 | - def __cmp__(self, other): |
1821 | - return cmp(self.lower(), other.lower()) |
1822 | - |
1823 | - |
1824 | - def __hash__(self): |
1825 | - return hash(self.lower()) |
1826 | - |
1827 | - |
1828 | - def __contains__(self, substring): |
1829 | - return substring.lower() in self.lower() |
1830 | - |
1831 | - |
1832 | - def startswith(self, prefix, *rest): |
1833 | - if isinstance(prefix, tuple): |
1834 | - lprefix = tuple(s.lower() for s in prefix) |
1835 | - else: |
1836 | - lprefix = prefix.lower() |
1837 | - return self.lower().startswith(lprefix, *rest) |
1838 | - |
1839 | - |
1840 | - def endswith(self, suffix, *rest): |
1841 | - if isinstance(suffix, tuple): |
1842 | - lsuffix = tuple(s.lower() for s in suffix) |
1843 | - else: |
1844 | - lsuffix = suffix.lower() |
1845 | - return self.lower().endswith(lsuffix, *rest) |
1846 | - |
1847 | - |
1848 | - def count(self, substring, *rest): |
1849 | - return self.lower().count(substring.lower(), *rest) |
1850 | - |
1851 | - |
1852 | - def find(self, substring, *rest): |
1853 | - return self.lower().find(substring.lower(), *rest) |
1854 | - |
1855 | - |
1856 | - def index(self, substring, *rest): |
1857 | - return self.lower().index(substring.lower(), *rest) |
1858 | - |
1859 | - |
1860 | - def rfind(self, substring, *rest): |
1861 | - return self.lower().rfind(substring.lower(), *rest) |
1862 | - |
1863 | - |
1864 | - def rindex(self, substring, *rest): |
1865 | - return self.lower().rindex(substring.lower(), *rest) |
1866 | |
1867 | === removed file 'Epsilon/epsilon/cooperator.py' |
1868 | --- Epsilon/epsilon/cooperator.py 2009-05-22 13:21:37 +0000 |
1869 | +++ Epsilon/epsilon/cooperator.py 1970-01-01 00:00:00 +0000 |
1870 | @@ -1,32 +0,0 @@ |
1871 | - |
1872 | -from twisted.application.service import Service |
1873 | -from twisted.internet.task import SchedulerStopped, Cooperator, coiterate |
1874 | - |
1875 | -def iterateInReactor(i, delay=None): |
1876 | - """ |
1877 | - Cooperatively iterate over the given iterator. |
1878 | - |
1879 | - @see: L{twisted.internet.task.coiterate}. |
1880 | - """ |
1881 | - return coiterate(i) |
1882 | - |
1883 | - |
1884 | -class SchedulingService(Service): |
1885 | - """ |
1886 | - Simple L{IService} implementation. |
1887 | - """ |
1888 | - def __init__(self): |
1889 | - self.coop = Cooperator(started=False) |
1890 | - |
1891 | - def addIterator(self, iterator): |
1892 | - return self.coop.coiterate(iterator) |
1893 | - |
1894 | - def startService(self): |
1895 | - self.coop.start() |
1896 | - |
1897 | - def stopService(self): |
1898 | - self.coop.stop() |
1899 | - |
1900 | -__all__ = [ |
1901 | - 'SchedulerStopped', 'Cooperator', |
1902 | - 'SchedulingService', 'iterateInReactor'] |
1903 | |
1904 | === removed file 'Epsilon/epsilon/descriptor.py' |
1905 | --- Epsilon/epsilon/descriptor.py 2008-02-08 14:07:46 +0000 |
1906 | +++ Epsilon/epsilon/descriptor.py 1970-01-01 00:00:00 +0000 |
1907 | @@ -1,147 +0,0 @@ |
1908 | -# -*- test-case-name: epsilon.test.test_descriptor -*- |
1909 | - |
1910 | -""" |
1911 | -Provides an 'attribute' class for one-use descriptors. |
1912 | -""" |
1913 | - |
1914 | -attribute = None |
1915 | - |
1916 | -class _MetaAttribute(type): |
1917 | - def __new__(meta, name, bases, dict): |
1918 | - # for reals, yo. |
1919 | - for kw in ['get', 'set', 'delete']: |
1920 | - if kw in dict: |
1921 | - dict[kw] = staticmethod(dict[kw]) |
1922 | - secretClass = type.__new__(meta, name, bases, dict) |
1923 | - if attribute is None: |
1924 | - return secretClass |
1925 | - return secretClass() |
1926 | - |
1927 | -class attribute(object): |
1928 | - """ |
1929 | - Convenience class for providing one-shot descriptors, similar to |
1930 | - 'property'. For example: |
1931 | - |
1932 | - >>> from epsilon.descriptor import attribute |
1933 | - >>> class Dynamo(object): |
1934 | - ... class dynamic(attribute): |
1935 | - ... def get(self): |
1936 | - ... self.dynCount += 1 |
1937 | - ... return self.dynCount |
1938 | - ... def set(self, value): |
1939 | - ... self.dynCount += value |
1940 | - ... dynCount = 0 |
1941 | - ... |
1942 | - >>> d = Dynamo() |
1943 | - >>> d.dynamic |
1944 | - 1 |
1945 | - >>> d.dynamic |
1946 | - 2 |
1947 | - >>> d.dynamic = 6 |
1948 | - >>> d.dynamic |
1949 | - 9 |
1950 | - >>> d.dynamic |
1951 | - 10 |
1952 | - >>> del d.dynamic |
1953 | - Traceback (most recent call last): |
1954 | - ... |
1955 | - AttributeError: attribute cannot be removed |
1956 | - """ |
1957 | - |
1958 | - __metaclass__ = _MetaAttribute |
1959 | - |
1960 | - def __get__(self, oself, type): |
1961 | - """ |
1962 | - Private implementation of descriptor interface. |
1963 | - """ |
1964 | - if oself is None: |
1965 | - return self |
1966 | - return self.get(oself) |
1967 | - |
1968 | - def __set__(self, oself, value): |
1969 | - """ |
1970 | - Private implementation of descriptor interface. |
1971 | - """ |
1972 | - return self.set(oself, value) |
1973 | - |
1974 | - def __delete__(self, oself): |
1975 | - """ |
1976 | - Private implementation of descriptor interface. |
1977 | - """ |
1978 | - return self.delete(oself) |
1979 | - |
1980 | - def set(self, value): |
1981 | - """ |
1982 | - Implement this method to provide attribute setting. Default behavior |
1983 | - is that attributes are not settable. |
1984 | - """ |
1985 | - raise AttributeError('read only attribute') |
1986 | - |
1987 | - def get(self): |
1988 | - """ |
1989 | - Implement this method to provide attribute retrieval. Default behavior |
1990 | - is that unset attributes do not have any value. |
1991 | - """ |
1992 | - raise AttributeError('attribute has no value') |
1993 | - |
1994 | - def delete(self): |
1995 | - """ |
1996 | - Implement this method to provide attribute deletion. Default behavior |
1997 | - is that attributes cannot be deleted. |
1998 | - """ |
1999 | - raise AttributeError('attribute cannot be removed') |
2000 | - |
2001 | - |
2002 | - |
2003 | -def requiredAttribute(requiredAttributeName): |
2004 | - """ |
2005 | - Utility for defining attributes on base classes/mixins which require their |
2006 | - values to be supplied by their derived classes. C{None} is a common, but |
2007 | - almost never suitable default value for these kinds of attributes, as it |
2008 | - may cause operations in the derived class to fail silently in peculiar |
2009 | - ways. If a C{requiredAttribute} is accessed before having its value |
2010 | - changed, a C{AttributeError} will be raised with a helpful error message. |
2011 | - |
2012 | - @param requiredAttributeName: The name of the required attribute. |
2013 | - @type requiredAttributeName: C{str} |
2014 | - |
2015 | - Example: |
2016 | - >>> from epsilon.descriptor import requiredAttribute |
2017 | - ... |
2018 | - >>> class FooTestMixin: |
2019 | - ... expectedResult = requiredAttribute('expectedResult') |
2020 | - ... |
2021 | - >>> class BrokenFooTestCase(TestCase, FooTestMixin): |
2022 | - ... pass |
2023 | - ... |
2024 | - >>> brokenFoo = BrokenFooTestCase() |
2025 | - >>> print brokenFoo.expectedResult |
2026 | - Traceback (most recent call last): |
2027 | - ... |
2028 | - AttributeError: Required attribute 'expectedResult' has not been |
2029 | - changed from its default value on '<BrokenFooTestCase |
2030 | - instance>'. |
2031 | - ... |
2032 | - >>> class WorkingFooTestCase(TestCase, FooTestMixin): |
2033 | - ... expectedResult = 7 |
2034 | - ... |
2035 | - >>> workingFoo = WorkingFooTestCase() |
2036 | - >>> print workingFoo.expectedResult |
2037 | - ... 7 |
2038 | - >>> |
2039 | - """ |
2040 | - class RequiredAttribute(attribute): |
2041 | - def get(self): |
2042 | - if requiredAttributeName not in self.__dict__: |
2043 | - raise AttributeError( |
2044 | - ('Required attribute %r has not been changed' |
2045 | - ' from its default value on %r' % ( |
2046 | - requiredAttributeName, self))) |
2047 | - return self.__dict__[requiredAttributeName] |
2048 | - def set(self, value): |
2049 | - self.__dict__[requiredAttributeName] = value |
2050 | - return RequiredAttribute |
2051 | - |
2052 | - |
2053 | - |
2054 | -__all__ = ['attribute', 'requiredAttribute'] |
2055 | |
2056 | === removed file 'Epsilon/epsilon/expose.py' |
2057 | --- Epsilon/epsilon/expose.py 2008-08-13 02:55:58 +0000 |
2058 | +++ Epsilon/epsilon/expose.py 1970-01-01 00:00:00 +0000 |
2059 | @@ -1,141 +0,0 @@ |
2060 | -# Copright 2008 Divmod, Inc. See LICENSE file for details. |
2061 | -# -*- test-case-name: epsilon.test.test_expose -*- |
2062 | - |
2063 | -""" |
2064 | -This module provides L{Exposer}, a utility for creating decorators that expose |
2065 | -methods on types for a particular purpose. |
2066 | - |
2067 | -The typical usage of this module is for an infrastructure layer (usually one |
2068 | -that allows methods to be invoked from the network, directly or indirectly) to |
2069 | -provide an explicit API for exposing those methods securely. |
2070 | - |
2071 | -For example, a sketch of a finger protocol implementation which could use this |
2072 | -to expose the results of certain methods as finger results:: |
2073 | - |
2074 | - # tx_finger.py |
2075 | - fingermethod = Exposer("This object exposes finger methods.") |
2076 | - ... |
2077 | - class FingerProtocol(Protocol): |
2078 | - def __init__(self, fingerModel): |
2079 | - self.model = fingerModel |
2080 | - ... |
2081 | - def fingerQuestionReceived(self, whichUser): |
2082 | - try: |
2083 | - method = fingermethod.get(self.model, whichUser) |
2084 | - except MethodNotExposed: |
2085 | - method = lambda : "Unknown user" |
2086 | - return method() |
2087 | - |
2088 | - # myfingerserver.py |
2089 | - from tx_finger import fingermethod |
2090 | - ... |
2091 | - class MyFingerModel(object): |
2092 | - @fingermethod.expose("bob") |
2093 | - def someMethod(self): |
2094 | - return "Bob is great." |
2095 | - |
2096 | -Assuming lots of protocol code to hook everything together, this would then |
2097 | -allow you to use MyFingerModel and 'finger bob' to get the message 'Bob is |
2098 | -great.' |
2099 | -""" |
2100 | - |
2101 | -import inspect |
2102 | - |
2103 | -from types import FunctionType |
2104 | - |
2105 | - |
2106 | -class MethodNotExposed(Exception): |
2107 | - """ |
2108 | - The requested method was not exposed for the purpose requested. More |
2109 | - specifically, L{Exposer.get} was used to retrieve a key from an object |
2110 | - which does not expose that key with that exposer. |
2111 | - """ |
2112 | - |
2113 | - |
2114 | -class NameRequired(Exception): |
2115 | - """ |
2116 | - L{Exposer.expose} was used to decorate a non-function object without having |
2117 | - a key explicitly specified. |
2118 | - """ |
2119 | - |
2120 | - |
2121 | - |
2122 | -class Exposer(object): |
2123 | - """ |
2124 | - This is an object that can expose and retrieve methods on classes. |
2125 | - |
2126 | - @ivar _exposed: a dict mapping exposed keys to exposed function objects. |
2127 | - """ |
2128 | - |
2129 | - def __init__(self, doc): |
2130 | - """ |
2131 | - Create an exposer. |
2132 | - """ |
2133 | - self.__doc__ = doc |
2134 | - self._exposed = {} |
2135 | - |
2136 | - |
2137 | - def expose(self, key=None): |
2138 | - """ |
2139 | - Expose the decorated method for this L{Exposer} with the given key. A |
2140 | - method which is exposed will be able to be retrieved by this |
2141 | - L{Exposer}'s C{get} method with that key. If no key is provided, the |
2142 | - key is the method name of the exposed method. |
2143 | - |
2144 | - Use like so:: |
2145 | - |
2146 | - class MyClass: |
2147 | - @someExposer.expose() |
2148 | - def foo(): ... |
2149 | - |
2150 | - or:: |
2151 | - |
2152 | - class MyClass: |
2153 | - @someExposer.expose('foo') |
2154 | - def unrelatedMethodName(): ... |
2155 | - |
2156 | - @param key: a hashable object, used by L{Exposer.get} to look up the |
2157 | - decorated method later. If None, the key is the exposed method's name. |
2158 | - |
2159 | - @return: a 1-argument callable which records its input as exposed, then |
2160 | - returns it. |
2161 | - """ |
2162 | - def decorator(function): |
2163 | - rkey = key |
2164 | - if rkey is None: |
2165 | - if isinstance(function, FunctionType): |
2166 | - rkey = function.__name__ |
2167 | - else: |
2168 | - raise NameRequired() |
2169 | - if rkey not in self._exposed: |
2170 | - self._exposed[rkey] = [] |
2171 | - self._exposed[rkey].append(function) |
2172 | - return function |
2173 | - return decorator |
2174 | - |
2175 | - |
2176 | - def get(self, obj, key): |
2177 | - """ |
2178 | - Retrieve 'key' from an instance of a class which previously exposed it. |
2179 | - |
2180 | - @param key: a hashable object, previously passed to L{Exposer.expose}. |
2181 | - |
2182 | - @return: the object which was exposed with the given name on obj's key. |
2183 | - |
2184 | - @raise MethodNotExposed: when the key in question was not exposed with |
2185 | - this exposer. |
2186 | - """ |
2187 | - if key not in self._exposed: |
2188 | - raise MethodNotExposed() |
2189 | - rightFuncs = self._exposed[key] |
2190 | - T = obj.__class__ |
2191 | - seen = {} |
2192 | - for subT in inspect.getmro(T): |
2193 | - for name, value in subT.__dict__.items(): |
2194 | - for rightFunc in rightFuncs: |
2195 | - if value is rightFunc: |
2196 | - if name in seen: |
2197 | - raise MethodNotExposed() |
2198 | - return value.__get__(obj, T) |
2199 | - seen[name] = True |
2200 | - raise MethodNotExposed() |
2201 | |
2202 | === removed file 'Epsilon/epsilon/extime.py' |
2203 | --- Epsilon/epsilon/extime.py 2009-11-16 19:09:42 +0000 |
2204 | +++ Epsilon/epsilon/extime.py 1970-01-01 00:00:00 +0000 |
2205 | @@ -1,980 +0,0 @@ |
2206 | -# -*- test-case-name: epsilon.test.test_extime -*- |
2207 | -""" |
2208 | -Extended date/time formatting and miscellaneous functionality. |
2209 | - |
2210 | -See the class 'Time' for details. |
2211 | -""" |
2212 | - |
2213 | -import datetime |
2214 | -import re |
2215 | - |
2216 | -from email.Utils import parsedate_tz |
2217 | - |
2218 | -_EPOCH = datetime.datetime.utcfromtimestamp(0) |
2219 | - |
2220 | - |
2221 | -class InvalidPrecision(Exception): |
2222 | - """ |
2223 | - L{Time.asHumanly} was passed an invalid precision value. |
2224 | - """ |
2225 | - |
2226 | - |
2227 | - |
2228 | -def sanitizeStructTime(struct): |
2229 | - """ |
2230 | - Convert struct_time tuples with possibly invalid values to valid |
2231 | - ones by substituting the closest valid value. |
2232 | - """ |
2233 | - maxValues = (9999, 12, 31, 23, 59, 59) |
2234 | - minValues = (1, 1, 1, 0, 0, 0) |
2235 | - newstruct = [] |
2236 | - for value, maxValue, minValue in zip(struct[:6], maxValues, minValues): |
2237 | - newstruct.append(max(minValue, min(value, maxValue))) |
2238 | - return tuple(newstruct) + struct[6:] |
2239 | - |
2240 | -def _timedeltaToSignHrMin(offset): |
2241 | - """ |
2242 | - Return a (sign, hour, minute) triple for the offset described by timedelta. |
2243 | - |
2244 | - sign is a string, either "+" or "-". In the case of 0 offset, sign is "+". |
2245 | - """ |
2246 | - minutes = round((offset.days * 3600000000 * 24 |
2247 | - + offset.seconds * 1000000 |
2248 | - + offset.microseconds) |
2249 | - / 60000000.0) |
2250 | - if minutes < 0: |
2251 | - sign = '-' |
2252 | - minutes = -minutes |
2253 | - else: |
2254 | - sign = '+' |
2255 | - return (sign, minutes // 60, minutes % 60) |
2256 | - |
2257 | -def _timedeltaToSeconds(offset): |
2258 | - """ |
2259 | - Convert a datetime.timedelta instance to simply a number of seconds. |
2260 | - |
2261 | - For example, you can specify purely second intervals with timedelta's |
2262 | - constructor: |
2263 | - |
2264 | - >>> td = datetime.timedelta(seconds=99999999) |
2265 | - |
2266 | - but then you can't get them out again: |
2267 | - |
2268 | - >>> td.seconds |
2269 | - 35199 |
2270 | - |
2271 | - This allows you to: |
2272 | - |
2273 | - >>> import epsilon.extime |
2274 | - >>> epsilon.extime._timedeltaToSeconds(td) |
2275 | - 99999999.0 |
2276 | - |
2277 | - @param offset: a L{datetime.timedelta} representing an interval that we |
2278 | - wish to know the total number of seconds for. |
2279 | - |
2280 | - @return: a number of seconds |
2281 | - @rtype: float |
2282 | - """ |
2283 | - return ((offset.days * 60*60*24) + |
2284 | - (offset.seconds) + |
2285 | - (offset.microseconds * 1e-6)) |
2286 | - |
2287 | -class FixedOffset(datetime.tzinfo): |
2288 | - _zeroOffset = datetime.timedelta() |
2289 | - |
2290 | - def __init__(self, hours, minutes): |
2291 | - self.offset = datetime.timedelta(minutes = hours * 60 + minutes) |
2292 | - |
2293 | - def utcoffset(self, dt): |
2294 | - return self.offset |
2295 | - |
2296 | - def tzname(self, dt): |
2297 | - return _timedeltaToSignHrMin(self.offset) |
2298 | - |
2299 | - def dst(self, tz): |
2300 | - return self._zeroOffset |
2301 | - |
2302 | - def __repr__(self): |
2303 | - return '<%s.%s object at 0x%x offset %r>' % ( |
2304 | - self.__module__, type(self).__name__, id(self), self.offset) |
2305 | - |
2306 | - |
2307 | - |
2308 | -class Time(object): |
2309 | - """An object representing a well defined instant in time. |
2310 | - |
2311 | - A Time object unambiguously addresses some time, independent of timezones, |
2312 | - contorted base-60 counting schemes, leap seconds, and the effects of |
2313 | - general relativity. It provides methods for returning a representation of |
2314 | - this time in various ways that a human or a programmer might find more |
2315 | - useful in various applications. |
2316 | - |
2317 | - Every Time instance has an attribute 'resolution'. This can be ignored, or |
2318 | - the instance can be considered to address a span of time. This resolution |
2319 | - is determined by the value used to initalize the instance, or the |
2320 | - resolution of the internal representation, whichever is greater. It is |
2321 | - mostly useful when using input formats that allow the specification of |
2322 | - whole days or weeks. For example, ISO 8601 allows one to state a time as, |
2323 | - "2005-W03", meaning "the third week of 2005". In this case the resolution |
2324 | - is set to one week. Other formats are considered to express only an instant |
2325 | - in time, such as a POSIX timestamp, because the resolution of the time is |
2326 | - limited only by the hardware's representation of a real number. |
2327 | - |
2328 | - Timezones are significant only for instances with a resolution greater than |
2329 | - one day. When the timezone is insignificant, the result of methods like |
2330 | - asISO8601TimeAndDate is the same for any given tzinfo parameter. Sort order |
2331 | - is determined by the start of the period in UTC. For example, "today" sorts |
2332 | - after "midnight today, central Europe", and before "midnight today, US |
2333 | - Eastern". For applications that need to store a mix of timezone dependent |
2334 | - and independent instances, it may be wise to store them separately, since |
2335 | - the time between the start and end of today in the local timezone may not |
2336 | - include the start of today in UTC, and thus not independent instances |
2337 | - addressing the whole day. In other words, the desired sort order (the one |
2338 | - where just "Monday" sorts before any more precise time in "Monday", and |
2339 | - after any in "Sunday") of Time instances is dependant on the timezone |
2340 | - context. |
2341 | - |
2342 | - Date arithmetic and boolean operations operate on instants in time, not |
2343 | - periods. In this case, the start of the period is used as the value, and |
2344 | - the result has a resolution of 0. |
2345 | - |
2346 | - For containment tests with the 'in' operator, the period addressed by the |
2347 | - instance is used. |
2348 | - |
2349 | - The methods beginning with 'from' are constructors to create instances from |
2350 | - various formats. Some of them are textual formats, and others are other |
2351 | - time types commonly found in Python code. |
2352 | - |
2353 | - Likewise, methods beginning with 'as' return the represented time in |
2354 | - various formats. Some of these methods should try to reflect the resolution |
2355 | - of the instance. However, they don't yet. |
2356 | - |
2357 | - For formats with both a constructor and a formatter, d == fromFu(d.asFu()) |
2358 | - |
2359 | - @type resolution: datetime.timedelta |
2360 | - @ivar resolution: the length of the period to which this instance could |
2361 | - refer. For example, "Today, 13:38" could refer to any time between 13:38 |
2362 | - until but not including 13:39. In this case resolution would be |
2363 | - timedelta(minutes=1). |
2364 | - """ |
2365 | - |
2366 | - # the instance variable _time is the internal representation of time. It |
2367 | - # is a naive datetime object which is always UTC. A UTC tzinfo would be |
2368 | - # great, if one existed, and anyway it complicates pickling. |
2369 | - |
2370 | - |
2371 | - class Precision(object): |
2372 | - MINUTES = object() |
2373 | - SECONDS = object() |
2374 | - |
2375 | - |
2376 | - _timeFormat = { |
2377 | - Precision.MINUTES: '%I:%M %p', |
2378 | - Precision.SECONDS: '%I:%M:%S %p'} |
2379 | - |
2380 | - rfc2822Weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] |
2381 | - |
2382 | - rfc2822Months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', |
2383 | - 'Sep', 'Oct', 'Nov', 'Dec'] |
2384 | - |
2385 | - resolution = datetime.timedelta.resolution |
2386 | - |
2387 | - # |
2388 | - # Methods to create new instances |
2389 | - # |
2390 | - |
2391 | - def __init__(self): |
2392 | - """Return a new Time instance representing the time now. |
2393 | - |
2394 | - See also the fromFu methods to create new instances from other types of |
2395 | - initializers. |
2396 | - """ |
2397 | - self._time = datetime.datetime.utcnow() |
2398 | - |
2399 | - |
2400 | - def _fromWeekday(klass, match, tzinfo, now): |
2401 | - weekday = klass.weekdays.index(match.group('weekday').lower()) |
2402 | - dtnow = now.asDatetime().replace( |
2403 | - hour=0, minute=0, second=0, microsecond=0) |
2404 | - daysInFuture = (weekday - dtnow.weekday()) % len(klass.weekdays) |
2405 | - if daysInFuture == 0: |
2406 | - daysInFuture = 7 |
2407 | - self = klass.fromDatetime(dtnow + datetime.timedelta(days=daysInFuture)) |
2408 | - assert self.asDatetime().weekday() == weekday |
2409 | - self.resolution = datetime.timedelta(days=1) |
2410 | - return self |
2411 | - |
2412 | - |
2413 | - def _fromTodayOrTomorrow(klass, match, tzinfo, now): |
2414 | - dtnow = now.asDatetime().replace( |
2415 | - hour=0, minute=0, second=0, microsecond=0) |
2416 | - when = match.group(0).lower() |
2417 | - if when == 'tomorrow': |
2418 | - dtnow += datetime.timedelta(days=1) |
2419 | - elif when == 'yesterday': |
2420 | - dtnow -= datetime.timedelta(days=1) |
2421 | - else: |
2422 | - assert when == 'today' |
2423 | - self = klass.fromDatetime(dtnow) |
2424 | - self.resolution = datetime.timedelta(days=1) |
2425 | - return self |
2426 | - |
2427 | - |
2428 | - def _fromTime(klass, match, tzinfo, now): |
2429 | - minute = int(match.group('minute')) |
2430 | - hour = int(match.group('hour')) |
2431 | - ampm = (match.group('ampm') or '').lower() |
2432 | - if ampm: |
2433 | - if not 1 <= hour <= 12: |
2434 | - raise ValueError, 'hour %i is not in 1..12' % (hour,) |
2435 | - if hour == 12 and ampm == 'am': |
2436 | - hour = 0 |
2437 | - elif ampm == 'pm': |
2438 | - hour += 12 |
2439 | - if not 0 <= hour <= 23: |
2440 | - raise ValueError, 'hour %i is not in 0..23' % (hour,) |
2441 | - |
2442 | - dtnow = now.asDatetime(tzinfo).replace(second=0, microsecond=0) |
2443 | - dtthen = dtnow.replace(hour=hour, minute=minute) |
2444 | - if dtthen < dtnow: |
2445 | - dtthen += datetime.timedelta(days=1) |
2446 | - |
2447 | - self = klass.fromDatetime(dtthen) |
2448 | - self.resolution = datetime.timedelta(minutes=1) |
2449 | - return self |
2450 | - |
2451 | - |
2452 | - def _fromNoonOrMidnight(klass, match, tzinfo, now): |
2453 | - when = match.group(0).lower() |
2454 | - if when == 'noon': |
2455 | - hour = 12 |
2456 | - else: |
2457 | - assert when == 'midnight' |
2458 | - hour = 0 |
2459 | - dtnow = now.asDatetime(tzinfo).replace( |
2460 | - minute=0, second=0, microsecond=0) |
2461 | - dtthen = dtnow.replace(hour=hour) |
2462 | - if dtthen < dtnow: |
2463 | - dtthen += datetime.timedelta(days=1) |
2464 | - |
2465 | - self = klass.fromDatetime(dtthen) |
2466 | - self.resolution = datetime.timedelta(minutes=1) |
2467 | - return self |
2468 | - |
2469 | - def _fromNow(klass, match, tzinfo, now): |
2470 | - # coerce our 'now' argument to an instant |
2471 | - return now + datetime.timedelta(0) |
2472 | - |
2473 | - weekdays = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', |
2474 | - 'saturday', 'sunday'] |
2475 | - |
2476 | - humanlyPatterns = [ |
2477 | - (re.compile(r""" |
2478 | - \b |
2479 | - ((next|this)\s+)? |
2480 | - (?P<weekday> |
2481 | - monday |
2482 | - | tuesday |
2483 | - | wednesday |
2484 | - | thursday |
2485 | - | friday |
2486 | - | saturday |
2487 | - | sunday |
2488 | - ) |
2489 | - \b |
2490 | - """, re.IGNORECASE | re.VERBOSE), |
2491 | - _fromWeekday), |
2492 | - (re.compile(r"\b(today|tomorrow|yesterday)\b", re.IGNORECASE), |
2493 | - _fromTodayOrTomorrow), |
2494 | - (re.compile(r""" |
2495 | - \b |
2496 | - (?P<hour>\d{1,2}):(?P<minute>\d{2}) |
2497 | - (\s*(?P<ampm>am|pm))? |
2498 | - \b |
2499 | - """, re.IGNORECASE | re.VERBOSE), |
2500 | - _fromTime), |
2501 | - (re.compile(r"\b(noon|midnight)\b", re.IGNORECASE), |
2502 | - _fromNoonOrMidnight), |
2503 | - (re.compile(r"\b(now)\b", re.IGNORECASE), |
2504 | - _fromNow), |
2505 | - ] |
2506 | - |
2507 | - _fromWeekday = classmethod(_fromWeekday) |
2508 | - _fromTodayOrTomorrow = classmethod(_fromTodayOrTomorrow) |
2509 | - _fromTime = classmethod(_fromTime) |
2510 | - _fromNoonOrMidnight = classmethod(_fromNoonOrMidnight) |
2511 | - _fromNow = classmethod(_fromNow) |
2512 | - |
2513 | - |
2514 | - def fromHumanly(klass, humanStr, tzinfo=None, now=None): |
2515 | - """Return a new Time instance from a string a human might type. |
2516 | - |
2517 | - @param humanStr: the string to be parsed. |
2518 | - |
2519 | - @param tzinfo: A tzinfo instance indicating the timezone to assume if |
2520 | - none is specified in humanStr. If None, assume UTC. |
2521 | - |
2522 | - @param now: A Time instance to be considered "now" for when |
2523 | - interpreting relative dates like "tomorrow". If None, use the real now. |
2524 | - |
2525 | - Total crap now, it just supports weekdays, "today" and "tomorrow" for |
2526 | - now. This is pretty insufficient and useless, but good enough for some |
2527 | - demo functionality, or something. |
2528 | - """ |
2529 | - humanStr = humanStr.strip() |
2530 | - if now is None: |
2531 | - now = Time() |
2532 | - if tzinfo is None: |
2533 | - tzinfo = FixedOffset(0, 0) |
2534 | - |
2535 | - for pattern, creator in klass.humanlyPatterns: |
2536 | - match = pattern.match(humanStr) |
2537 | - if not match \ |
2538 | - or match.span()[1] != len(humanStr): |
2539 | - continue |
2540 | - try: |
2541 | - return creator(klass, match, tzinfo, now) |
2542 | - except ValueError: |
2543 | - continue |
2544 | - raise ValueError, 'could not parse date: %r' % (humanStr,) |
2545 | - |
2546 | - fromHumanly = classmethod(fromHumanly) |
2547 | - |
2548 | - |
2549 | - iso8601pattern = re.compile(r""" |
2550 | - ^ (?P<year> \d{4}) |
2551 | - ( |
2552 | - # a year may optionally be followed by one of: |
2553 | - # - a month |
2554 | - # - a week |
2555 | - # - a specific day, and an optional time |
2556 | - # a specific day is one of: |
2557 | - # - a month and day |
2558 | - # - week and weekday |
2559 | - # - a day of the year |
2560 | - ( |
2561 | - -? (?P<month1> \d{2}) |
2562 | - | |
2563 | - -? W (?P<week1> \d{2}) |
2564 | - | |
2565 | - ( |
2566 | - -? (?P<month2> \d{2}) |
2567 | - -? (?P<day> \d{2}) |
2568 | - | |
2569 | - -? W (?P<week2> \d{2}) |
2570 | - -? (?P<weekday> \d) |
2571 | - | |
2572 | - -? (?P<dayofyear> \d{3}) |
2573 | - ) |
2574 | - ( |
2575 | - T (?P<hour> \d{2}) |
2576 | - ( |
2577 | - :? (?P<minute> \d{2}) |
2578 | - ( |
2579 | - :? (?P<second> \d{2}) |
2580 | - ( |
2581 | - [\.,] (?P<fractionalsec> \d+) |
2582 | - )? |
2583 | - )? |
2584 | - )? |
2585 | - ( |
2586 | - (?P<zulu> Z) |
2587 | - | |
2588 | - (?P<tzhour> [+\-]\d{2}) |
2589 | - ( |
2590 | - :? (?P<tzmin> \d{2}) |
2591 | - )? |
2592 | - )? |
2593 | - )? |
2594 | - )? |
2595 | - )? $""", re.VERBOSE) |
2596 | - |
2597 | - |
2598 | - def fromISO8601TimeAndDate(klass, iso8601string, tzinfo=None): |
2599 | - """Return a new Time instance from a string formated as in ISO 8601. |
2600 | - |
2601 | - If the given string contains no timezone, it is assumed to be in the |
2602 | - timezone specified by the parameter `tzinfo`, or UTC if tzinfo is None. |
2603 | - An input string with an explicit timezone will always override tzinfo. |
2604 | - |
2605 | - If the given iso8601string does not contain all parts of the time, they |
2606 | - will default to 0 in the timezone given by `tzinfo`. |
2607 | - |
2608 | - WARNING: this function is incomplete. ISO is dumb and their standards |
2609 | - are not free. Only a subset of all valid ISO 8601 dates are parsed, |
2610 | - because I can't find a formal description of the format. However, |
2611 | - common ones should work. |
2612 | - """ |
2613 | - |
2614 | - def calculateTimezone(): |
2615 | - if groups['zulu'] == 'Z': |
2616 | - return FixedOffset(0, 0) |
2617 | - else: |
2618 | - tzhour = groups.pop('tzhour') |
2619 | - tzmin = groups.pop('tzmin') |
2620 | - if tzhour is not None: |
2621 | - return FixedOffset(int(tzhour), int(tzmin or 0)) |
2622 | - return tzinfo or FixedOffset(0, 0) |
2623 | - |
2624 | - def coerceGroups(): |
2625 | - groups['month'] = groups['month1'] or groups['month2'] |
2626 | - groups['week'] = groups['week1'] or groups['week2'] |
2627 | - # don't include fractional seconds, because it's not an integer. |
2628 | - defaultTo0 = ['hour', 'minute', 'second'] |
2629 | - defaultTo1 = ['month', 'day', 'week', 'weekday', 'dayofyear'] |
2630 | - if groups['fractionalsec'] is None: |
2631 | - groups['fractionalsec'] = '0' |
2632 | - for key in defaultTo0: |
2633 | - if groups[key] is None: |
2634 | - groups[key] = 0 |
2635 | - for key in defaultTo1: |
2636 | - if groups[key] is None: |
2637 | - groups[key] = 1 |
2638 | - groups['fractionalsec'] = float('.'+groups['fractionalsec']) |
2639 | - for key in defaultTo0 + defaultTo1 + ['year']: |
2640 | - groups[key] = int(groups[key]) |
2641 | - |
2642 | - for group, min, max in [ |
2643 | - # some years have only 52 weeks |
2644 | - ('week', 1, 53), |
2645 | - ('weekday', 1, 7), |
2646 | - ('month', 1, 12), |
2647 | - ('day', 1, 31), |
2648 | - ('hour', 0, 24), |
2649 | - ('minute', 0, 59), |
2650 | - |
2651 | - # Sometime in the 22nd century AD, two leap seconds will be |
2652 | - # required every year. In the 25th century AD, four every |
2653 | - # year. We'll ignore that for now though because it would be |
2654 | - # tricky to get right and we certainly don't need it for our |
2655 | - # target applications. In other words, post-singularity |
2656 | - # Martian users, please do not rely on this code for |
2657 | - # compatibility with Greater Galactic Protectorate of Earth |
2658 | - # date/time formatting! Apologies, but no library I know of in |
2659 | - # Python is sufficient for processing their dates and times |
2660 | - # without ADA bindings to get the radiation-safety zone counter |
2661 | - # correct. -glyph |
2662 | - |
2663 | - ('second', 0, 61), |
2664 | - # don't forget leap years |
2665 | - ('dayofyear', 1, 366)]: |
2666 | - if not min <= groups[group] <= max: |
2667 | - raise ValueError, '%s must be in %i..%i' % (group, min, max) |
2668 | - |
2669 | - def determineResolution(): |
2670 | - if match.group('fractionalsec') is not None: |
2671 | - return max(datetime.timedelta.resolution, |
2672 | - datetime.timedelta( |
2673 | - microseconds=1 * 10 ** -len( |
2674 | - match.group('fractionalsec')) * 1000000)) |
2675 | - |
2676 | - for testGroup, resolution in [ |
2677 | - ('second', datetime.timedelta(seconds=1)), |
2678 | - ('minute', datetime.timedelta(minutes=1)), |
2679 | - ('hour', datetime.timedelta(hours=1)), |
2680 | - ('weekday', datetime.timedelta(days=1)), |
2681 | - ('dayofyear', datetime.timedelta(days=1)), |
2682 | - ('day', datetime.timedelta(days=1)), |
2683 | - ('week1', datetime.timedelta(weeks=1)), |
2684 | - ('week2', datetime.timedelta(weeks=1))]: |
2685 | - if match.group(testGroup) is not None: |
2686 | - return resolution |
2687 | - |
2688 | - if match.group('month1') is not None \ |
2689 | - or match.group('month2') is not None: |
2690 | - if self._time.month == 12: |
2691 | - return datetime.timedelta(days=31) |
2692 | - nextMonth = self._time.replace(month=self._time.month+1) |
2693 | - return nextMonth - self._time |
2694 | - else: |
2695 | - nextYear = self._time.replace(year=self._time.year+1) |
2696 | - return nextYear - self._time |
2697 | - |
2698 | - def calculateDtime(tzinfo): |
2699 | - """Calculate a datetime for the start of the addressed period.""" |
2700 | - |
2701 | - if match.group('week1') is not None \ |
2702 | - or match.group('week2') is not None: |
2703 | - if not 0 < groups['week'] <= 53: |
2704 | - raise ValueError( |
2705 | - 'week must be in 1..53 (was %i)' % (groups['week'],)) |
2706 | - dtime = datetime.datetime( |
2707 | - groups['year'], |
2708 | - 1, |
2709 | - 4, |
2710 | - groups['hour'], |
2711 | - groups['minute'], |
2712 | - groups['second'], |
2713 | - int(round(groups['fractionalsec'] * 1000000)), |
2714 | - tzinfo=tzinfo |
2715 | - ) |
2716 | - dtime -= datetime.timedelta(days = dtime.weekday()) |
2717 | - dtime += datetime.timedelta( |
2718 | - days = (groups['week']-1) * 7 + groups['weekday'] - 1) |
2719 | - if dtime.isocalendar() != ( |
2720 | - groups['year'], groups['week'], groups['weekday']): |
2721 | - # actually the problem could be an error in my logic, but |
2722 | - # nothing should cause this but requesting week 53 of a |
2723 | - # year with 52 weeks. |
2724 | - raise ValueError('year %04i has no week %02i' % |
2725 | - (groups['year'], groups['week'])) |
2726 | - return dtime |
2727 | - |
2728 | - if match.group('dayofyear') is not None: |
2729 | - dtime = datetime.datetime( |
2730 | - groups['year'], |
2731 | - 1, |
2732 | - 1, |
2733 | - groups['hour'], |
2734 | - groups['minute'], |
2735 | - groups['second'], |
2736 | - int(round(groups['fractionalsec'] * 1000000)), |
2737 | - tzinfo=tzinfo |
2738 | - ) |
2739 | - dtime += datetime.timedelta(days=groups['dayofyear']-1) |
2740 | - if dtime.year != groups['year']: |
2741 | - raise ValueError( |
2742 | - 'year %04i has no day of year %03i' % |
2743 | - (groups['year'], groups['dayofyear'])) |
2744 | - return dtime |
2745 | - |
2746 | - else: |
2747 | - return datetime.datetime( |
2748 | - groups['year'], |
2749 | - groups['month'], |
2750 | - groups['day'], |
2751 | - groups['hour'], |
2752 | - groups['minute'], |
2753 | - groups['second'], |
2754 | - int(round(groups['fractionalsec'] * 1000000)), |
2755 | - tzinfo=tzinfo |
2756 | - ) |
2757 | - |
2758 | - |
2759 | - match = klass.iso8601pattern.match(iso8601string) |
2760 | - if match is None: |
2761 | - raise ValueError( |
2762 | - '%r could not be parsed as an ISO 8601 date and time' % |
2763 | - (iso8601string,)) |
2764 | - |
2765 | - groups = match.groupdict() |
2766 | - coerceGroups() |
2767 | - if match.group('hour') is not None: |
2768 | - timezone = calculateTimezone() |
2769 | - else: |
2770 | - timezone = None |
2771 | - self = klass.fromDatetime(calculateDtime(timezone)) |
2772 | - self.resolution = determineResolution() |
2773 | - return self |
2774 | - |
2775 | - fromISO8601TimeAndDate = classmethod(fromISO8601TimeAndDate) |
2776 | - |
2777 | - def fromStructTime(klass, structTime, tzinfo=None): |
2778 | - """Return a new Time instance from a time.struct_time. |
2779 | - |
2780 | - If tzinfo is None, structTime is in UTC. Otherwise, tzinfo is a |
2781 | - datetime.tzinfo instance coresponding to the timezone in which |
2782 | - structTime is. |
2783 | - |
2784 | - Many of the functions in the standard time module return these things. |
2785 | - This will also work with a plain 9-tuple, for parity with the time |
2786 | - module. The last three elements, or tm_wday, tm_yday, and tm_isdst are |
2787 | - ignored. |
2788 | - """ |
2789 | - dtime = datetime.datetime(tzinfo=tzinfo, *structTime[:6]) |
2790 | - self = klass.fromDatetime(dtime) |
2791 | - self.resolution = datetime.timedelta(seconds=1) |
2792 | - return self |
2793 | - |
2794 | - fromStructTime = classmethod(fromStructTime) |
2795 | - |
2796 | - def fromDatetime(klass, dtime): |
2797 | - """Return a new Time instance from a datetime.datetime instance. |
2798 | - |
2799 | - If the datetime instance does not have an associated timezone, it is |
2800 | - assumed to be UTC. |
2801 | - """ |
2802 | - self = klass.__new__(klass) |
2803 | - if dtime.tzinfo is not None: |
2804 | - self._time = dtime.astimezone(FixedOffset(0, 0)).replace(tzinfo=None) |
2805 | - else: |
2806 | - self._time = dtime |
2807 | - self.resolution = datetime.timedelta.resolution |
2808 | - return self |
2809 | - |
2810 | - fromDatetime = classmethod(fromDatetime) |
2811 | - |
2812 | - def fromPOSIXTimestamp(klass, secs): |
2813 | - """Return a new Time instance from seconds since the POSIX epoch. |
2814 | - |
2815 | - The POSIX epoch is midnight Jan 1, 1970 UTC. According to POSIX, leap |
2816 | - seconds don't exist, so one UTC day is exactly 86400 seconds, even if |
2817 | - it wasn't. |
2818 | - |
2819 | - @param secs: a number of seconds, represented as an integer, long or |
2820 | - float. |
2821 | - """ |
2822 | - self = klass.fromDatetime(_EPOCH + datetime.timedelta(seconds=secs)) |
2823 | - self.resolution = datetime.timedelta() |
2824 | - return self |
2825 | - |
2826 | - fromPOSIXTimestamp = classmethod(fromPOSIXTimestamp) |
2827 | - |
2828 | - def fromRFC2822(klass, rfc822string): |
2829 | - """ |
2830 | - Return a new Time instance from a string formated as described in RFC 2822. |
2831 | - |
2832 | - @type rfc822string: str |
2833 | - |
2834 | - @raise ValueError: if the timestamp is not formatted properly (or if |
2835 | - certain obsoleted elements of the specification are used). |
2836 | - |
2837 | - @return: a new L{Time} |
2838 | - """ |
2839 | - |
2840 | - # parsedate_tz is going to give us a "struct_time plus", a 10-tuple |
2841 | - # containing the 9 values a struct_time would, i.e.: (tm_year, tm_mon, |
2842 | - # tm_day, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst), plus a |
2843 | - # bonus "offset", which is an offset (in _seconds_, of all things). |
2844 | - |
2845 | - maybeStructTimePlus = parsedate_tz(rfc822string) |
2846 | - |
2847 | - if maybeStructTimePlus is None: |
2848 | - raise ValueError, 'could not parse RFC 2822 date %r' % (rfc822string,) |
2849 | - structTimePlus = sanitizeStructTime(maybeStructTimePlus) |
2850 | - offsetInSeconds = structTimePlus[-1] |
2851 | - if offsetInSeconds is None: |
2852 | - offsetInSeconds = 0 |
2853 | - self = klass.fromStructTime( |
2854 | - structTimePlus, |
2855 | - FixedOffset( |
2856 | - hours=0, |
2857 | - minutes=offsetInSeconds // 60)) |
2858 | - self.resolution = datetime.timedelta(seconds=1) |
2859 | - return self |
2860 | - |
2861 | - fromRFC2822 = classmethod(fromRFC2822) |
2862 | - |
2863 | - # |
2864 | - # Methods to produce various formats |
2865 | - # |
2866 | - |
2867 | - def asPOSIXTimestamp(self): |
2868 | - """Return this time as a timestamp as specified by POSIX. |
2869 | - |
2870 | - This timestamp is the count of the number of seconds since Midnight, |
2871 | - Jan 1 1970 UTC, ignoring leap seconds. |
2872 | - """ |
2873 | - mytimedelta = self._time - _EPOCH |
2874 | - return _timedeltaToSeconds(mytimedelta) |
2875 | - |
2876 | - def asDatetime(self, tzinfo=None): |
2877 | - """Return this time as an aware datetime.datetime instance. |
2878 | - |
2879 | - The returned datetime object has the specified tzinfo, or a tzinfo |
2880 | - describing UTC if the tzinfo parameter is None. |
2881 | - """ |
2882 | - if tzinfo is None: |
2883 | - tzinfo = FixedOffset(0, 0) |
2884 | - |
2885 | - if not self.isTimezoneDependent(): |
2886 | - return self._time.replace(tzinfo=tzinfo) |
2887 | - else: |
2888 | - return self._time.replace(tzinfo=FixedOffset(0, 0)).astimezone(tzinfo) |
2889 | - |
2890 | - def asNaiveDatetime(self, tzinfo=None): |
2891 | - """Return this time as a naive datetime.datetime instance. |
2892 | - |
2893 | - The returned datetime object has its tzinfo set to None, but is in the |
2894 | - timezone given by the tzinfo parameter, or UTC if the parameter is |
2895 | - None. |
2896 | - """ |
2897 | - return self.asDatetime(tzinfo).replace(tzinfo=None) |
2898 | - |
2899 | - def asRFC2822(self, tzinfo=None, includeDayOfWeek=True): |
2900 | - """Return this Time formatted as specified in RFC 2822. |
2901 | - |
2902 | - RFC 2822 specifies the format of email messages. |
2903 | - |
2904 | - RFC 2822 says times in email addresses should reflect the local |
2905 | - timezone. If tzinfo is a datetime.tzinfo instance, the returned |
2906 | - formatted string will reflect that timezone. Otherwise, the timezone |
2907 | - will be '-0000', which RFC 2822 defines as UTC, but with an unknown |
2908 | - local timezone. |
2909 | - |
2910 | - RFC 2822 states that the weekday is optional. The parameter |
2911 | - includeDayOfWeek indicates whether or not to include it. |
2912 | - """ |
2913 | - dtime = self.asDatetime(tzinfo) |
2914 | - |
2915 | - if tzinfo is None: |
2916 | - rfcoffset = '-0000' |
2917 | - else: |
2918 | - rfcoffset = '%s%02i%02i' % _timedeltaToSignHrMin(dtime.utcoffset()) |
2919 | - |
2920 | - rfcstring = '' |
2921 | - if includeDayOfWeek: |
2922 | - rfcstring += self.rfc2822Weekdays[dtime.weekday()] + ', ' |
2923 | - |
2924 | - rfcstring += '%i %s %4i %02i:%02i:%02i %s' % ( |
2925 | - dtime.day, |
2926 | - self.rfc2822Months[dtime.month - 1], |
2927 | - dtime.year, |
2928 | - dtime.hour, |
2929 | - dtime.minute, |
2930 | - dtime.second, |
2931 | - rfcoffset) |
2932 | - |
2933 | - return rfcstring |
2934 | - |
2935 | - def asISO8601TimeAndDate(self, includeDelimiters=True, tzinfo=None, |
2936 | - includeTimezone=True): |
2937 | - """Return this time formatted as specified by ISO 8861. |
2938 | - |
2939 | - ISO 8601 allows optional dashes to delimit dates and colons to delimit |
2940 | - times. The parameter includeDelimiters (default True) defines the |
2941 | - inclusion of these delimiters in the output. |
2942 | - |
2943 | - If tzinfo is a datetime.tzinfo instance, the output time will be in the |
2944 | - timezone given. If it is None (the default), then the timezone string |
2945 | - will not be included in the output, and the time will be in UTC. |
2946 | - |
2947 | - The includeTimezone parameter coresponds to the inclusion of an |
2948 | - explicit timezone. The default is True. |
2949 | - """ |
2950 | - if not self.isTimezoneDependent(): |
2951 | - tzinfo = None |
2952 | - dtime = self.asDatetime(tzinfo) |
2953 | - |
2954 | - if includeDelimiters: |
2955 | - dateSep = '-' |
2956 | - timeSep = ':' |
2957 | - else: |
2958 | - dateSep = timeSep = '' |
2959 | - |
2960 | - if includeTimezone: |
2961 | - if tzinfo is None: |
2962 | - timezone = '+00%s00' % (timeSep,) |
2963 | - else: |
2964 | - sign, hour, min = _timedeltaToSignHrMin(dtime.utcoffset()) |
2965 | - timezone = '%s%02i%s%02i' % (sign, hour, timeSep, min) |
2966 | - else: |
2967 | - timezone = '' |
2968 | - |
2969 | - microsecond = ('%06i' % (dtime.microsecond,)).rstrip('0') |
2970 | - if microsecond: |
2971 | - microsecond = '.' + microsecond |
2972 | - |
2973 | - parts = [ |
2974 | - ('%04i' % (dtime.year,), datetime.timedelta(days=366)), |
2975 | - ('%s%02i' % (dateSep, dtime.month), datetime.timedelta(days=31)), |
2976 | - ('%s%02i' % (dateSep, dtime.day), datetime.timedelta(days=1)), |
2977 | - ('T', datetime.timedelta(hours=1)), |
2978 | - ('%02i' % (dtime.hour,), datetime.timedelta(hours=1)), |
2979 | - ('%s%02i' % (timeSep, dtime.minute), datetime.timedelta(minutes=1)), |
2980 | - ('%s%02i' % (timeSep, dtime.second), datetime.timedelta(seconds=1)), |
2981 | - (microsecond, datetime.timedelta(microseconds=1)), |
2982 | - (timezone, datetime.timedelta(hours=1)) |
2983 | - ] |
2984 | - |
2985 | - formatted = '' |
2986 | - for part, minResolution in parts: |
2987 | - if self.resolution <= minResolution: |
2988 | - formatted += part |
2989 | - |
2990 | - return formatted |
2991 | - |
2992 | - def asStructTime(self, tzinfo=None): |
2993 | - """Return this time represented as a time.struct_time. |
2994 | - |
2995 | - tzinfo is a datetime.tzinfo instance coresponding to the desired |
2996 | - timezone of the output. If is is the default None, UTC is assumed. |
2997 | - """ |
2998 | - dtime = self.asDatetime(tzinfo) |
2999 | - if tzinfo is None: |
3000 | - return dtime.utctimetuple() |
3001 | - else: |
3002 | - return dtime.timetuple() |
3003 | - |
3004 | - def asHumanly(self, tzinfo=None, now=None, precision=Precision.MINUTES): |
3005 | - """Return this time as a short string, tailored to the current time. |
3006 | - |
3007 | - Parts of the date that can be assumed are omitted. Consequently, the |
3008 | - output string depends on the current time. This is the format used for |
3009 | - displaying dates in most user visible places in the quotient web UI. |
3010 | - |
3011 | - By default, the current time is determined by the system clock. The |
3012 | - current time used for formatting the time can be changed by providing a |
3013 | - Time instance as the parameter 'now'. |
3014 | - |
3015 | - @param precision: The smallest unit of time that will be represented |
3016 | - in the returned string. Valid values are L{Time.Precision.MINUTES} and |
3017 | - L{Time.Precision.SECONDS}. |
3018 | - |
3019 | - @raise InvalidPrecision: if the specified precision is not either |
3020 | - L{Time.Precision.MINUTES} or L{Time.Precision.SECONDS}. |
3021 | - """ |
3022 | - try: |
3023 | - timeFormat = Time._timeFormat[precision] |
3024 | - except KeyError: |
3025 | - raise InvalidPrecision( |
3026 | - 'Use Time.Precision.MINUTES or Time.Precision.SECONDS') |
3027 | - |
3028 | - if now is None: |
3029 | - now = Time().asDatetime(tzinfo) |
3030 | - else: |
3031 | - now = now.asDatetime(tzinfo) |
3032 | - dtime = self.asDatetime(tzinfo) |
3033 | - |
3034 | - # Same day? |
3035 | - if dtime.date() == now.date(): |
3036 | - if self.isAllDay(): |
3037 | - return 'all day' |
3038 | - return dtime.strftime(timeFormat).lower() |
3039 | - else: |
3040 | - res = str(dtime.date().day) + dtime.strftime(' %b') # day + month |
3041 | - # Different year? |
3042 | - if not dtime.date().year == now.date().year: |
3043 | - res += dtime.strftime(' %Y') |
3044 | - if not self.isAllDay(): |
3045 | - res += dtime.strftime(', %s' % (timeFormat,)).lower() |
3046 | - return res |
3047 | - |
3048 | - # |
3049 | - # methods to return related times |
3050 | - # |
3051 | - |
3052 | - def getBounds(self, tzinfo=None): |
3053 | - """ |
3054 | - Return a pair describing the bounds of self. |
3055 | - |
3056 | - This returns a pair (min, max) of Time instances. It is not quite the |
3057 | - same as (self, self + self.resolution). This is because timezones are |
3058 | - insignificant for instances with a resolution greater or equal to 1 |
3059 | - day. |
3060 | - |
3061 | - To illustrate the problem, consider a Time instance:: |
3062 | - |
3063 | - T = Time.fromHumanly('today', tzinfo=anything) |
3064 | - |
3065 | - This will return an equivalent instance independent of the tzinfo used. |
3066 | - The hour, minute, and second of this instance are 0, and its resolution |
3067 | - is one day. |
3068 | - |
3069 | - Now say we have a sorted list of times, and we want to get all times |
3070 | - for 'today', where whoever said 'today' is in a timezone that's 5 hours |
3071 | - ahead of UTC. The start of 'today' in this timezone is UTC 05:00. The |
3072 | - example instance T above is before this, but obviously it is today. |
3073 | - |
3074 | - The min and max times this returns are such that all potentially |
3075 | - matching instances are within this range. However, this range might |
3076 | - contain unmatching instances. |
3077 | - |
3078 | - As an example of this, if 'today' is April first 2005, then |
3079 | - Time.fromISO8601TimeAndDate('2005-04-01T00:00:00') sorts in the same |
3080 | - place as T from above, but is not in the UTC+5 'today'. |
3081 | - |
3082 | - TIME IS FUN! |
3083 | - """ |
3084 | - if self.resolution >= datetime.timedelta(days=1) \ |
3085 | - and tzinfo is not None: |
3086 | - time = self._time.replace(tzinfo=tzinfo) |
3087 | - else: |
3088 | - time = self._time |
3089 | - |
3090 | - return ( |
3091 | - min(self.fromDatetime(time), self.fromDatetime(self._time)), |
3092 | - max(self.fromDatetime(time + self.resolution), |
3093 | - self.fromDatetime(self._time + self.resolution)) |
3094 | - ) |
3095 | - |
3096 | - def oneDay(self): |
3097 | - """Return a Time instance representing the day of the start of self. |
3098 | - |
3099 | - The returned new instance will be set to midnight of the day containing |
3100 | - the first instant of self in the specified timezone, and have a |
3101 | - resolution of datetime.timedelta(days=1). |
3102 | - """ |
3103 | - day = self.__class__.fromDatetime(self.asDatetime().replace( |
3104 | - hour=0, minute=0, second=0, microsecond=0)) |
3105 | - day.resolution = datetime.timedelta(days=1) |
3106 | - return day |
3107 | - |
3108 | - # |
3109 | - # useful predicates |
3110 | - # |
3111 | - |
3112 | - def isAllDay(self): |
3113 | - """Return True iff this instance represents exactly all day.""" |
3114 | - return self.resolution == datetime.timedelta(days=1) |
3115 | - |
3116 | - def isTimezoneDependent(self): |
3117 | - """Return True iff timezone is relevant for this instance. |
3118 | - |
3119 | - Timezone is only relevent for instances with a resolution better than |
3120 | - one day. |
3121 | - """ |
3122 | - return self.resolution < datetime.timedelta(days=1) |
3123 | - |
3124 | - # |
3125 | - # other magic methods |
3126 | - # |
3127 | - |
3128 | - def __cmp__(self, other): |
3129 | - if not isinstance(other, Time): |
3130 | - raise TypeError("Cannot meaningfully compare %r with %r" % (self, other)) |
3131 | - return cmp(self._time, other._time) |
3132 | - |
3133 | - def __eq__(self, other): |
3134 | - if isinstance(other, Time): |
3135 | - return cmp(self._time, other._time) == 0 |
3136 | - return False |
3137 | - |
3138 | - def __ne__(self, other): |
3139 | - return not (self == other) |
3140 | - |
3141 | - def __repr__(self): |
3142 | - return 'extime.Time.fromDatetime(%r)' % (self._time,) |
3143 | - |
3144 | - __str__ = asISO8601TimeAndDate |
3145 | - |
3146 | - def __contains__(self, other): |
3147 | - """Test if another Time instance is entirely within the period addressed by this one.""" |
3148 | - if not isinstance(other, Time): |
3149 | - raise TypeError( |
3150 | - '%r is not a Time instance; can not test for containment' |
3151 | - % (other,)) |
3152 | - if other._time < self._time: |
3153 | - return False |
3154 | - if self._time + self.resolution < other._time + other.resolution: |
3155 | - return False |
3156 | - return True |
3157 | - |
3158 | - def __add__(self, addend): |
3159 | - if not isinstance(addend, datetime.timedelta): |
3160 | - raise TypeError, 'expected a datetime.timedelta instance' |
3161 | - return Time.fromDatetime(self._time + addend) |
3162 | - |
3163 | - def __sub__(self, subtrahend): |
3164 | - """ |
3165 | - Implement subtraction of an interval or another time from this one. |
3166 | - |
3167 | - @type subtrahend: L{datetime.timedelta} or L{Time} |
3168 | - |
3169 | - @param subtrahend: The object to be subtracted from this one. |
3170 | - |
3171 | - @rtype: L{datetime.timedelta} or L{Time} |
3172 | - |
3173 | - @return: If C{subtrahend} is a L{datetime.timedelta}, the result is |
3174 | - a L{Time} instance which is offset from this one by that amount. If |
3175 | - C{subtrahend} is a L{Time}, the result is a L{datetime.timedelta} |
3176 | - instance which gives the difference between it and this L{Time} |
3177 | - instance. |
3178 | - """ |
3179 | - if isinstance(subtrahend, datetime.timedelta): |
3180 | - return Time.fromDatetime(self._time - subtrahend) |
3181 | - |
3182 | - if isinstance(subtrahend, Time): |
3183 | - return self.asDatetime() - subtrahend.asDatetime() |
3184 | - |
3185 | - return NotImplemented |
3186 | |
3187 | === removed file 'Epsilon/epsilon/hotfix.py' |
3188 | --- Epsilon/epsilon/hotfix.py 2009-05-22 13:03:43 +0000 |
3189 | +++ Epsilon/epsilon/hotfix.py 1970-01-01 00:00:00 +0000 |
3190 | @@ -1,81 +0,0 @@ |
3191 | - |
3192 | -import inspect |
3193 | - |
3194 | -class NoSuchHotfix(Exception): |
3195 | - """ |
3196 | - Man you must be pretty stupid. |
3197 | - """ |
3198 | - |
3199 | -_alreadyInstalled = set() |
3200 | -def require(packageName, fixName): |
3201 | - if (packageName, fixName) in _alreadyInstalled: |
3202 | - return |
3203 | - |
3204 | - if (packageName, fixName) == ('twisted', 'filepath_copyTo'): |
3205 | - from twisted.python import filepath |
3206 | - if filepath.FilePath('a') != filepath.FilePath('a'): |
3207 | - from epsilon.hotfixes import filepath_copyTo |
3208 | - filepath_copyTo.install() |
3209 | - elif (packageName, fixName) == ('twisted', 'timeoutmixin_calllater'): |
3210 | - from twisted.protocols import policies |
3211 | - if not hasattr(policies.TimeoutMixin, 'callLater'): |
3212 | - from epsilon.hotfixes import timeoutmixin_calllater |
3213 | - timeoutmixin_calllater.install() |
3214 | - elif (packageName, fixName) == ('twisted', 'delayedcall_seconds'): |
3215 | - from twisted.internet import base |
3216 | - args = inspect.getargs(base.DelayedCall.__init__.func_code)[0] |
3217 | - if 'seconds' not in args: |
3218 | - from epsilon.hotfixes import delayedcall_seconds |
3219 | - delayedcall_seconds.install() |
3220 | - elif (packageName, fixName) == ('twisted', 'deferredgenerator_tfailure'): |
3221 | - from twisted.internet import defer |
3222 | - result = [] |
3223 | - def test(): |
3224 | - d = defer.waitForDeferred(defer.succeed(1)) |
3225 | - yield d |
3226 | - result.append(d.getResult()) |
3227 | - defer.deferredGenerator(test)() |
3228 | - if result == [1]: |
3229 | - from epsilon.hotfixes import deferredgenerator_tfailure |
3230 | - deferredgenerator_tfailure.install() |
3231 | - else: |
3232 | - assert result == [None] |
3233 | - elif (packageName, fixName) == ("twisted", "proto_helpers_stringtransport"): |
3234 | - from twisted.test.proto_helpers import StringTransport |
3235 | - st = StringTransport() |
3236 | - try: |
3237 | - st.write(u'foo') |
3238 | - except TypeError, e: |
3239 | - pass |
3240 | - else: |
3241 | - from epsilon.hotfixes import proto_helpers_stringtransport |
3242 | - proto_helpers_stringtransport.install() |
3243 | - elif (packageName, fixName) == ("twisted", "internet_task_Clock"): |
3244 | - from twisted.internet.task import Clock |
3245 | - from twisted.internet import base |
3246 | - from twisted import version |
3247 | - from epsilon.hotfixes import internet_task_clock |
3248 | - if internet_task_clock.clockIsBroken(): |
3249 | - internet_task_clock.install() |
3250 | - elif (packageName, fixName) == ("twisted", "trial_assertwarns"): |
3251 | - from twisted.trial.unittest import TestCase |
3252 | - if not hasattr(TestCase, "failUnlessWarns"): |
3253 | - from epsilon.hotfixes import trial_assertwarns |
3254 | - trial_assertwarns.install() |
3255 | - elif (packageName, fixName) == ("twisted", "plugin_package_paths"): |
3256 | - try: |
3257 | - from twisted.plugin import pluginPackagePaths |
3258 | - except ImportError: |
3259 | - from epsilon.hotfixes import plugin_package_paths |
3260 | - plugin_package_paths.install() |
3261 | - elif (packageName, fixName) == ("twisted", "loopbackasync_reentrancy"): |
3262 | - # This one is really hard to detect reasonably. Invoking the code |
3263 | - # involves triggering the reactor, which it would be good to avoid. |
3264 | - from twisted import version |
3265 | - if (version.major, version.minor) < (8, 2): |
3266 | - from epsilon.hotfixes import loopbackasync_reentrancy |
3267 | - loopbackasync_reentrancy.install() |
3268 | - else: |
3269 | - raise NoSuchHotfix(packageName, fixName) |
3270 | - |
3271 | - _alreadyInstalled.add((packageName, fixName)) |
3272 | |
3273 | === removed directory 'Epsilon/epsilon/hotfixes' |
3274 | === removed file 'Epsilon/epsilon/hotfixes/__init__.py' |
3275 | === removed file 'Epsilon/epsilon/hotfixes/deferredgenerator_tfailure.py' |
3276 | --- Epsilon/epsilon/hotfixes/deferredgenerator_tfailure.py 2006-05-22 15:35:56 +0000 |
3277 | +++ Epsilon/epsilon/hotfixes/deferredgenerator_tfailure.py 1970-01-01 00:00:00 +0000 |
3278 | @@ -1,59 +0,0 @@ |
3279 | - |
3280 | -from twisted.python import failure |
3281 | -from twisted.internet import defer |
3282 | - |
3283 | -def getResult(self): |
3284 | - if isinstance(self.result, failure.Failure): |
3285 | - self.result.raiseException() |
3286 | - return self.result |
3287 | - |
3288 | - |
3289 | -def _deferGenerator(g, deferred=None): |
3290 | - """ |
3291 | - See L{waitForDeferred}. |
3292 | - """ |
3293 | - result = None |
3294 | - while 1: |
3295 | - if deferred is None: |
3296 | - deferred = defer.Deferred() |
3297 | - try: |
3298 | - result = g.next() |
3299 | - except StopIteration: |
3300 | - deferred.callback(result) |
3301 | - return deferred |
3302 | - except: |
3303 | - deferred.errback() |
3304 | - return deferred |
3305 | - |
3306 | - # Deferred.callback(Deferred) raises an error; we catch this case |
3307 | - # early here and give a nicer error message to the user in case |
3308 | - # they yield a Deferred. Perhaps eventually these semantics may |
3309 | - # change. |
3310 | - if isinstance(result, defer.Deferred): |
3311 | - return defer.fail(TypeError("Yield waitForDeferred(d), not d!")) |
3312 | - |
3313 | - if isinstance(result, defer.waitForDeferred): |
3314 | - waiting = [True, None] |
3315 | - # Pass vars in so they don't get changed going around the loop |
3316 | - def gotResult(r, waiting=waiting, result=result): |
3317 | - result.result = r |
3318 | - if waiting[0]: |
3319 | - waiting[0] = False |
3320 | - waiting[1] = r |
3321 | - else: |
3322 | - _deferGenerator(g, deferred) |
3323 | - result.d.addBoth(gotResult) |
3324 | - if waiting[0]: |
3325 | - # Haven't called back yet, set flag so that we get reinvoked |
3326 | - # and return from the loop |
3327 | - waiting[0] = False |
3328 | - return deferred |
3329 | - result = None # waiting[1] |
3330 | - |
3331 | - |
3332 | -def install(): |
3333 | - getResult.__module__ = 'twisted.internet.defer' |
3334 | - defer.waitForDeferred.getResult = getResult |
3335 | - |
3336 | - _deferGenerator.__module__ = 'twisted.internet.defer' |
3337 | - defer._deferGenerator = _deferGenerator |
3338 | |
3339 | === removed file 'Epsilon/epsilon/hotfixes/delayedcall_seconds.py' |
3340 | --- Epsilon/epsilon/hotfixes/delayedcall_seconds.py 2006-05-19 15:23:46 +0000 |
3341 | +++ Epsilon/epsilon/hotfixes/delayedcall_seconds.py 1970-01-01 00:00:00 +0000 |
3342 | @@ -1,160 +0,0 @@ |
3343 | - |
3344 | -import traceback |
3345 | - |
3346 | -from zope.interface import implements |
3347 | - |
3348 | -from twisted.persisted import styles |
3349 | -from twisted.internet.interfaces import IDelayedCall |
3350 | -from twisted.internet import error, base |
3351 | -from twisted.python import reflect |
3352 | - |
3353 | -class DelayedCall(styles.Ephemeral): |
3354 | - |
3355 | - implements(IDelayedCall) |
3356 | - # enable .debug to record creator call stack, and it will be logged if |
3357 | - # an exception occurs while the function is being run |
3358 | - debug = False |
3359 | - _str = None |
3360 | - |
3361 | - def __init__(self, time, func, args, kw, cancel, reset, seconds=None): |
3362 | - self.time, self.func, self.args, self.kw = time, func, args, kw |
3363 | - self.resetter = reset |
3364 | - self.canceller = cancel |
3365 | - self.seconds = seconds |
3366 | - self.cancelled = self.called = 0 |
3367 | - self.delayed_time = 0 |
3368 | - if self.debug: |
3369 | - self.creator = traceback.format_stack()[:-2] |
3370 | - |
3371 | - def getTime(self): |
3372 | - """Return the time at which this call will fire |
3373 | - |
3374 | - @rtype: C{float} |
3375 | - @return: The number of seconds after the epoch at which this call is |
3376 | - scheduled to be made. |
3377 | - """ |
3378 | - return self.time + self.delayed_time |
3379 | - |
3380 | - def cancel(self): |
3381 | - """Unschedule this call |
3382 | - |
3383 | - @raise AlreadyCancelled: Raised if this call has already been |
3384 | - unscheduled. |
3385 | - |
3386 | - @raise AlreadyCalled: Raised if this call has already been made. |
3387 | - """ |
3388 | - if self.cancelled: |
3389 | - raise error.AlreadyCancelled |
3390 | - elif self.called: |
3391 | - raise error.AlreadyCalled |
3392 | - else: |
3393 | - self.canceller(self) |
3394 | - self.cancelled = 1 |
3395 | - if self.debug: |
3396 | - self._str = str(self) |
3397 | - del self.func, self.args, self.kw |
3398 | - |
3399 | - def reset(self, secondsFromNow): |
3400 | - """Reschedule this call for a different time |
3401 | - |
3402 | - @type secondsFromNow: C{float} |
3403 | - @param secondsFromNow: The number of seconds from the time of the |
3404 | - C{reset} call at which this call will be scheduled. |
3405 | - |
3406 | - @raise AlreadyCancelled: Raised if this call has been cancelled. |
3407 | - @raise AlreadyCalled: Raised if this call has already been made. |
3408 | - """ |
3409 | - if self.cancelled: |
3410 | - raise error.AlreadyCancelled |
3411 | - elif self.called: |
3412 | - raise error.AlreadyCalled |
3413 | - else: |
3414 | - if self.seconds is None: |
3415 | - new_time = base.seconds() + secondsFromNow |
3416 | - else: |
3417 | - new_time = self.seconds() + secondsFromNow |
3418 | - if new_time < self.time: |
3419 | - self.delayed_time = 0 |
3420 | - self.time = new_time |
3421 | - self.resetter(self) |
3422 | - else: |
3423 | - self.delayed_time = new_time - self.time |
3424 | - |
3425 | - def delay(self, secondsLater): |
3426 | - """Reschedule this call for a later time |
3427 | - |
3428 | - @type secondsLater: C{float} |
3429 | - @param secondsLater: The number of seconds after the originally |
3430 | - scheduled time for which to reschedule this call. |
3431 | - |
3432 | - @raise AlreadyCancelled: Raised if this call has been cancelled. |
3433 | - @raise AlreadyCalled: Raised if this call has already been made. |
3434 | - """ |
3435 | - if self.cancelled: |
3436 | - raise error.AlreadyCancelled |
3437 | - elif self.called: |
3438 | - raise error.AlreadyCalled |
3439 | - else: |
3440 | - self.delayed_time += secondsLater |
3441 | - if self.delayed_time < 0: |
3442 | - self.activate_delay() |
3443 | - self.resetter(self) |
3444 | - |
3445 | - def activate_delay(self): |
3446 | - self.time += self.delayed_time |
3447 | - self.delayed_time = 0 |
3448 | - |
3449 | - def active(self): |
3450 | - """Determine whether this call is still pending |
3451 | - |
3452 | - @rtype: C{bool} |
3453 | - @return: True if this call has not yet been made or cancelled, |
3454 | - False otherwise. |
3455 | - """ |
3456 | - return not (self.cancelled or self.called) |
3457 | - |
3458 | - def __le__(self, other): |
3459 | - return self.time <= other.time |
3460 | - |
3461 | - def __str__(self): |
3462 | - if self._str is not None: |
3463 | - return self._str |
3464 | - if hasattr(self, 'func'): |
3465 | - if hasattr(self.func, 'func_name'): |
3466 | - func = self.func.func_name |
3467 | - if hasattr(self.func, 'im_class'): |
3468 | - func = self.func.im_class.__name__ + '.' + func |
3469 | - else: |
3470 | - func = reflect.safe_repr(self.func) |
3471 | - else: |
3472 | - func = None |
3473 | - |
3474 | - if self.seconds is None: |
3475 | - now = base.seconds() |
3476 | - else: |
3477 | - now = self.seconds() |
3478 | - L = ["<DelayedCall %s [%ss] called=%s cancelled=%s" % ( |
3479 | - id(self), self.time - now, self.called, self.cancelled)] |
3480 | - if func is not None: |
3481 | - L.extend((" ", func, "(")) |
3482 | - if self.args: |
3483 | - L.append(", ".join([reflect.safe_repr(e) for e in self.args])) |
3484 | - if self.kw: |
3485 | - L.append(", ") |
3486 | - if self.kw: |
3487 | - L.append(", ".join(['%s=%s' % (k, reflect.safe_repr(v)) for (k, v) in self.kw.iteritems()])) |
3488 | - L.append(")") |
3489 | - |
3490 | - if self.debug: |
3491 | - L.append("\n\ntraceback at creation: \n\n%s" % (' '.join(self.creator))) |
3492 | - L.append('>') |
3493 | - |
3494 | - return "".join(L) |
3495 | - |
3496 | - |
3497 | -def install(): |
3498 | - global DelayedCall |
3499 | - |
3500 | - base.DelayedCall.__dict__ = DelayedCall.__dict__ |
3501 | - base.DelayedCall.__dict__['__module__'] = 'twisted.internet.base' |
3502 | - DelayedCall = base.DelayedCall |
3503 | |
3504 | === removed file 'Epsilon/epsilon/hotfixes/filepath_copyTo.py' |
3505 | --- Epsilon/epsilon/hotfixes/filepath_copyTo.py 2005-12-06 07:36:12 +0000 |
3506 | +++ Epsilon/epsilon/hotfixes/filepath_copyTo.py 1970-01-01 00:00:00 +0000 |
3507 | @@ -1,403 +0,0 @@ |
3508 | -# -*- test-case-name: twisted.test.test_paths -*- |
3509 | -# Copyright (c) 2001-2004 Twisted Matrix Laboratories. |
3510 | -# See LICENSE for details. |
3511 | - |
3512 | -from __future__ import generators |
3513 | - |
3514 | -import os |
3515 | -import errno |
3516 | -import base64 |
3517 | -import random |
3518 | -import sha |
3519 | - |
3520 | -from os.path import isabs, exists, normpath, abspath, splitext |
3521 | -from os.path import basename, dirname |
3522 | -from os.path import join as joinpath |
3523 | -from os import sep as slash |
3524 | -from os import listdir, utime, stat |
3525 | -from os import remove |
3526 | - |
3527 | -from stat import ST_MODE, ST_MTIME, ST_ATIME, ST_CTIME, ST_SIZE |
3528 | - |
3529 | -from stat import S_ISREG, S_ISDIR, S_ISLNK |
3530 | - |
3531 | -try: |
3532 | - from os.path import islink |
3533 | -except ImportError: |
3534 | - def islink(path): |
3535 | - return False |
3536 | - |
3537 | -try: |
3538 | - from os import urandom as randomBytes |
3539 | -except ImportError: |
3540 | - def randomBytes(n): |
3541 | - randomData = [random.randrange(256) for n in xrange(n)] |
3542 | - return ''.join(map(chr, randomData)) |
3543 | - |
3544 | -try: |
3545 | - from base64 import urlsafe_b64encode as armor |
3546 | -except ImportError: |
3547 | - def armor(s): |
3548 | - return s.encode('hex') |
3549 | - |
3550 | -class InsecurePath(Exception): |
3551 | - pass |
3552 | - |
3553 | -def _secureEnoughString(): |
3554 | - """ |
3555 | - Create a pseudorandom, 16-character string for use in secure filenames. |
3556 | - """ |
3557 | - return armor(sha.new(randomBytes(64)).digest())[:16] |
3558 | - |
3559 | -class FilePath: |
3560 | - """I am a path on the filesystem that only permits 'downwards' access. |
3561 | - |
3562 | - Instantiate me with a pathname (for example, |
3563 | - FilePath('/home/myuser/public_html')) and I will attempt to only provide |
3564 | - access to files which reside inside that path. I may be a path to a file, |
3565 | - a directory, or a file which does not exist. |
3566 | - |
3567 | - The correct way to use me is to instantiate me, and then do ALL filesystem |
3568 | - access through me. In other words, do not import the 'os' module; if you |
3569 | - need to open a file, call my 'open' method. If you need to list a |
3570 | - directory, call my 'path' method. |
3571 | - |
3572 | - Even if you pass me a relative path, I will convert that to an absolute |
3573 | - path internally. |
3574 | - |
3575 | - @type alwaysCreate: C{bool} |
3576 | - @ivar alwaysCreate: When opening this file, only succeed if the file does not |
3577 | - already exist. |
3578 | - """ |
3579 | - |
3580 | - # __slots__ = 'path abs'.split() |
3581 | - |
3582 | - statinfo = None |
3583 | - |
3584 | - def __init__(self, path, alwaysCreate=False): |
3585 | - self.path = abspath(path) |
3586 | - self.alwaysCreate = alwaysCreate |
3587 | - |
3588 | - def __getstate__(self): |
3589 | - d = self.__dict__.copy() |
3590 | - if d.has_key('statinfo'): |
3591 | - del d['statinfo'] |
3592 | - return d |
3593 | - |
3594 | - def child(self, path): |
3595 | - norm = normpath(path) |
3596 | - if slash in norm: |
3597 | - raise InsecurePath("%r contains one or more directory separators" % (path,)) |
3598 | - newpath = abspath(joinpath(self.path, norm)) |
3599 | - if not newpath.startswith(self.path): |
3600 | - raise InsecurePath("%r is not a child of %s" % (newpath, self.path)) |
3601 | - return self.clonePath(newpath) |
3602 | - |
3603 | - def preauthChild(self, path): |
3604 | - """ |
3605 | - Use me if `path' might have slashes in it, but you know they're safe. |
3606 | - |
3607 | - (NOT slashes at the beginning. It still needs to be a _child_). |
3608 | - """ |
3609 | - newpath = abspath(joinpath(self.path, normpath(path))) |
3610 | - if not newpath.startswith(self.path): |
3611 | - raise InsecurePath("%s is not a child of %s" % (newpath, self.path)) |
3612 | - return self.clonePath(newpath) |
3613 | - |
3614 | - def childSearchPreauth(self, *paths): |
3615 | - """Return my first existing child with a name in 'paths'. |
3616 | - |
3617 | - paths is expected to be a list of *pre-secured* path fragments; in most |
3618 | - cases this will be specified by a system administrator and not an |
3619 | - arbitrary user. |
3620 | - |
3621 | - If no appropriately-named children exist, this will return None. |
3622 | - """ |
3623 | - p = self.path |
3624 | - for child in paths: |
3625 | - jp = joinpath(p, child) |
3626 | - if exists(jp): |
3627 | - return self.clonePath(jp) |
3628 | - |
3629 | - def siblingExtensionSearch(self, *exts): |
3630 | - """Attempt to return a path with my name, given multiple possible |
3631 | - extensions. |
3632 | - |
3633 | - Each extension in exts will be tested and the first path which exists |
3634 | - will be returned. If no path exists, None will be returned. If '' is |
3635 | - in exts, then if the file referred to by this path exists, 'self' will |
3636 | - be returned. |
3637 | - |
3638 | - The extension '*' has a magic meaning, which means "any path that |
3639 | - begins with self.path+'.' is acceptable". |
3640 | - """ |
3641 | - p = self.path |
3642 | - for ext in exts: |
3643 | - if not ext and self.exists(): |
3644 | - return self |
3645 | - if ext == '*': |
3646 | - basedot = basename(p)+'.' |
3647 | - for fn in listdir(dirname(p)): |
3648 | - if fn.startswith(basedot): |
3649 | - return self.clonePath(joinpath(dirname(p), fn)) |
3650 | - p2 = p + ext |
3651 | - if exists(p2): |
3652 | - return self.clonePath(p2) |
3653 | - |
3654 | - def siblingExtension(self, ext): |
3655 | - return self.clonePath(self.path+ext) |
3656 | - |
3657 | - def open(self, mode='r'): |
3658 | - if self.alwaysCreate: |
3659 | - assert 'a' not in mode, "Appending not supported when alwaysCreate == True" |
3660 | - return self.create() |
3661 | - return open(self.path, mode+'b') |
3662 | - |
3663 | - # stat methods below |
3664 | - |
3665 | - def restat(self, reraise=True): |
3666 | - try: |
3667 | - self.statinfo = stat(self.path) |
3668 | - except OSError: |
3669 | - self.statinfo = 0 |
3670 | - if reraise: |
3671 | - raise |
3672 | - |
3673 | - def getsize(self): |
3674 | - st = self.statinfo |
3675 | - if not st: |
3676 | - self.restat() |
3677 | - st = self.statinfo |
3678 | - return st[ST_SIZE] |
3679 | - |
3680 | - def getmtime(self): |
3681 | - st = self.statinfo |
3682 | - if not st: |
3683 | - self.restat() |
3684 | - st = self.statinfo |
3685 | - return st[ST_MTIME] |
3686 | - |
3687 | - def getctime(self): |
3688 | - st = self.statinfo |
3689 | - if not st: |
3690 | - self.restat() |
3691 | - st = self.statinfo |
3692 | - return st[ST_CTIME] |
3693 | - |
3694 | - def getatime(self): |
3695 | - st = self.statinfo |
3696 | - if not st: |
3697 | - self.restat() |
3698 | - st = self.statinfo |
3699 | - return st[ST_ATIME] |
3700 | - |
3701 | - def exists(self): |
3702 | - if self.statinfo: |
3703 | - return True |
3704 | - elif self.statinfo is None: |
3705 | - self.restat(False) |
3706 | - return self.exists() |
3707 | - else: |
3708 | - return False |
3709 | - |
3710 | - def isdir(self): |
3711 | - st = self.statinfo |
3712 | - if not st: |
3713 | - self.restat(False) |
3714 | - st = self.statinfo |
3715 | - if not st: |
3716 | - return False |
3717 | - return S_ISDIR(st[ST_MODE]) |
3718 | - |
3719 | - def isfile(self): |
3720 | - st = self.statinfo |
3721 | - if not st: |
3722 | - self.restat(False) |
3723 | - st = self.statinfo |
3724 | - if not st: |
3725 | - return False |
3726 | - return S_ISREG(st[ST_MODE]) |
3727 | - |
3728 | - def islink(self): |
3729 | - st = self.statinfo |
3730 | - if not st: |
3731 | - self.restat(False) |
3732 | - st = self.statinfo |
3733 | - if not st: |
3734 | - return False |
3735 | - return S_ISLNK(st[ST_MODE]) |
3736 | - |
3737 | - def isabs(self): |
3738 | - return isabs(self.path) |
3739 | - |
3740 | - def listdir(self): |
3741 | - return listdir(self.path) |
3742 | - |
3743 | - def splitext(self): |
3744 | - return splitext(self.path) |
3745 | - |
3746 | - def __repr__(self): |
3747 | - return 'FilePath(%r)' % self.path |
3748 | - |
3749 | - def touch(self): |
3750 | - try: |
3751 | - self.open('a').close() |
3752 | - except IOError: |
3753 | - pass |
3754 | - utime(self.path, None) |
3755 | - |
3756 | - def remove(self): |
3757 | - if self.isdir(): |
3758 | - for child in self.children(): |
3759 | - child.remove() |
3760 | - os.rmdir(self.path) |
3761 | - else: |
3762 | - os.remove(self.path) |
3763 | - self.restat(False) |
3764 | - |
3765 | - def makedirs(self): |
3766 | - return os.makedirs(self.path) |
3767 | - |
3768 | - def globChildren(self, pattern): |
3769 | - """ |
3770 | - Assuming I am representing a directory, return a list of |
3771 | - FilePaths representing my children that match the given |
3772 | - pattern. |
3773 | - """ |
3774 | - import glob |
3775 | - path = self.path[-1] == '/' and self.path + pattern or slash.join([self.path, pattern]) |
3776 | - return map(self.clonePath, glob.glob(path)) |
3777 | - |
3778 | - def basename(self): |
3779 | - return basename(self.path) |
3780 | - |
3781 | - def dirname(self): |
3782 | - return dirname(self.path) |
3783 | - |
3784 | - def parent(self): |
3785 | - return self.clonePath(self.dirname()) |
3786 | - |
3787 | - def setContent(self, content, ext='.new'): |
3788 | - sib = self.siblingExtension(ext) |
3789 | - sib.open('w').write(content) |
3790 | - os.rename(sib.path, self.path) |
3791 | - |
3792 | - def getContent(self): |
3793 | - return self.open().read() |
3794 | - |
3795 | - # new in 2.2.0 |
3796 | - |
3797 | - def __cmp__(self, other): |
3798 | - if not isinstance(other, FilePath): |
3799 | - return NotImplemented |
3800 | - return cmp(self.path, other.path) |
3801 | - |
3802 | - def createDirectory(self): |
3803 | - os.mkdir(self.path) |
3804 | - |
3805 | - def requireCreate(self, val=1): |
3806 | - self.alwaysCreate = val |
3807 | - |
3808 | - def create(self): |
3809 | - """Exclusively create a file, only if this file previously did not exist. |
3810 | - """ |
3811 | - fdint = os.open(self.path, (os.O_EXCL | |
3812 | - os.O_CREAT | |
3813 | - os.O_RDWR)) |
3814 | - |
3815 | - # XXX TODO: 'name' attribute of returned files is not mutable or |
3816 | - # settable via fdopen, so this file is slighly less functional than the |
3817 | - # one returned from 'open' by default. send a patch to Python... |
3818 | - |
3819 | - return os.fdopen(fdint, 'w+b') |
3820 | - |
3821 | - def temporarySibling(self): |
3822 | - """ |
3823 | - Create a path naming a temporary sibling of this path in a secure fashion. |
3824 | - """ |
3825 | - sib = self.parent().child(_secureEnoughString() + self.basename()) |
3826 | - sib.requireCreate() |
3827 | - return sib |
3828 | - |
3829 | - def children(self): |
3830 | - return map(self.child, self.listdir()) |
3831 | - |
3832 | - def walk(self): |
3833 | - yield self |
3834 | - if self.isdir(): |
3835 | - for c in self.children(): |
3836 | - for subc in c.walk(): |
3837 | - yield subc |
3838 | - |
3839 | - _chunkSize = 2 ** 2 ** 2 ** 2 |
3840 | - |
3841 | - def copyTo(self, destination): |
3842 | - # XXX TODO: *thorough* audit and documentation of the exact desired |
3843 | - # semantics of this code. Right now the behavior of existent |
3844 | - # destination symlinks is convenient, and quite possibly correct, but |
3845 | - # its security properties need to be explained. |
3846 | - if self.isdir(): |
3847 | - if not destination.exists(): |
3848 | - destination.createDirectory() |
3849 | - for child in self.children(): |
3850 | - destChild = destination.child(child.basename()) |
3851 | - child.copyTo(destChild) |
3852 | - elif self.isfile(): |
3853 | - writefile = destination.open('w') |
3854 | - readfile = self.open() |
3855 | - while 1: |
3856 | - # XXX TODO: optionally use os.open, os.read and O_DIRECT and |
3857 | - # use os.fstatvfs to determine chunk sizes and make |
3858 | - # *****sure**** copy is page-atomic; the following is good |
3859 | - # enough for 99.9% of everybody and won't take a week to audit |
3860 | - # though. |
3861 | - chunk = readfile.read(self._chunkSize) |
3862 | - writefile.write(chunk) |
3863 | - if len(chunk) < self._chunkSize: |
3864 | - break |
3865 | - writefile.close() |
3866 | - readfile.close() |
3867 | - else: |
3868 | - # If you see the following message because you want to copy |
3869 | - # symlinks, fifos, block devices, character devices, or unix |
3870 | - # sockets, please feel free to add support to do sensible things in |
3871 | - # reaction to those types! |
3872 | - raise NotImplementedError( |
3873 | - "Only copying of files and directories supported") |
3874 | - |
3875 | - def moveTo(self, destination): |
3876 | - try: |
3877 | - os.rename(self.path, destination.path) |
3878 | - self.restat(False) |
3879 | - except OSError, ose: |
3880 | - if ose.errno == errno.EXDEV: |
3881 | - # man 2 rename, ubuntu linux 5.10 "breezy": |
3882 | - |
3883 | - # oldpath and newpath are not on the same mounted filesystem. |
3884 | - # (Linux permits a filesystem to be mounted at multiple |
3885 | - # points, but rename(2) does not work across different mount |
3886 | - # points, even if the same filesystem is mounted on both.) |
3887 | - |
3888 | - # that means it's time to copy trees of directories! |
3889 | - secsib = destination.secureSibling() |
3890 | - self.copyTo(secsib) # slow |
3891 | - secsib.moveTo(destination) # visible |
3892 | - |
3893 | - # done creating new stuff. let's clean me up. |
3894 | - mysecsib = self.secureSibling() |
3895 | - self.moveTo(mysecsib) # visible |
3896 | - mysecsib.remove() # slow |
3897 | - else: |
3898 | - raise |
3899 | - |
3900 | - |
3901 | -FilePath.clonePath = FilePath |
3902 | - |
3903 | - |
3904 | -def install(): |
3905 | - global FilePath |
3906 | - |
3907 | - from twisted.python import filepath |
3908 | - filepath.FilePath.__dict__ = FilePath.__dict__ |
3909 | - filepath.FilePath.__dict__['__module__'] = 'twisted.python.filepath' |
3910 | - FilePath = filepath.FilePath |
3911 | |
3912 | === removed file 'Epsilon/epsilon/hotfixes/internet_task_clock.py' |
3913 | --- Epsilon/epsilon/hotfixes/internet_task_clock.py 2007-06-22 20:06:46 +0000 |
3914 | +++ Epsilon/epsilon/hotfixes/internet_task_clock.py 1970-01-01 00:00:00 +0000 |
3915 | @@ -1,38 +0,0 @@ |
3916 | -""" |
3917 | -Fix from Twisted r20480. |
3918 | -""" |
3919 | -from twisted.internet.task import Clock |
3920 | -from twisted.internet import base |
3921 | - |
3922 | -def callLater(self, when, what, *a, **kw): |
3923 | - """ |
3924 | - Copied from twisted.internet.task.Clock, r20480. Fixes the bug |
3925 | - where the wrong DelayedCall would sometimes be returned. |
3926 | - """ |
3927 | - dc = base.DelayedCall(self.seconds() + when, |
3928 | - what, a, kw, |
3929 | - self.calls.remove, |
3930 | - lambda c: None, |
3931 | - self.seconds) |
3932 | - self.calls.append(dc) |
3933 | - self.calls.sort(lambda a, b: cmp(a.getTime(), b.getTime())) |
3934 | - return dc |
3935 | - |
3936 | -def clockIsBroken(): |
3937 | - """ |
3938 | - Returns whether twisted.internet.task.Clock has the bug that |
3939 | - returns the wrong DelayedCall or not. |
3940 | - """ |
3941 | - clock = Clock() |
3942 | - dc1 = clock.callLater(10, lambda: None) |
3943 | - dc2 = clock.callLater(1, lambda: None) |
3944 | - if dc1 is dc2: |
3945 | - return True |
3946 | - else: |
3947 | - return False |
3948 | - |
3949 | -def install(): |
3950 | - """ |
3951 | - Insert the fixed callLater method. |
3952 | - """ |
3953 | - Clock.callLater = callLater |
3954 | |
3955 | === removed file 'Epsilon/epsilon/hotfixes/loopbackasync_reentrancy.py' |
3956 | --- Epsilon/epsilon/hotfixes/loopbackasync_reentrancy.py 2008-08-28 14:40:39 +0000 |
3957 | +++ Epsilon/epsilon/hotfixes/loopbackasync_reentrancy.py 1970-01-01 00:00:00 +0000 |
3958 | @@ -1,26 +0,0 @@ |
3959 | - |
3960 | -""" |
3961 | -Fix from Twisted r23970 |
3962 | -""" |
3963 | - |
3964 | -from twisted.internet.task import deferLater |
3965 | -from twisted.protocols.loopback import _loopbackAsyncBody |
3966 | - |
3967 | -def _loopbackAsyncContinue(ignored, server, serverToClient, client, clientToServer): |
3968 | - # Clear the Deferred from each message queue, since it has already fired |
3969 | - # and cannot be used again. |
3970 | - clientToServer._notificationDeferred = serverToClient._notificationDeferred = None |
3971 | - |
3972 | - # Schedule some more byte-pushing to happen. This isn't done |
3973 | - # synchronously because no actual transport can re-enter dataReceived as |
3974 | - # a result of calling write, and doing this synchronously could result |
3975 | - # in that. |
3976 | - from twisted.internet import reactor |
3977 | - return deferLater( |
3978 | - reactor, 0, |
3979 | - _loopbackAsyncBody, server, serverToClient, client, clientToServer) |
3980 | - |
3981 | - |
3982 | -def install(): |
3983 | - from twisted.protocols import loopback |
3984 | - loopback._loopbackAsyncContinue = _loopbackAsyncContinue |
3985 | |
3986 | === removed file 'Epsilon/epsilon/hotfixes/plugin_package_paths.py' |
3987 | --- Epsilon/epsilon/hotfixes/plugin_package_paths.py 2008-08-07 14:03:07 +0000 |
3988 | +++ Epsilon/epsilon/hotfixes/plugin_package_paths.py 1970-01-01 00:00:00 +0000 |
3989 | @@ -1,42 +0,0 @@ |
3990 | -# Copyright (c) 2007 Twisted Matrix Laboratories. |
3991 | -# Copyright (c) 2008 Divmod. |
3992 | -# See LICENSE for details. |
3993 | - |
3994 | - |
3995 | - |
3996 | -import sys, os |
3997 | - |
3998 | -def pluginPackagePaths(name): |
3999 | - """ |
4000 | - Return a list of additional directories which should be searched for |
4001 | - modules to be included as part of the named plugin package. |
4002 | - |
4003 | - @type name: C{str} |
4004 | - @param name: The fully-qualified Python name of a plugin package, eg |
4005 | - C{'twisted.plugins'}. |
4006 | - |
4007 | - @rtype: C{list} of C{str} |
4008 | - @return: The absolute paths to other directories which may contain plugin |
4009 | - modules for the named plugin package. |
4010 | - """ |
4011 | - package = name.split('.') |
4012 | - # Note that this may include directories which do not exist. It may be |
4013 | - # preferable to remove such directories at this point, rather than allow |
4014 | - # them to be searched later on. |
4015 | - # |
4016 | - # Note as well that only '__init__.py' will be considered to make a |
4017 | - # directory a package (and thus exclude it from this list). This means |
4018 | - # that if you create a master plugin package which has some other kind of |
4019 | - # __init__ (eg, __init__.pyc) it will be incorrectly treated as a |
4020 | - # supplementary plugin directory. |
4021 | - return [ |
4022 | - os.path.abspath(os.path.join(x, *package)) |
4023 | - for x |
4024 | - in sys.path |
4025 | - if |
4026 | - not os.path.exists(os.path.join(x, *package + ['__init__.py']))] |
4027 | - |
4028 | - |
4029 | -def install(): |
4030 | - import twisted.plugin |
4031 | - twisted.plugin.pluginPackagePaths = pluginPackagePaths |
4032 | |
4033 | === removed file 'Epsilon/epsilon/hotfixes/proto_helpers_stringtransport.py' |
4034 | --- Epsilon/epsilon/hotfixes/proto_helpers_stringtransport.py 2006-06-22 20:48:07 +0000 |
4035 | +++ Epsilon/epsilon/hotfixes/proto_helpers_stringtransport.py 1970-01-01 00:00:00 +0000 |
4036 | @@ -1,11 +0,0 @@ |
4037 | -from twisted.test import proto_helpers |
4038 | - |
4039 | -class StringTransport: |
4040 | - |
4041 | - def write(self, data): |
4042 | - if isinstance(data, unicode): # no, really, I mean it |
4043 | - raise TypeError("Data must not be unicode") |
4044 | - self.io.write(data) |
4045 | - |
4046 | -def install(): |
4047 | - proto_helpers.StringTransport.__dict__['write'] = StringTransport.__dict__['write'] |
4048 | |
4049 | === removed file 'Epsilon/epsilon/hotfixes/timeoutmixin_calllater.py' |
4050 | --- Epsilon/epsilon/hotfixes/timeoutmixin_calllater.py 2006-05-19 15:23:46 +0000 |
4051 | +++ Epsilon/epsilon/hotfixes/timeoutmixin_calllater.py 1970-01-01 00:00:00 +0000 |
4052 | @@ -1,60 +0,0 @@ |
4053 | - |
4054 | -from twisted.internet import reactor |
4055 | - |
4056 | -class TimeoutMixin: |
4057 | - """Mixin for protocols which wish to timeout connections |
4058 | - |
4059 | - @cvar timeOut: The number of seconds after which to timeout the connection. |
4060 | - """ |
4061 | - timeOut = None |
4062 | - |
4063 | - __timeoutCall = None |
4064 | - |
4065 | - def callLater(self, period, func): |
4066 | - return reactor.callLater(period, func) |
4067 | - |
4068 | - |
4069 | - def resetTimeout(self): |
4070 | - """Reset the timeout count down""" |
4071 | - if self.__timeoutCall is not None and self.timeOut is not None: |
4072 | - self.__timeoutCall.reset(self.timeOut) |
4073 | - |
4074 | - def setTimeout(self, period): |
4075 | - """Change the timeout period |
4076 | - |
4077 | - @type period: C{int} or C{NoneType} |
4078 | - @param period: The period, in seconds, to change the timeout to, or |
4079 | - C{None} to disable the timeout. |
4080 | - """ |
4081 | - prev = self.timeOut |
4082 | - self.timeOut = period |
4083 | - |
4084 | - if self.__timeoutCall is not None: |
4085 | - if period is None: |
4086 | - self.__timeoutCall.cancel() |
4087 | - self.__timeoutCall = None |
4088 | - else: |
4089 | - self.__timeoutCall.reset(period) |
4090 | - elif period is not None: |
4091 | - self.__timeoutCall = self.callLater(period, self.__timedOut) |
4092 | - |
4093 | - return prev |
4094 | - |
4095 | - def __timedOut(self): |
4096 | - self.__timeoutCall = None |
4097 | - self.timeoutConnection() |
4098 | - |
4099 | - def timeoutConnection(self): |
4100 | - """Called when the connection times out. |
4101 | - Override to define behavior other than dropping the connection. |
4102 | - """ |
4103 | - self.transport.loseConnection() |
4104 | - |
4105 | - |
4106 | -def install(): |
4107 | - global TimeoutMixin |
4108 | - |
4109 | - from twisted.protocols import policies |
4110 | - policies.TimeoutMixin.__dict__ = TimeoutMixin.__dict__ |
4111 | - policies.TimeoutMixin.__dict__['module'] = 'twisted.protocols.policies' |
4112 | - TimeoutMixin = policies.TimeoutMixin |
4113 | |
4114 | === removed file 'Epsilon/epsilon/hotfixes/trial_assertwarns.py' |
4115 | --- Epsilon/epsilon/hotfixes/trial_assertwarns.py 2008-05-13 19:40:37 +0000 |
4116 | +++ Epsilon/epsilon/hotfixes/trial_assertwarns.py 1970-01-01 00:00:00 +0000 |
4117 | @@ -1,61 +0,0 @@ |
4118 | - |
4119 | -""" |
4120 | -failUnlessWarns assertion from twisted.trial in Twisted 8.0. |
4121 | -""" |
4122 | - |
4123 | -import warnings |
4124 | - |
4125 | -def failUnlessWarns(self, category, message, filename, f, |
4126 | - *args, **kwargs): |
4127 | - """ |
4128 | - Fail if the given function doesn't generate the specified warning when |
4129 | - called. It calls the function, checks the warning, and forwards the |
4130 | - result of the function if everything is fine. |
4131 | - |
4132 | - @param category: the category of the warning to check. |
4133 | - @param message: the output message of the warning to check. |
4134 | - @param filename: the filename where the warning should come from. |
4135 | - @param f: the function which is supposed to generate the warning. |
4136 | - @type f: any callable. |
4137 | - @param args: the arguments to C{f}. |
4138 | - @param kwargs: the keywords arguments to C{f}. |
4139 | - |
4140 | - @return: the result of the original function C{f}. |
4141 | - """ |
4142 | - warningsShown = [] |
4143 | - def warnExplicit(*args): |
4144 | - warningsShown.append(args) |
4145 | - |
4146 | - origExplicit = warnings.warn_explicit |
4147 | - try: |
4148 | - warnings.warn_explicit = warnExplicit |
4149 | - result = f(*args, **kwargs) |
4150 | - finally: |
4151 | - warnings.warn_explicit = origExplicit |
4152 | - |
4153 | - if not warningsShown: |
4154 | - self.fail("No warnings emitted") |
4155 | - first = warningsShown[0] |
4156 | - for other in warningsShown[1:]: |
4157 | - if other[:2] != first[:2]: |
4158 | - self.fail("Can't handle different warnings") |
4159 | - gotMessage, gotCategory, gotFilename, lineno = first[:4] |
4160 | - self.assertEqual(gotMessage, message) |
4161 | - self.assertIdentical(gotCategory, category) |
4162 | - |
4163 | - # Use starts with because of .pyc/.pyo issues. |
4164 | - self.failUnless( |
4165 | - filename.startswith(gotFilename), |
4166 | - 'Warning in %r, expected %r' % (gotFilename, filename)) |
4167 | - |
4168 | - # It would be nice to be able to check the line number as well, but |
4169 | - # different configurations actually end up reporting different line |
4170 | - # numbers (generally the variation is only 1 line, but that's enough |
4171 | - # to fail the test erroneously...). |
4172 | - # self.assertEqual(lineno, xxx) |
4173 | - |
4174 | - return result |
4175 | - |
4176 | -def install(): |
4177 | - from twisted.trial.unittest import TestCase |
4178 | - TestCase.failUnlessWarns = TestCase.assertWarns = failUnlessWarns |
4179 | |
4180 | === removed file 'Epsilon/epsilon/iepsilon.py' |
4181 | --- Epsilon/epsilon/iepsilon.py 2008-11-10 20:26:55 +0000 |
4182 | +++ Epsilon/epsilon/iepsilon.py 1970-01-01 00:00:00 +0000 |
4183 | @@ -1,25 +0,0 @@ |
4184 | -# Copyright (c) 2008 Divmod. See LICENSE for details. |
4185 | - |
4186 | -""" |
4187 | -Epsilon interfaces. |
4188 | -""" |
4189 | -from zope.interface import Attribute |
4190 | - |
4191 | -from twisted.cred.credentials import ICredentials |
4192 | - |
4193 | - |
4194 | -class IOneTimePad(ICredentials): |
4195 | - """ |
4196 | - A type of opaque credential for authenticating users, which can be used |
4197 | - only a single time. |
4198 | - |
4199 | - This interface should also be responsible for authenticating. See #2784. |
4200 | - """ |
4201 | - padValue = Attribute( |
4202 | - """ |
4203 | - C{str} giving the value of the one-time pad. The value will be |
4204 | - compared by a L{twisted.cred.checkers.ICredentialsChecker} (e.g. |
4205 | - L{epsilon.ampauth.OneTimePadChecker}) against all valid one-time pads. |
4206 | - If there is a match, login will be successful and the pad will be |
4207 | - invalidated (further attempts to use it will fail). |
4208 | - """) |
4209 | |
4210 | === removed file 'Epsilon/epsilon/juice.py' |
4211 | --- Epsilon/epsilon/juice.py 2009-07-06 11:51:08 +0000 |
4212 | +++ Epsilon/epsilon/juice.py 1970-01-01 00:00:00 +0000 |
4213 | @@ -1,1009 +0,0 @@ |
4214 | -# -*- test-case-name: epsilon.test.test_juice -*- |
4215 | -# Copyright 2005 Divmod, Inc. See LICENSE file for details |
4216 | - |
4217 | -__metaclass__ = type |
4218 | - |
4219 | -import warnings, pprint |
4220 | - |
4221 | -from twisted.internet.main import CONNECTION_LOST |
4222 | -from twisted.internet.defer import Deferred, maybeDeferred, fail |
4223 | -from twisted.internet.protocol import ServerFactory, ClientFactory |
4224 | -from twisted.internet.ssl import Certificate |
4225 | -from twisted.python.failure import Failure |
4226 | -from twisted.python import log, filepath |
4227 | - |
4228 | -from epsilon.liner import LineReceiver |
4229 | -from epsilon import extime |
4230 | - |
4231 | -ASK = '_ask' |
4232 | -ANSWER = '_answer' |
4233 | -COMMAND = '_command' |
4234 | -ERROR = '_error' |
4235 | -ERROR_CODE = '_error_code' |
4236 | -ERROR_DESCRIPTION = '_error_description' |
4237 | -LENGTH = '_length' |
4238 | -BODY = 'body' |
4239 | - |
4240 | -debug = False |
4241 | - |
4242 | -class JuiceBox(dict): |
4243 | - """ I am a packet in the JUICE protocol. """ |
4244 | - |
4245 | - def __init__(self, __body='', **kw): |
4246 | - self.update(kw) |
4247 | - if __body: |
4248 | - assert isinstance(__body, str), "body must be a string: %r" % ( repr(__body),) |
4249 | - self['body'] = __body |
4250 | - |
4251 | - def body(): |
4252 | - def get(self): |
4253 | - warnings.warn("body attribute of boxes is now just a regular field", |
4254 | - stacklevel=2) |
4255 | - return self['body'] |
4256 | - def set(self, newbody): |
4257 | - warnings.warn("body attribute of boxes is now just a regular field", |
4258 | - stacklevel=2) |
4259 | - self['body'] = newbody |
4260 | - return get,set |
4261 | - body = property(*body()) |
4262 | - |
4263 | - def copy(self): |
4264 | - newBox = self.__class__() |
4265 | - newBox.update(self) |
4266 | - return newBox |
4267 | - |
4268 | - def serialize(self, |
4269 | - delimiter='\r\n', |
4270 | - escaped='\r\n '): |
4271 | - assert LENGTH not in self |
4272 | - |
4273 | - L = [] |
4274 | - for (k, v) in self.iteritems(): |
4275 | - if k == BODY: |
4276 | - k = LENGTH |
4277 | - v = str(len(self[BODY])) |
4278 | - L.append(k.replace('_', '-').title()) |
4279 | - L.append(': ') |
4280 | - L.append(v.replace(delimiter, escaped)) |
4281 | - L.append(delimiter) |
4282 | - |
4283 | - L.append(delimiter) |
4284 | - if BODY in self: |
4285 | - L.append(self[BODY]) |
4286 | - |
4287 | - bytes = ''.join(L) |
4288 | - return bytes |
4289 | - |
4290 | - def sendTo(self, proto): |
4291 | - """ |
4292 | - Serialize and send this box to a Juice instance. By the time it is |
4293 | - being sent, several keys are required. I must have exactly ONE of:: |
4294 | - |
4295 | - -ask |
4296 | - -answer |
4297 | - -error |
4298 | - |
4299 | - If the '-ask' header is set, then the '-command' header must also be |
4300 | - set. |
4301 | - """ |
4302 | - proto.sendPacket(self) |
4303 | - |
4304 | -# juice.Box => JuiceBox |
4305 | - |
4306 | -Box = JuiceBox |
4307 | - |
4308 | -class TLSBox(JuiceBox): |
4309 | - def __repr__(self): |
4310 | - return 'TLS(**%s)' % (super(TLSBox, self).__repr__(),) |
4311 | - |
4312 | - |
4313 | - def __init__(self, __certificate, __verify=None, __sslstarted=None, **kw): |
4314 | - super(TLSBox, self).__init__(**kw) |
4315 | - self.certificate = __certificate |
4316 | - self.verify = __verify |
4317 | - self.sslstarted = __sslstarted |
4318 | - |
4319 | - def sendTo(self, proto): |
4320 | - super(TLSBox, self).sendTo(proto) |
4321 | - if self.verify is None: |
4322 | - proto.startTLS(self.certificate) |
4323 | - else: |
4324 | - proto.startTLS(self.certificate, self.verify) |
4325 | - if self.sslstarted is not None: |
4326 | - self.sslstarted() |
4327 | - |
4328 | -class QuitBox(JuiceBox): |
4329 | - def __repr__(self): |
4330 | - return 'Quit(**%s)' % (super(QuitBox, self).__repr__(),) |
4331 | - |
4332 | - |
4333 | - def sendTo(self, proto): |
4334 | - super(QuitBox, self).sendTo(proto) |
4335 | - proto.transport.loseConnection() |
4336 | - |
4337 | -class _SwitchBox(JuiceBox): |
4338 | - def __repr__(self): |
4339 | - return 'Switch(**%s)' % (super(_SwitchBox, self).__repr__(),) |
4340 | - |
4341 | - |
4342 | - def __init__(self, __proto, **kw): |
4343 | - super(_SwitchBox, self).__init__(**kw) |
4344 | - self.innerProto = __proto |
4345 | - |
4346 | - def sendTo(self, proto): |
4347 | - super(_SwitchBox, self).sendTo(proto) |
4348 | - proto._switchTo(self.innerProto) |
4349 | - |
4350 | - |
4351 | - |
4352 | -class NegotiateBox(JuiceBox): |
4353 | - def __repr__(self): |
4354 | - return 'Negotiate(**%s)' % (super(NegotiateBox, self).__repr__(),) |
4355 | - |
4356 | - |
4357 | - def sendTo(self, proto): |
4358 | - super(NegotiateBox, self).sendTo(proto) |
4359 | - proto._setProtocolVersion(int(self['version'])) |
4360 | - |
4361 | - |
4362 | - |
4363 | -class JuiceError(Exception): |
4364 | - pass |
4365 | - |
4366 | -class RemoteJuiceError(JuiceError): |
4367 | - """ |
4368 | - This error indicates that something went wrong on the remote end of the |
4369 | - connection, and the error was serialized and transmitted to you. |
4370 | - """ |
4371 | - def __init__(self, errorCode, description, fatal=False): |
4372 | - """Create a remote error with an error code and description. |
4373 | - """ |
4374 | - Exception.__init__(self, "Remote[%s]: %s" % (errorCode, description)) |
4375 | - self.errorCode = errorCode |
4376 | - self.description = description |
4377 | - self.fatal = fatal |
4378 | - |
4379 | -class UnhandledRemoteJuiceError(RemoteJuiceError): |
4380 | - def __init__(self, description): |
4381 | - errorCode = "UNHANDLED" |
4382 | - RemoteJuiceError.__init__(self, errorCode, description) |
4383 | - |
4384 | -class JuiceBoxError(JuiceError): |
4385 | - pass |
4386 | - |
4387 | -class MalformedJuiceBox(JuiceBoxError): |
4388 | - pass |
4389 | - |
4390 | -class UnhandledCommand(JuiceError): |
4391 | - pass |
4392 | - |
4393 | - |
4394 | -class IncompatibleVersions(JuiceError): |
4395 | - pass |
4396 | - |
4397 | -class _Transactor: |
4398 | - def __init__(self, store, callable): |
4399 | - self.store = store |
4400 | - self.callable = callable |
4401 | - |
4402 | - def __call__(self, box): |
4403 | - return self.store.transact(self.callable, box) |
4404 | - |
4405 | - def __repr__(self): |
4406 | - return '<Transaction in: %s of: %s>' % (self.store, self.callable) |
4407 | - |
4408 | -class DispatchMixin: |
4409 | - baseDispatchPrefix = 'juice_' |
4410 | - autoDispatchPrefix = 'command_' |
4411 | - |
4412 | - wrapper = None |
4413 | - |
4414 | - def _auto(self, aCallable, proto, namespace=None): |
4415 | - if aCallable is None: |
4416 | - return None |
4417 | - command = aCallable.command |
4418 | - if namespace not in command.namespaces: |
4419 | - # if you're in the wrong namespace, you are very likely not allowed |
4420 | - # to invoke the command you are trying to invoke. some objects |
4421 | - # have commands exposed in a separate namespace for security |
4422 | - # reasons, since the security model is a role : namespace mapping. |
4423 | - log.msg('WRONG NAMESPACE: %r, %r' % (namespace, command.namespaces)) |
4424 | - return None |
4425 | - def doit(box): |
4426 | - kw = stringsToObjects(box, command.arguments, proto) |
4427 | - for name, extraArg in command.extra: |
4428 | - kw[name] = extraArg.fromTransport(proto.transport) |
4429 | -# def checkIsDict(result): |
4430 | -# if not isinstance(result, dict): |
4431 | -# raise RuntimeError("%r returned %r, not dictionary" % ( |
4432 | -# aCallable, result)) |
4433 | -# return result |
4434 | - def checkKnownErrors(error): |
4435 | - key = error.trap(*command.allErrors) |
4436 | - code = command.allErrors[key] |
4437 | - desc = str(error.value) |
4438 | - return Failure(RemoteJuiceError( |
4439 | - code, desc, error in command.fatalErrors)) |
4440 | - return maybeDeferred(aCallable, **kw).addCallback( |
4441 | - command.makeResponse, proto).addErrback( |
4442 | - checkKnownErrors) |
4443 | - return doit |
4444 | - |
4445 | - def _wrap(self, aCallable): |
4446 | - if aCallable is None: |
4447 | - return None |
4448 | - wrap = self.wrapper |
4449 | - if wrap is not None: |
4450 | - return wrap(aCallable) |
4451 | - else: |
4452 | - return aCallable |
4453 | - |
4454 | - def normalizeCommand(self, cmd): |
4455 | - """Return the canonical form of a command. |
4456 | - """ |
4457 | - return cmd.upper().strip().replace('-', '_') |
4458 | - |
4459 | - def lookupFunction(self, proto, name, namespace): |
4460 | - """Return a callable to invoke when executing the named command. |
4461 | - """ |
4462 | - # Try to find a method to be invoked in a transaction first |
4463 | - # Otherwise fallback to a "regular" method |
4464 | - fName = self.autoDispatchPrefix + name |
4465 | - fObj = getattr(self, fName, None) |
4466 | - if fObj is not None: |
4467 | - # pass the namespace along |
4468 | - return self._auto(fObj, proto, namespace) |
4469 | - |
4470 | - assert namespace is None, 'Old-style parsing' |
4471 | - # Fall back to simplistic command dispatching - we probably want to get |
4472 | - # rid of this eventually, there's no reason to do extra work and write |
4473 | - # fewer docs all the time. |
4474 | - fName = self.baseDispatchPrefix + name |
4475 | - return getattr(self, fName, None) |
4476 | - |
4477 | - def dispatchCommand(self, proto, cmd, box, namespace=None): |
4478 | - fObj = self.lookupFunction(proto, self.normalizeCommand(cmd), namespace) |
4479 | - if fObj is None: |
4480 | - return fail(UnhandledCommand(cmd)) |
4481 | - return maybeDeferred(self._wrap(fObj), box) |
4482 | - |
4483 | -PYTHON_KEYWORDS = [ |
4484 | - 'and', 'del', 'for', 'is', 'raise', 'assert', 'elif', 'from', 'lambda', |
4485 | - 'return', 'break', 'else', 'global', 'not', 'try', 'class', 'except', |
4486 | - 'if', 'or', 'while', 'continue', 'exec', 'import', 'pass', 'yield', |
4487 | - 'def', 'finally', 'in', 'print'] |
4488 | - |
4489 | -def normalizeKey(key): |
4490 | - lkey = key.lower().replace('-', '_') |
4491 | - if lkey in PYTHON_KEYWORDS: |
4492 | - return lkey.title() |
4493 | - return lkey |
4494 | - |
4495 | - |
4496 | -def parseJuiceHeaders(lines): |
4497 | - """ |
4498 | - Create a JuiceBox from a list of header lines. |
4499 | - |
4500 | - @param lines: a list of lines. |
4501 | - """ |
4502 | - b = JuiceBox() |
4503 | - bodylen = 0 |
4504 | - key = None |
4505 | - for L in lines: |
4506 | - if L[0] == ' ': |
4507 | - # continuation |
4508 | - assert key is not None |
4509 | - b[key] += '\r\n'+L[1:] |
4510 | - continue |
4511 | - parts = L.split(': ', 1) |
4512 | - if len(parts) != 2: |
4513 | - raise MalformedJuiceBox("Wrong number of parts: %r" % (L,)) |
4514 | - key, value = parts |
4515 | - key = normalizeKey(key) |
4516 | - b[key] = value |
4517 | - return int(b.pop(LENGTH, 0)), b |
4518 | - |
4519 | -class JuiceParserBase(DispatchMixin): |
4520 | - |
4521 | - def __init__(self): |
4522 | - self._outstandingRequests = {} |
4523 | - |
4524 | - def _puke(self, failure): |
4525 | - log.msg("Juice server or network failure " |
4526 | - "unhandled by client application:") |
4527 | - log.err(failure) |
4528 | - log.msg( |
4529 | - "Dropping connection! " |
4530 | - "To avoid, add errbacks to ALL remote commands!") |
4531 | - if self.transport is not None: |
4532 | - self.transport.loseConnection() |
4533 | - |
4534 | - _counter = 0L |
4535 | - |
4536 | - def _nextTag(self): |
4537 | - self._counter += 1 |
4538 | - return '%x' % (self._counter,) |
4539 | - |
4540 | - def failAllOutgoing(self, reason): |
4541 | - OR = self._outstandingRequests.items() |
4542 | - self._outstandingRequests = None # we can never send another request |
4543 | - for key, value in OR: |
4544 | - value.errback(reason) |
4545 | - |
4546 | - def juiceBoxReceived(self, box): |
4547 | - if debug: |
4548 | - log.msg("Juice receive: %s" % pprint.pformat(dict(box.iteritems()))) |
4549 | - |
4550 | - if ANSWER in box: |
4551 | - question = self._outstandingRequests.pop(box[ANSWER]) |
4552 | - question.addErrback(self._puke) |
4553 | - self._wrap(question.callback)(box) |
4554 | - elif ERROR in box: |
4555 | - question = self._outstandingRequests.pop(box[ERROR]) |
4556 | - question.addErrback(self._puke) |
4557 | - self._wrap(question.errback)( |
4558 | - Failure(RemoteJuiceError(box[ERROR_CODE], |
4559 | - box[ERROR_DESCRIPTION]))) |
4560 | - elif COMMAND in box: |
4561 | - cmd = box[COMMAND] |
4562 | - def sendAnswer(answerBox): |
4563 | - if ASK not in box: |
4564 | - return |
4565 | - if self.transport is None: |
4566 | - return |
4567 | - answerBox[ANSWER] = box[ASK] |
4568 | - answerBox.sendTo(self) |
4569 | - def sendError(error): |
4570 | - if ASK not in box: |
4571 | - return error |
4572 | - if error.check(RemoteJuiceError): |
4573 | - code = error.value.errorCode |
4574 | - desc = error.value.description |
4575 | - if error.value.fatal: |
4576 | - errorBox = QuitBox() |
4577 | - else: |
4578 | - errorBox = JuiceBox() |
4579 | - else: |
4580 | - errorBox = QuitBox() |
4581 | - log.err(error) # here is where server-side logging happens |
4582 | - # if the error isn't handled |
4583 | - code = 'UNHANDLED' |
4584 | - desc = "Unhandled Remote System Exception " |
4585 | - errorBox[ERROR] = box[ASK] |
4586 | - errorBox[ERROR_DESCRIPTION] = desc |
4587 | - errorBox[ERROR_CODE] = code |
4588 | - if self.transport is not None: |
4589 | - errorBox.sendTo(self) |
4590 | - return None # intentionally stop the error here: don't log the |
4591 | - # traceback if it's handled, do log it (earlier) if |
4592 | - # it isn't |
4593 | - self.dispatchCommand(self, cmd, box).addCallbacks(sendAnswer, sendError |
4594 | - ).addErrback(self._puke) |
4595 | - else: |
4596 | - raise RuntimeError( |
4597 | - "Empty packet received over connection-oriented juice: %r" % (box,)) |
4598 | - |
4599 | - def sendBoxCommand(self, command, box, requiresAnswer=True): |
4600 | - """ |
4601 | - Send a command across the wire with the given C{juice.Box}. |
4602 | - |
4603 | - Returns a Deferred which fires with the response C{juice.Box} when it |
4604 | - is received, or fails with a C{juice.RemoteJuiceError} if an error is |
4605 | - received. |
4606 | - |
4607 | - If the Deferred fails and the error is not handled by the caller of |
4608 | - this method, the failure will be logged and the connection dropped. |
4609 | - """ |
4610 | - if self._outstandingRequests is None: |
4611 | - return fail(CONNECTION_LOST) |
4612 | - box[COMMAND] = command |
4613 | - tag = self._nextTag() |
4614 | - if requiresAnswer: |
4615 | - box[ASK] = tag |
4616 | - result = self._outstandingRequests[tag] = Deferred() |
4617 | - else: |
4618 | - result = None |
4619 | - box.sendTo(self) |
4620 | - return result |
4621 | - |
4622 | - |
4623 | - |
4624 | - |
4625 | - |
4626 | - |
4627 | -class Argument: |
4628 | - optional = False |
4629 | - |
4630 | - def __init__(self, optional=False): |
4631 | - self.optional = optional |
4632 | - |
4633 | - def retrieve(self, d, name): |
4634 | - if self.optional: |
4635 | - value = d.get(name) |
4636 | - if value is not None: |
4637 | - del d[name] |
4638 | - else: |
4639 | - value = d.pop(name) |
4640 | - return value |
4641 | - |
4642 | - def fromBox(self, name, strings, objects, proto): |
4643 | - st = self.retrieve(strings, name) |
4644 | - if self.optional and st is None: |
4645 | - objects[name] = None |
4646 | - else: |
4647 | - objects[name] = self.fromStringProto(st, proto) |
4648 | - |
4649 | - def toBox(self, name, strings, objects, proto): |
4650 | - obj = self.retrieve(objects, name) |
4651 | - if self.optional and obj is None: |
4652 | - # strings[name] = None |
4653 | - return |
4654 | - else: |
4655 | - strings[name] = self.toStringProto(obj, proto) |
4656 | - |
4657 | - def fromStringProto(self, inString, proto): |
4658 | - return self.fromString(inString) |
4659 | - |
4660 | - def toStringProto(self, inObject, proto): |
4661 | - return self.toString(inObject) |
4662 | - |
4663 | - def fromString(self, inString): |
4664 | - raise NotImplementedError() |
4665 | - |
4666 | - def toString(self, inObject): |
4667 | - raise NotImplementedError() |
4668 | - |
4669 | -class JuiceList(Argument): |
4670 | - def __init__(self, subargs): |
4671 | - self.subargs = subargs |
4672 | - |
4673 | - def fromStringProto(self, inString, proto): |
4674 | - boxes = parseString(inString) |
4675 | - values = [stringsToObjects(box, self.subargs, proto) |
4676 | - for box in boxes] |
4677 | - return values |
4678 | - |
4679 | - def toStringProto(self, inObject, proto): |
4680 | - return ''.join([objectsToStrings( |
4681 | - objects, self.subargs, Box(), proto |
4682 | - ).serialize() for objects in inObject]) |
4683 | - |
4684 | -class ListOf(Argument): |
4685 | - def __init__(self, subarg, delimiter=', '): |
4686 | - self.subarg = subarg |
4687 | - self.delimiter = delimiter |
4688 | - |
4689 | - def fromStringProto(self, inString, proto): |
4690 | - strings = inString.split(self.delimiter) |
4691 | - L = [self.subarg.fromStringProto(string, proto) |
4692 | - for string in strings] |
4693 | - return L |
4694 | - |
4695 | - def toStringProto(self, inObject, proto): |
4696 | - L = [] |
4697 | - for inSingle in inObject: |
4698 | - outString = self.subarg.toStringProto(inSingle, proto) |
4699 | - assert self.delimiter not in outString |
4700 | - L.append(outString) |
4701 | - return self.delimiter.join(L) |
4702 | - |
4703 | -class Integer(Argument): |
4704 | - fromString = int |
4705 | - def toString(self, inObject): |
4706 | - return str(int(inObject)) |
4707 | - |
4708 | -class String(Argument): |
4709 | - def toString(self, inObject): |
4710 | - return inObject |
4711 | - def fromString(self, inString): |
4712 | - return inString |
4713 | - |
4714 | -class EncodedString(Argument): |
4715 | - |
4716 | - def __init__(self, encoding): |
4717 | - self.encoding = encoding |
4718 | - |
4719 | - def toString(self, inObject): |
4720 | - return inObject.encode(self.encoding) |
4721 | - |
4722 | - def fromString(self, inString): |
4723 | - return inString.decode(self.encoding) |
4724 | - |
4725 | -# Temporary backwards compatibility for Exponent |
4726 | - |
4727 | -Body = String |
4728 | - |
4729 | -class Unicode(String): |
4730 | - def toString(self, inObject): |
4731 | - # assert isinstance(inObject, unicode) |
4732 | - return String.toString(self, inObject.encode('utf-8')) |
4733 | - |
4734 | - def fromString(self, inString): |
4735 | - # assert isinstance(inString, str) |
4736 | - return String.fromString(self, inString).decode('utf-8') |
4737 | - |
4738 | -class Path(Unicode): |
4739 | - def fromString(self, inString): |
4740 | - return filepath.FilePath(Unicode.fromString(self, inString)) |
4741 | - |
4742 | - def toString(self, inObject): |
4743 | - return Unicode.toString(self, inObject.path) |
4744 | - |
4745 | - |
4746 | -class Float(Argument): |
4747 | - fromString = float |
4748 | - toString = str |
4749 | - |
4750 | -class Base64Binary(Argument): |
4751 | - def toString(self, inObject): |
4752 | - return inObject.encode('base64').replace('\n', '') |
4753 | - def fromString(self, inString): |
4754 | - return inString.decode('base64') |
4755 | - |
4756 | -class Time(Argument): |
4757 | - def toString(self, inObject): |
4758 | - return inObject.asISO8601TimeAndDate() |
4759 | - def fromString(self, inString): |
4760 | - return extime.Time.fromISO8601TimeAndDate(inString) |
4761 | - |
4762 | -class ExtraArg: |
4763 | - def fromTransport(self, inTransport): |
4764 | - raise NotImplementedError() |
4765 | - |
4766 | -class Peer(ExtraArg): |
4767 | - def fromTransport(self, inTransport): |
4768 | - return inTransport.getQ2QPeer() |
4769 | - |
4770 | -class PeerDomain(ExtraArg): |
4771 | - def fromTransport(self, inTransport): |
4772 | - return inTransport.getQ2QPeer().domain |
4773 | - |
4774 | -class PeerUser(ExtraArg): |
4775 | - def fromTransport(self, inTransport): |
4776 | - return inTransport.getQ2QPeer().resource |
4777 | - |
4778 | -class Host(ExtraArg): |
4779 | - def fromTransport(self, inTransport): |
4780 | - return inTransport.getQ2QHost() |
4781 | - |
4782 | -class HostDomain(ExtraArg): |
4783 | - def fromTransport(self, inTransport): |
4784 | - return inTransport.getQ2QHost().domain |
4785 | - |
4786 | -class HostUser(ExtraArg): |
4787 | - def fromTransport(self, inTransport): |
4788 | - return inTransport.getQ2QHost().resource |
4789 | - |
4790 | - |
4791 | - |
4792 | -class Boolean(Argument): |
4793 | - def fromString(self, inString): |
4794 | - if inString == 'True': |
4795 | - return True |
4796 | - elif inString == 'False': |
4797 | - return False |
4798 | - else: |
4799 | - raise RuntimeError("Bad boolean value: %r" % (inString,)) |
4800 | - |
4801 | - def toString(self, inObject): |
4802 | - if inObject: |
4803 | - return 'True' |
4804 | - else: |
4805 | - return 'False' |
4806 | - |
4807 | -class Command: |
4808 | - class __metaclass__(type): |
4809 | - def __new__(cls, name, bases, attrs): |
4810 | - re = attrs['reverseErrors'] = {} |
4811 | - er = attrs['allErrors'] = {} |
4812 | - for v, k in attrs.get('errors',{}).iteritems(): |
4813 | - re[k] = v |
4814 | - er[v] = k |
4815 | - for v, k in attrs.get('fatalErrors',{}).iteritems(): |
4816 | - re[k] = v |
4817 | - er[v] = k |
4818 | - return type.__new__(cls, name, bases, attrs) |
4819 | - |
4820 | - arguments = [] |
4821 | - response = [] |
4822 | - extra = [] |
4823 | - namespaces = [None] # This is set to [None] on purpose: None means |
4824 | - # "no namespace", not "empty list". "empty |
4825 | - # list" will make your command invalid in _all_ |
4826 | - # namespaces, effectively uncallable. |
4827 | - errors = {} |
4828 | - fatalErrors = {} |
4829 | - |
4830 | - commandType = Box |
4831 | - responseType = Box |
4832 | - |
4833 | - def commandName(): |
4834 | - def get(self): |
4835 | - return self.__class__.__name__ |
4836 | - raise NotImplementedError("Missing command name") |
4837 | - return get, |
4838 | - commandName = property(*commandName()) |
4839 | - |
4840 | - def __init__(self, **kw): |
4841 | - self.structured = kw |
4842 | - givenArgs = [normalizeKey(k) for k in kw.keys()] |
4843 | - forgotten = [] |
4844 | - for name, arg in self.arguments: |
4845 | - if normalizeKey(name) not in givenArgs and not arg.optional: |
4846 | - forgotten.append(normalizeKey(name)) |
4847 | -# for v in kw.itervalues(): |
4848 | -# if v is None: |
4849 | -# from pprint import pformat |
4850 | -# raise RuntimeError("ARGH: %s" % pformat(kw)) |
4851 | - if forgotten: |
4852 | - if len(forgotten) == 1: |
4853 | - plural = 'an argument' |
4854 | - else: |
4855 | - plural = 'some arguments' |
4856 | - raise RuntimeError("You forgot %s to %r: %s" % ( |
4857 | - plural, self.commandName, ', '.join(forgotten))) |
4858 | - forgotten = [] |
4859 | - |
4860 | - def makeResponse(cls, objects, proto): |
4861 | - try: |
4862 | - return objectsToStrings(objects, cls.response, cls.responseType(), proto) |
4863 | - except: |
4864 | - log.msg("Exception in %r.makeResponse" % (cls,)) |
4865 | - raise |
4866 | - makeResponse = classmethod(makeResponse) |
4867 | - |
4868 | - def do(self, proto, namespace=None, requiresAnswer=True): |
4869 | - if namespace is not None: |
4870 | - cmd = namespace + ":" + self.commandName |
4871 | - else: |
4872 | - cmd = self.commandName |
4873 | - def _massageError(error): |
4874 | - error.trap(RemoteJuiceError) |
4875 | - rje = error.value |
4876 | - return Failure(self.reverseErrors.get(rje.errorCode, UnhandledRemoteJuiceError)(rje.description)) |
4877 | - |
4878 | - d = proto.sendBoxCommand( |
4879 | - cmd, objectsToStrings(self.structured, self.arguments, self.commandType(), |
4880 | - proto), |
4881 | - requiresAnswer) |
4882 | - |
4883 | - if requiresAnswer: |
4884 | - d.addCallback(stringsToObjects, self.response, proto) |
4885 | - d.addCallback(self.addExtra, proto.transport) |
4886 | - d.addErrback(_massageError) |
4887 | - |
4888 | - return d |
4889 | - |
4890 | - def addExtra(self, d, transport): |
4891 | - for name, extraArg in self.extra: |
4892 | - d[name] = extraArg.fromTransport(transport) |
4893 | - return d |
4894 | - |
4895 | - |
4896 | -class ProtocolSwitchCommand(Command): |
4897 | - """Use this command to switch from something Juice-derived to a different |
4898 | - protocol mid-connection. This can be useful to use juice as the |
4899 | - connection-startup negotiation phase. Since TLS is a different layer |
4900 | - entirely, you can use Juice to negotiate the security parameters of your |
4901 | - connection, then switch to a different protocol, and the connection will |
4902 | - remain secured. |
4903 | - """ |
4904 | - |
4905 | - def __init__(self, __protoToSwitchToFactory, **kw): |
4906 | - self.protoToSwitchToFactory = __protoToSwitchToFactory |
4907 | - super(ProtocolSwitchCommand, self).__init__(**kw) |
4908 | - |
4909 | - def makeResponse(cls, innerProto, proto): |
4910 | - return _SwitchBox(innerProto) |
4911 | - |
4912 | - makeResponse = classmethod(makeResponse) |
4913 | - |
4914 | - def do(self, proto, namespace=None): |
4915 | - d = super(ProtocolSwitchCommand, self).do(proto) |
4916 | - proto._lock() |
4917 | - def switchNow(ign): |
4918 | - innerProto = self.protoToSwitchToFactory.buildProtocol(proto.transport.getPeer()) |
4919 | - proto._switchTo(innerProto, self.protoToSwitchToFactory) |
4920 | - return ign |
4921 | - def die(ign): |
4922 | - proto.transport.loseConnection() |
4923 | - return ign |
4924 | - def handle(ign): |
4925 | - self.protoToSwitchToFactory.clientConnectionFailed(None, Failure(CONNECTION_LOST)) |
4926 | - return ign |
4927 | - return d.addCallbacks(switchNow, handle).addErrback(die) |
4928 | - |
4929 | -class Negotiate(Command): |
4930 | - commandName = 'Negotiate' |
4931 | - |
4932 | - arguments = [('versions', ListOf(Integer()))] |
4933 | - response = [('version', Integer())] |
4934 | - |
4935 | - responseType = NegotiateBox |
4936 | - |
4937 | - |
4938 | -class Juice(LineReceiver, JuiceParserBase): |
4939 | - """ |
4940 | - JUICE (JUice Is Concurrent Events) is a simple connection-oriented |
4941 | - request/response protocol. Packets, or "boxes", are collections of |
4942 | - RFC2822-inspired headers, plus a body. Note that this is NOT a literal |
4943 | - interpretation of any existing RFC, 822, 2822 or otherwise, but a simpler |
4944 | - version that does not do line continuations, does not specify any |
4945 | - particular format for header values, dispatches semantic meanings of most |
4946 | - headers on the -Command header rather than giving them global meaning, and |
4947 | - allows multiple sets of headers (messages, or JuiceBoxes) on a connection. |
4948 | - |
4949 | - All headers whose names begin with a dash ('-') are reserved for use by the |
4950 | - protocol. All others are for application use - their meaning depends on |
4951 | - the value of the "-Command" header. |
4952 | - """ |
4953 | - |
4954 | - protocolName = 'juice-base' |
4955 | - |
4956 | - hostCertificate = None |
4957 | - |
4958 | - MAX_LENGTH = 1024 * 1024 |
4959 | - |
4960 | - isServer = property(lambda self: self._issueGreeting, |
4961 | - doc=""" |
4962 | - True if this is a juice server, e.g. it is going to |
4963 | - issue or has issued a server greeting upon |
4964 | - connection. |
4965 | - """) |
4966 | - |
4967 | - isClient = property(lambda self: not self._issueGreeting, |
4968 | - doc=""" |
4969 | - True if this is a juice server, e.g. it is not going to |
4970 | - issue or did not issue a server greeting upon |
4971 | - connection. |
4972 | - """) |
4973 | - |
4974 | - def __init__(self, issueGreeting): |
4975 | - """ |
4976 | - @param issueGreeting: whether to issue a greeting when connected. This |
4977 | - should be set on server-side Juice protocols. |
4978 | - """ |
4979 | - JuiceParserBase.__init__(self) |
4980 | - self._issueGreeting = issueGreeting |
4981 | - |
4982 | - def __repr__(self): |
4983 | - return '<%s %s/%s at 0x%x>' % (self.__class__.__name__, self.isClient and 'client' or 'server', self.innerProtocol, id(self)) |
4984 | - |
4985 | - __locked = False |
4986 | - |
4987 | - def _lock(self): |
4988 | - """ Lock this Juice instance so that no further Juice traffic may be sent. |
4989 | - This is used when sending a request to switch underlying protocols. |
4990 | - You probably want to subclass ProtocolSwitchCommand rather than calling |
4991 | - this directly. |
4992 | - """ |
4993 | - self.__locked = True |
4994 | - |
4995 | - innerProtocol = None |
4996 | - |
4997 | - def _switchTo(self, newProto, clientFactory=None): |
4998 | - """ Switch this Juice instance to a new protocol. You need to do this |
4999 | - 'simultaneously' on both ends of a connection; the easiest way to do |
5000 | - this is to use a subclass of ProtocolSwitchCommand. |
The diff has been truncated for viewing.