[Twisted-Python] Information on new endpoints API?

Glyph Lefkowitz glyph at twistedmatrix.com
Tue Jun 14 19:37:50 EDT 2011


On Jun 14, 2011, at 2:37 PM, exarkun at twistedmatrix.com wrote:

> On 07:37 am, albert.brandl at weiermayer.com wrote:
>> 

>> We've defined a constant GPS_PORT as "SERIAL:/dev/ttyS2:baudrate=4800"
>> for the production environment and "tcp:1049" for the test environment
> 
> To be clear, though, it sounds like you're defining GPS_PORT in a .py 
> file, and that's more or less what I was speaking against. :)  Instead, 
> I would define GPS_PORT as either SerialEndpoint(reactor, "/dev/ttyS2", 
> baudrate=4800) or TCP4ServerEndpoint(reactor, 1049).

In a rare show of dissent within the cabal[1], I disagree with Jean-Paul on this.

> The rest of your code can still be indifferent to which of these is in play (and you can have a service for either of these using StreamServerEndpointService).

This is the most important point to take away, and I agree with it.  As much as possible, code should interact with endpoints as parameters.  If you need to pass something around, pass the endpoint or the service itself, not the string or the parameters used to construct it.

> If you misspell something or leave off a required argument, though, you're more likely to get an exception that points more directly at the problem.

First, if you find bad error reporting while using the string-using APIs, it would be better to file a bug for improved error handling than to switch to hard-coding your endpoint types.  There's no particular reason that error-reporting needs to be worse in that case.

Second, I hate to say so, but the current error-reporting behavior wasn't designed particularly thoughtfully in either system, so in practice, sometimes you'll get better reporting from one and in some you'll get it from the other.  For example:

>>> from twisted.internet.endpoints import TCP4ServerEndpoint, serverFromString
>>> from twisted.internet import reactor
>>> serverFromString(reactor, 'bananas')
   ...
ValueError: Unqualified strport description passed to 'service'. Use qualified endpoint descriptions; for example, 'tcp:bananas'.
>>> serverFromString(reactor, 'tcp:bananas')
   ...
ValueError: invalid literal for int() with base 10: 'bananas'
>>> TCP4ServerEndpoint(reactor, 'bananas')
<twisted.internet.endpoints.TCP4ServerEndpoint object at 0x103629190>
>>> TCP4ServerEndpoint(reactor)
   ...
TypeError: __init__() takes at least 3 arguments (2 given)
>>> serverFromString(reactor, 'tcp:')
Traceback (most recent call last):
   ...
ValueError: invalid literal for int() with base 10: ''
>>> serverFromString(reactor, "bananas:")
   ...
ValueError: Unknown endpoint type: 'bananas'

Both of these really *should* be saying: "'bananas' is not a valid TCP port number" or "TCP requires a port number".  (Python's error reporting could use some improving here too: which __init__, exactly?)

Personally, I recommend that most developers prefer the string-based APIs, because they allow for more flexibility with less code.  (Granted, not a lot less code: just the N imports for each endpoint type you might support vs. the 1 import for serverFromString or strports.service.)

When you create a TCP4ServerEndpoint, that is an object with a very specific contract.  However, an endpoint created with the "tcp:" prefix passed to serverFromString has an opportunity for more dynamic behavior.  The former must always bind to TCPv4 sockets.  The latter, however, may develop different behavior.  For example, IPv6 support "for free" would be possible with this API, because by using the string-based APIs you are explicitly saying "I am passing some input in here, and I don't know what kind of stream-based transport I might get".  The compatibility contract with this code is much weaker, because it supports plugins, and already might be giving you lots of different kinds of objects depending on your configuration.

The place where I would recommend against using the string-based APIs is in systems which require knowledge of their transports.  For example, if you want to report about the apparent address of your peer in a protocol message with a specific format, you can't correctly do that with an arbitrary endpoint, because you won't know what datatype to expect from the getPeer() and getHost() methods on your transport.  You can't accept any endpoint that serverFromString might give you and then assume it will give you an IPv4Address as a peer.

So, you should be as flexible as you can, but no more.

> Put another way, I suggest writing this:
> 
>    if debug_mode():
>        gps_port = TCP4ServerEndpoint(reactor, 1049)
>    else:
>        gps_port = SerialEndpoint(reactor, "/dev/ttyS2", baudrate=4800)
> 
>    ...
> 
>    gps_service = StreamServerEndpointService(gps_port, gps_factory)
> 
> And save the "tcp:1049"-style strings for your .ini files where you 
> cannot write it that way.

This then begs the question: where does "debug_mode()"'s result come from?  Presumably someone had to indicate a boolean to this code somehow.  It would be simpler for whoever was supplying the value for debug_mode to just supply a value for the endpoint string description instead.  And if it's OK to hard-code the debug_mode boolean, why not just hard-code the string?  Since gps_factory already needs to accept being connected to different transport types, this is 8 lines of code (including the imports, not shown above) where 3 will do fine.

Can this code really only run over plain TCP and serial ports?  If not, then you might have to add an 'if secure()' to the TCP case.  And then you suddenly need a way to get a certificate in there, and all kinds of other information related to TLS.  Better to let the existing string-parsing configuration system do that for you.

-glyph

[1]: There is no cabal.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://twistedmatrix.com/pipermail/twisted-python/attachments/20110614/7fc5e13a/attachment.htm 


More information about the Twisted-Python mailing list