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