##// END OF EJS Templates
wireprototypes: disable pytype where it's just confused...
Augie Fackler -
r43782:f5fcf712 default
parent child Browse files
Show More
@@ -1,454 +1,454 b''
1 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
1 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 from .node import (
8 from .node import (
9 bin,
9 bin,
10 hex,
10 hex,
11 )
11 )
12 from .i18n import _
12 from .i18n import _
13 from .pycompat import getattr
13 from .pycompat import getattr
14 from .thirdparty import attr
14 from .thirdparty import attr
15 from . import (
15 from . import (
16 error,
16 error,
17 util,
17 util,
18 )
18 )
19 from .interfaces import util as interfaceutil
19 from .interfaces import util as interfaceutil
20 from .utils import compression
20 from .utils import compression
21
21
22 # Names of the SSH protocol implementations.
22 # Names of the SSH protocol implementations.
23 SSHV1 = b'ssh-v1'
23 SSHV1 = b'ssh-v1'
24 # These are advertised over the wire. Increment the counters at the end
24 # These are advertised over the wire. Increment the counters at the end
25 # to reflect BC breakages.
25 # to reflect BC breakages.
26 SSHV2 = b'exp-ssh-v2-0003'
26 SSHV2 = b'exp-ssh-v2-0003'
27 HTTP_WIREPROTO_V2 = b'exp-http-v2-0003'
27 HTTP_WIREPROTO_V2 = b'exp-http-v2-0003'
28
28
29 NARROWCAP = b'exp-narrow-1'
29 NARROWCAP = b'exp-narrow-1'
30 ELLIPSESCAP1 = b'exp-ellipses-1'
30 ELLIPSESCAP1 = b'exp-ellipses-1'
31 ELLIPSESCAP = b'exp-ellipses-2'
31 ELLIPSESCAP = b'exp-ellipses-2'
32 SUPPORTED_ELLIPSESCAP = (ELLIPSESCAP1, ELLIPSESCAP)
32 SUPPORTED_ELLIPSESCAP = (ELLIPSESCAP1, ELLIPSESCAP)
33
33
34 # All available wire protocol transports.
34 # All available wire protocol transports.
35 TRANSPORTS = {
35 TRANSPORTS = {
36 SSHV1: {b'transport': b'ssh', b'version': 1,},
36 SSHV1: {b'transport': b'ssh', b'version': 1,},
37 SSHV2: {
37 SSHV2: {
38 b'transport': b'ssh',
38 b'transport': b'ssh',
39 # TODO mark as version 2 once all commands are implemented.
39 # TODO mark as version 2 once all commands are implemented.
40 b'version': 1,
40 b'version': 1,
41 },
41 },
42 b'http-v1': {b'transport': b'http', b'version': 1,},
42 b'http-v1': {b'transport': b'http', b'version': 1,},
43 HTTP_WIREPROTO_V2: {b'transport': b'http', b'version': 2,},
43 HTTP_WIREPROTO_V2: {b'transport': b'http', b'version': 2,},
44 }
44 }
45
45
46
46
47 class bytesresponse(object):
47 class bytesresponse(object):
48 """A wire protocol response consisting of raw bytes."""
48 """A wire protocol response consisting of raw bytes."""
49
49
50 def __init__(self, data):
50 def __init__(self, data):
51 self.data = data
51 self.data = data
52
52
53
53
54 class ooberror(object):
54 class ooberror(object):
55 """wireproto reply: failure of a batch of operation
55 """wireproto reply: failure of a batch of operation
56
56
57 Something failed during a batch call. The error message is stored in
57 Something failed during a batch call. The error message is stored in
58 `self.message`.
58 `self.message`.
59 """
59 """
60
60
61 def __init__(self, message):
61 def __init__(self, message):
62 self.message = message
62 self.message = message
63
63
64
64
65 class pushres(object):
65 class pushres(object):
66 """wireproto reply: success with simple integer return
66 """wireproto reply: success with simple integer return
67
67
68 The call was successful and returned an integer contained in `self.res`.
68 The call was successful and returned an integer contained in `self.res`.
69 """
69 """
70
70
71 def __init__(self, res, output):
71 def __init__(self, res, output):
72 self.res = res
72 self.res = res
73 self.output = output
73 self.output = output
74
74
75
75
76 class pusherr(object):
76 class pusherr(object):
77 """wireproto reply: failure
77 """wireproto reply: failure
78
78
79 The call failed. The `self.res` attribute contains the error message.
79 The call failed. The `self.res` attribute contains the error message.
80 """
80 """
81
81
82 def __init__(self, res, output):
82 def __init__(self, res, output):
83 self.res = res
83 self.res = res
84 self.output = output
84 self.output = output
85
85
86
86
87 class streamres(object):
87 class streamres(object):
88 """wireproto reply: binary stream
88 """wireproto reply: binary stream
89
89
90 The call was successful and the result is a stream.
90 The call was successful and the result is a stream.
91
91
92 Accepts a generator containing chunks of data to be sent to the client.
92 Accepts a generator containing chunks of data to be sent to the client.
93
93
94 ``prefer_uncompressed`` indicates that the data is expected to be
94 ``prefer_uncompressed`` indicates that the data is expected to be
95 uncompressable and that the stream should therefore use the ``none``
95 uncompressable and that the stream should therefore use the ``none``
96 engine.
96 engine.
97 """
97 """
98
98
99 def __init__(self, gen=None, prefer_uncompressed=False):
99 def __init__(self, gen=None, prefer_uncompressed=False):
100 self.gen = gen
100 self.gen = gen
101 self.prefer_uncompressed = prefer_uncompressed
101 self.prefer_uncompressed = prefer_uncompressed
102
102
103
103
104 class streamreslegacy(object):
104 class streamreslegacy(object):
105 """wireproto reply: uncompressed binary stream
105 """wireproto reply: uncompressed binary stream
106
106
107 The call was successful and the result is a stream.
107 The call was successful and the result is a stream.
108
108
109 Accepts a generator containing chunks of data to be sent to the client.
109 Accepts a generator containing chunks of data to be sent to the client.
110
110
111 Like ``streamres``, but sends an uncompressed data for "version 1" clients
111 Like ``streamres``, but sends an uncompressed data for "version 1" clients
112 using the application/mercurial-0.1 media type.
112 using the application/mercurial-0.1 media type.
113 """
113 """
114
114
115 def __init__(self, gen=None):
115 def __init__(self, gen=None):
116 self.gen = gen
116 self.gen = gen
117
117
118
118
119 # list of nodes encoding / decoding
119 # list of nodes encoding / decoding
120 def decodelist(l, sep=b' '):
120 def decodelist(l, sep=b' '):
121 if l:
121 if l:
122 return [bin(v) for v in l.split(sep)]
122 return [bin(v) for v in l.split(sep)]
123 return []
123 return []
124
124
125
125
126 def encodelist(l, sep=b' '):
126 def encodelist(l, sep=b' '):
127 try:
127 try:
128 return sep.join(map(hex, l))
128 return sep.join(map(hex, l))
129 except TypeError:
129 except TypeError:
130 raise
130 raise
131
131
132
132
133 # batched call argument encoding
133 # batched call argument encoding
134
134
135
135
136 def escapebatcharg(plain):
136 def escapebatcharg(plain):
137 return (
137 return (
138 plain.replace(b':', b':c')
138 plain.replace(b':', b':c')
139 .replace(b',', b':o')
139 .replace(b',', b':o')
140 .replace(b';', b':s')
140 .replace(b';', b':s')
141 .replace(b'=', b':e')
141 .replace(b'=', b':e')
142 )
142 )
143
143
144
144
145 def unescapebatcharg(escaped):
145 def unescapebatcharg(escaped):
146 return (
146 return (
147 escaped.replace(b':e', b'=')
147 escaped.replace(b':e', b'=')
148 .replace(b':s', b';')
148 .replace(b':s', b';')
149 .replace(b':o', b',')
149 .replace(b':o', b',')
150 .replace(b':c', b':')
150 .replace(b':c', b':')
151 )
151 )
152
152
153
153
154 # mapping of options accepted by getbundle and their types
154 # mapping of options accepted by getbundle and their types
155 #
155 #
156 # Meant to be extended by extensions. It is the extension's responsibility to
156 # Meant to be extended by extensions. It is the extension's responsibility to
157 # ensure such options are properly processed in exchange.getbundle.
157 # ensure such options are properly processed in exchange.getbundle.
158 #
158 #
159 # supported types are:
159 # supported types are:
160 #
160 #
161 # :nodes: list of binary nodes, transmitted as space-separated hex nodes
161 # :nodes: list of binary nodes, transmitted as space-separated hex nodes
162 # :csv: list of values, transmitted as comma-separated values
162 # :csv: list of values, transmitted as comma-separated values
163 # :scsv: set of values, transmitted as comma-separated values
163 # :scsv: set of values, transmitted as comma-separated values
164 # :plain: string with no transformation needed.
164 # :plain: string with no transformation needed.
165 GETBUNDLE_ARGUMENTS = {
165 GETBUNDLE_ARGUMENTS = {
166 b'heads': b'nodes',
166 b'heads': b'nodes',
167 b'bookmarks': b'boolean',
167 b'bookmarks': b'boolean',
168 b'common': b'nodes',
168 b'common': b'nodes',
169 b'obsmarkers': b'boolean',
169 b'obsmarkers': b'boolean',
170 b'phases': b'boolean',
170 b'phases': b'boolean',
171 b'bundlecaps': b'scsv',
171 b'bundlecaps': b'scsv',
172 b'listkeys': b'csv',
172 b'listkeys': b'csv',
173 b'cg': b'boolean',
173 b'cg': b'boolean',
174 b'cbattempted': b'boolean',
174 b'cbattempted': b'boolean',
175 b'stream': b'boolean',
175 b'stream': b'boolean',
176 b'includepats': b'csv',
176 b'includepats': b'csv',
177 b'excludepats': b'csv',
177 b'excludepats': b'csv',
178 }
178 }
179
179
180
180
181 class baseprotocolhandler(interfaceutil.Interface):
181 class baseprotocolhandler(interfaceutil.Interface):
182 """Abstract base class for wire protocol handlers.
182 """Abstract base class for wire protocol handlers.
183
183
184 A wire protocol handler serves as an interface between protocol command
184 A wire protocol handler serves as an interface between protocol command
185 handlers and the wire protocol transport layer. Protocol handlers provide
185 handlers and the wire protocol transport layer. Protocol handlers provide
186 methods to read command arguments, redirect stdio for the duration of
186 methods to read command arguments, redirect stdio for the duration of
187 the request, handle response types, etc.
187 the request, handle response types, etc.
188 """
188 """
189
189
190 name = interfaceutil.Attribute(
190 name = interfaceutil.Attribute(
191 """The name of the protocol implementation.
191 """The name of the protocol implementation.
192
192
193 Used for uniquely identifying the transport type.
193 Used for uniquely identifying the transport type.
194 """
194 """
195 )
195 )
196
196
197 def getargs(args):
197 def getargs(args):
198 """return the value for arguments in <args>
198 """return the value for arguments in <args>
199
199
200 For version 1 transports, returns a list of values in the same
200 For version 1 transports, returns a list of values in the same
201 order they appear in ``args``. For version 2 transports, returns
201 order they appear in ``args``. For version 2 transports, returns
202 a dict mapping argument name to value.
202 a dict mapping argument name to value.
203 """
203 """
204
204
205 def getprotocaps():
205 def getprotocaps():
206 """Returns the list of protocol-level capabilities of client
206 """Returns the list of protocol-level capabilities of client
207
207
208 Returns a list of capabilities as declared by the client for
208 Returns a list of capabilities as declared by the client for
209 the current request (or connection for stateful protocol handlers)."""
209 the current request (or connection for stateful protocol handlers)."""
210
210
211 def getpayload():
211 def getpayload():
212 """Provide a generator for the raw payload.
212 """Provide a generator for the raw payload.
213
213
214 The caller is responsible for ensuring that the full payload is
214 The caller is responsible for ensuring that the full payload is
215 processed.
215 processed.
216 """
216 """
217
217
218 def mayberedirectstdio():
218 def mayberedirectstdio():
219 """Context manager to possibly redirect stdio.
219 """Context manager to possibly redirect stdio.
220
220
221 The context manager yields a file-object like object that receives
221 The context manager yields a file-object like object that receives
222 stdout and stderr output when the context manager is active. Or it
222 stdout and stderr output when the context manager is active. Or it
223 yields ``None`` if no I/O redirection occurs.
223 yields ``None`` if no I/O redirection occurs.
224
224
225 The intent of this context manager is to capture stdio output
225 The intent of this context manager is to capture stdio output
226 so it may be sent in the response. Some transports support streaming
226 so it may be sent in the response. Some transports support streaming
227 stdio to the client in real time. For these transports, stdio output
227 stdio to the client in real time. For these transports, stdio output
228 won't be captured.
228 won't be captured.
229 """
229 """
230
230
231 def client():
231 def client():
232 """Returns a string representation of this client (as bytes)."""
232 """Returns a string representation of this client (as bytes)."""
233
233
234 def addcapabilities(repo, caps):
234 def addcapabilities(repo, caps):
235 """Adds advertised capabilities specific to this protocol.
235 """Adds advertised capabilities specific to this protocol.
236
236
237 Receives the list of capabilities collected so far.
237 Receives the list of capabilities collected so far.
238
238
239 Returns a list of capabilities. The passed in argument can be returned.
239 Returns a list of capabilities. The passed in argument can be returned.
240 """
240 """
241
241
242 def checkperm(perm):
242 def checkperm(perm):
243 """Validate that the client has permissions to perform a request.
243 """Validate that the client has permissions to perform a request.
244
244
245 The argument is the permission required to proceed. If the client
245 The argument is the permission required to proceed. If the client
246 doesn't have that permission, the exception should raise or abort
246 doesn't have that permission, the exception should raise or abort
247 in a protocol specific manner.
247 in a protocol specific manner.
248 """
248 """
249
249
250
250
251 class commandentry(object):
251 class commandentry(object):
252 """Represents a declared wire protocol command."""
252 """Represents a declared wire protocol command."""
253
253
254 def __init__(
254 def __init__(
255 self,
255 self,
256 func,
256 func,
257 args=b'',
257 args=b'',
258 transports=None,
258 transports=None,
259 permission=b'push',
259 permission=b'push',
260 cachekeyfn=None,
260 cachekeyfn=None,
261 extracapabilitiesfn=None,
261 extracapabilitiesfn=None,
262 ):
262 ):
263 self.func = func
263 self.func = func
264 self.args = args
264 self.args = args
265 self.transports = transports or set()
265 self.transports = transports or set()
266 self.permission = permission
266 self.permission = permission
267 self.cachekeyfn = cachekeyfn
267 self.cachekeyfn = cachekeyfn
268 self.extracapabilitiesfn = extracapabilitiesfn
268 self.extracapabilitiesfn = extracapabilitiesfn
269
269
270 def _merge(self, func, args):
270 def _merge(self, func, args):
271 """Merge this instance with an incoming 2-tuple.
271 """Merge this instance with an incoming 2-tuple.
272
272
273 This is called when a caller using the old 2-tuple API attempts
273 This is called when a caller using the old 2-tuple API attempts
274 to replace an instance. The incoming values are merged with
274 to replace an instance. The incoming values are merged with
275 data not captured by the 2-tuple and a new instance containing
275 data not captured by the 2-tuple and a new instance containing
276 the union of the two objects is returned.
276 the union of the two objects is returned.
277 """
277 """
278 return commandentry(
278 return commandentry(
279 func,
279 func,
280 args=args,
280 args=args,
281 transports=set(self.transports),
281 transports=set(self.transports),
282 permission=self.permission,
282 permission=self.permission,
283 )
283 )
284
284
285 # Old code treats instances as 2-tuples. So expose that interface.
285 # Old code treats instances as 2-tuples. So expose that interface.
286 def __iter__(self):
286 def __iter__(self):
287 yield self.func
287 yield self.func
288 yield self.args
288 yield self.args
289
289
290 def __getitem__(self, i):
290 def __getitem__(self, i):
291 if i == 0:
291 if i == 0:
292 return self.func
292 return self.func
293 elif i == 1:
293 elif i == 1:
294 return self.args
294 return self.args
295 else:
295 else:
296 raise IndexError(b'can only access elements 0 and 1')
296 raise IndexError(b'can only access elements 0 and 1')
297
297
298
298
299 class commanddict(dict):
299 class commanddict(dict):
300 """Container for registered wire protocol commands.
300 """Container for registered wire protocol commands.
301
301
302 It behaves like a dict. But __setitem__ is overwritten to allow silent
302 It behaves like a dict. But __setitem__ is overwritten to allow silent
303 coercion of values from 2-tuples for API compatibility.
303 coercion of values from 2-tuples for API compatibility.
304 """
304 """
305
305
306 def __setitem__(self, k, v):
306 def __setitem__(self, k, v):
307 if isinstance(v, commandentry):
307 if isinstance(v, commandentry):
308 pass
308 pass
309 # Cast 2-tuples to commandentry instances.
309 # Cast 2-tuples to commandentry instances.
310 elif isinstance(v, tuple):
310 elif isinstance(v, tuple):
311 if len(v) != 2:
311 if len(v) != 2:
312 raise ValueError(b'command tuples must have exactly 2 elements')
312 raise ValueError(b'command tuples must have exactly 2 elements')
313
313
314 # It is common for extensions to wrap wire protocol commands via
314 # It is common for extensions to wrap wire protocol commands via
315 # e.g. ``wireproto.commands[x] = (newfn, args)``. Because callers
315 # e.g. ``wireproto.commands[x] = (newfn, args)``. Because callers
316 # doing this aren't aware of the new API that uses objects to store
316 # doing this aren't aware of the new API that uses objects to store
317 # command entries, we automatically merge old state with new.
317 # command entries, we automatically merge old state with new.
318 if k in self:
318 if k in self:
319 v = self[k]._merge(v[0], v[1])
319 v = self[k]._merge(v[0], v[1])
320 else:
320 else:
321 # Use default values from @wireprotocommand.
321 # Use default values from @wireprotocommand.
322 v = commandentry(
322 v = commandentry(
323 v[0],
323 v[0],
324 args=v[1],
324 args=v[1],
325 transports=set(TRANSPORTS),
325 transports=set(TRANSPORTS),
326 permission=b'push',
326 permission=b'push',
327 )
327 )
328 else:
328 else:
329 raise ValueError(
329 raise ValueError(
330 b'command entries must be commandentry instances '
330 b'command entries must be commandentry instances '
331 b'or 2-tuples'
331 b'or 2-tuples'
332 )
332 )
333
333
334 return super(commanddict, self).__setitem__(k, v)
334 return super(commanddict, self).__setitem__(k, v)
335
335
336 def commandavailable(self, command, proto):
336 def commandavailable(self, command, proto):
337 """Determine if a command is available for the requested protocol."""
337 """Determine if a command is available for the requested protocol."""
338 assert proto.name in TRANSPORTS
338 assert proto.name in TRANSPORTS
339
339
340 entry = self.get(command)
340 entry = self.get(command)
341
341
342 if not entry:
342 if not entry:
343 return False
343 return False
344
344
345 if proto.name not in entry.transports:
345 if proto.name not in entry.transports:
346 return False
346 return False
347
347
348 return True
348 return True
349
349
350
350
351 def supportedcompengines(ui, role):
351 def supportedcompengines(ui, role):
352 """Obtain the list of supported compression engines for a request."""
352 """Obtain the list of supported compression engines for a request."""
353 assert role in (compression.CLIENTROLE, compression.SERVERROLE)
353 assert role in (compression.CLIENTROLE, compression.SERVERROLE)
354
354
355 compengines = compression.compengines.supportedwireengines(role)
355 compengines = compression.compengines.supportedwireengines(role)
356
356
357 # Allow config to override default list and ordering.
357 # Allow config to override default list and ordering.
358 if role == compression.SERVERROLE:
358 if role == compression.SERVERROLE:
359 configengines = ui.configlist(b'server', b'compressionengines')
359 configengines = ui.configlist(b'server', b'compressionengines')
360 config = b'server.compressionengines'
360 config = b'server.compressionengines'
361 else:
361 else:
362 # This is currently implemented mainly to facilitate testing. In most
362 # This is currently implemented mainly to facilitate testing. In most
363 # cases, the server should be in charge of choosing a compression engine
363 # cases, the server should be in charge of choosing a compression engine
364 # because a server has the most to lose from a sub-optimal choice. (e.g.
364 # because a server has the most to lose from a sub-optimal choice. (e.g.
365 # CPU DoS due to an expensive engine or a network DoS due to poor
365 # CPU DoS due to an expensive engine or a network DoS due to poor
366 # compression ratio).
366 # compression ratio).
367 configengines = ui.configlist(
367 configengines = ui.configlist(
368 b'experimental', b'clientcompressionengines'
368 b'experimental', b'clientcompressionengines'
369 )
369 )
370 config = b'experimental.clientcompressionengines'
370 config = b'experimental.clientcompressionengines'
371
371
372 # No explicit config. Filter out the ones that aren't supposed to be
372 # No explicit config. Filter out the ones that aren't supposed to be
373 # advertised and return default ordering.
373 # advertised and return default ordering.
374 if not configengines:
374 if not configengines:
375 attr = (
375 attr = (
376 b'serverpriority' if role == util.SERVERROLE else b'clientpriority'
376 b'serverpriority' if role == util.SERVERROLE else b'clientpriority'
377 )
377 )
378 return [
378 return [
379 e for e in compengines if getattr(e.wireprotosupport(), attr) > 0
379 e for e in compengines if getattr(e.wireprotosupport(), attr) > 0
380 ]
380 ]
381
381
382 # If compression engines are listed in the config, assume there is a good
382 # If compression engines are listed in the config, assume there is a good
383 # reason for it (like server operators wanting to achieve specific
383 # reason for it (like server operators wanting to achieve specific
384 # performance characteristics). So fail fast if the config references
384 # performance characteristics). So fail fast if the config references
385 # unusable compression engines.
385 # unusable compression engines.
386 validnames = set(e.name() for e in compengines)
386 validnames = set(e.name() for e in compengines)
387 invalidnames = set(e for e in configengines if e not in validnames)
387 invalidnames = set(e for e in configengines if e not in validnames)
388 if invalidnames:
388 if invalidnames:
389 raise error.Abort(
389 raise error.Abort(
390 _(b'invalid compression engine defined in %s: %s')
390 _(b'invalid compression engine defined in %s: %s')
391 % (config, b', '.join(sorted(invalidnames)))
391 % (config, b', '.join(sorted(invalidnames)))
392 )
392 )
393
393
394 compengines = [e for e in compengines if e.name() in configengines]
394 compengines = [e for e in compengines if e.name() in configengines]
395 compengines = sorted(
395 compengines = sorted(
396 compengines, key=lambda e: configengines.index(e.name())
396 compengines, key=lambda e: configengines.index(e.name())
397 )
397 )
398
398
399 if not compengines:
399 if not compengines:
400 raise error.Abort(
400 raise error.Abort(
401 _(
401 _(
402 b'%s config option does not specify any known '
402 b'%s config option does not specify any known '
403 b'compression engines'
403 b'compression engines'
404 )
404 )
405 % config,
405 % config,
406 hint=_(b'usable compression engines: %s')
406 hint=_(b'usable compression engines: %s')
407 % b', '.sorted(validnames),
407 % b', '.sorted(validnames), # pytype: disable=attribute-error
408 )
408 )
409
409
410 return compengines
410 return compengines
411
411
412
412
413 @attr.s
413 @attr.s
414 class encodedresponse(object):
414 class encodedresponse(object):
415 """Represents response data that is already content encoded.
415 """Represents response data that is already content encoded.
416
416
417 Wire protocol version 2 only.
417 Wire protocol version 2 only.
418
418
419 Commands typically emit Python objects that are encoded and sent over the
419 Commands typically emit Python objects that are encoded and sent over the
420 wire. If commands emit an object of this type, the encoding step is bypassed
420 wire. If commands emit an object of this type, the encoding step is bypassed
421 and the content from this object is used instead.
421 and the content from this object is used instead.
422 """
422 """
423
423
424 data = attr.ib()
424 data = attr.ib()
425
425
426
426
427 @attr.s
427 @attr.s
428 class alternatelocationresponse(object):
428 class alternatelocationresponse(object):
429 """Represents a response available at an alternate location.
429 """Represents a response available at an alternate location.
430
430
431 Instances are sent in place of actual response objects when the server
431 Instances are sent in place of actual response objects when the server
432 is sending a "content redirect" response.
432 is sending a "content redirect" response.
433
433
434 Only compatible with wire protocol version 2.
434 Only compatible with wire protocol version 2.
435 """
435 """
436
436
437 url = attr.ib()
437 url = attr.ib()
438 mediatype = attr.ib()
438 mediatype = attr.ib()
439 size = attr.ib(default=None)
439 size = attr.ib(default=None)
440 fullhashes = attr.ib(default=None)
440 fullhashes = attr.ib(default=None)
441 fullhashseed = attr.ib(default=None)
441 fullhashseed = attr.ib(default=None)
442 serverdercerts = attr.ib(default=None)
442 serverdercerts = attr.ib(default=None)
443 servercadercerts = attr.ib(default=None)
443 servercadercerts = attr.ib(default=None)
444
444
445
445
446 @attr.s
446 @attr.s
447 class indefinitebytestringresponse(object):
447 class indefinitebytestringresponse(object):
448 """Represents an object to be encoded to an indefinite length bytestring.
448 """Represents an object to be encoded to an indefinite length bytestring.
449
449
450 Instances are initialized from an iterable of chunks, with each chunk being
450 Instances are initialized from an iterable of chunks, with each chunk being
451 a bytes instance.
451 a bytes instance.
452 """
452 """
453
453
454 chunks = attr.ib()
454 chunks = attr.ib()
General Comments 0
You need to be logged in to leave comments. Login now