-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-127209: Clarify and clean up the separation between BaseServer and TCPServer #127976
base: main
Are you sure you want to change the base?
Conversation
Question: can this break existing code now that we raise a |
I don't think there's much risk. Any code that will hit that function instead of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although I understand that a BaseServer is socket-less, if possible, I'd leave out the updates on the timeout and only raise NotImplementedError
in get_request()
. Are there projects which directly inherit from BaseServer
and define their own socket
property, thereby "implicitly" broken by the change?
If you are willing to how timeout is handled, this requires a true What's New entry to indicate that we no more use self.socket.gettimeout()
.
Finally, I think we should indicate that get_request
raises an exception in a What's New entry because it's still a user-facing change (and it doesn't hurt to remind users) (and that before it was an AttributeError being raised). The rationale is that people could catch AttributeError
but not NotImplementedError
(although I agree that this probably not the best coding practice; better catch both if you're worried about an object missing an interface to implement)
Lib/socketserver.py
Outdated
timeout = self.socket.gettimeout() | ||
if timeout is None: | ||
timeout = self.timeout | ||
elif self.timeout is not None: | ||
timeout = min(timeout, self.timeout) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't this affect users that directly inherit from BaseServer
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new version I just pushed leaves the existing timeout logic in place, but uses hasattr
to make it safe for any subclasses that don't have a socket
attribute. I can put it back to the way it was if you think that's still too much, but this should be less obtrusive while still increasing the range of possibilities for users of the base class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the NEWS entry should be moved out of the documentation category at this point? [EDIT] I moved it to Library
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't really look too much at the issue, and just did a blind review; if I ask stupid questions or bring things up that have already been discussed, forgive me!
.. method:: server_bind() | ||
|
||
Called by the server's constructor to bind the socket to the desired address. | ||
May be overridden. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems more clear to me:
May be overridden. | |
May be overridden by a subclass. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm okay with that, but note that:
a) I didn't write this text, I just moved it to a new section
b) The current language is consistent with usage in the rest of the file. They should probably be all updated together if we're going to make this particular adjustment.
The type of socket used by the server; :const:`socket.SOCK_STREAM` and | ||
:const:`socket.SOCK_DGRAM` are two common values. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only values, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know. This isn't new, I just moved it. I do see that socket documentation says that out of all the possible socket types, only those two "appear to be generally useful", so I think the answer is "probably, but hard to say for sure". Maybe language like "are the most common values
" would work to strengthen the statement.
@@ -280,7 +277,9 @@ def handle_request(self): | |||
""" | |||
# Support people who used socket.settimeout() to escape | |||
# handle_request before self.timeout was available. | |||
timeout = self.socket.gettimeout() | |||
timeout = None | |||
if hasattr(self, "socket"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems a little odd. We assumed that the socket
attribute was present before--why not now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's based on going back to the original intent of making BaseServer and TCPServer separate classes. Originally, there was only TCPServer. BaseServer was created to abstract away from a socket-based interface to allow a more general idea of requests.
The original commit from 2001 suggests the use case of a subclass of BaseServer which connects to a SQL database instead of a socket.
The commit which added this part was later, in 2008, and I think just didn't realize that that was supposed to be part of the separation between the two. This is a minimal adjustment to ensure that existing uses aren't affected, but non-socket servers are possible again.
Must be overridden by subclasses. | ||
""" | ||
raise NotImplementedError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect this has already been brought up, but bring me up to speed--why do this instead of using abc.abstractmethod
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pure conservatism; I wasn't sure about adding abstract methods to a class that doesn't have any right now. I think it would be correct to do so, but this code pre-dates the existence of abstract methods and I didn't feel confident enough to pull that in. I'd be happy to do so if that's generally a safe thing to do.
Co-authored-by: Peter Bierma <[email protected]>
This turned out to mostly be a documentation change. I updated the documentation and docstrings to clarify what's from
BaseServer
and what's added byTCPServer
. For code changes, I added a stub method forBaseServer.get_request
which just raisesNotImplementedError
.While examining the history of the module, it's clear to me that
BaseServer
was intended to not reference the socket used byTCPServer
and it's subclasses. You can see the original commit which createdBaseServer
here: 90cb906The use of
self.socket
inBaseServer.handle_request
was introduced much later, and seems to me like it broke the expected separation. I added the private methodBaseSever._get_timeout
so thatTCPServer
can handlesocket.settimeout()
without requiring alternate implementations ofBaseServer
to have aself.socket
attribute.Unlike
get_request
, nothing inBaseServer
usesfileno
orserver_bind
, so I documented those two as being introduced byTCPServer
rather than create a stub method for them.I think it's also worth noting that
TCPServer
also adds aallow_reuse_port
attribute which is not currently documented. I wasn't sure if there was a reason for that, so I didn't try to add it.As suggested by @picnixz in the issue, this MR leaves the question of whether
BaseServer.get_request
should be marked as abstract for another time. It would make sense to do so, but I haven't checked any possible performance implications, so this is the conservative approach.socketserver.BaseServer
have abstract methods? #127209📚 Documentation preview 📚: https://cpython-previews--127976.org.readthedocs.build/en/127976/library/socketserver.html#module-socketserver