On Jun 14, 2011, at 2:37 PM, [email protected] wrote:

> On 07:37 am, [email protected] 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.

_______________________________________________
Twisted-Python mailing list
[email protected]
http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python

Reply via email to