PageRenderTime 25ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/postgresql/python/socket.py

https://github.com/wchorolque/py-postgresql
Python | 118 lines | 72 code | 12 blank | 34 comment | 10 complexity | 4a2ba7308ad99404ca98e44215c4a356 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. ##
  2. # copyright 2009, James William Pye
  3. # http://python.projects.postgresql.org
  4. ##
  5. """
  6. socket tools
  7. """
  8. import random
  9. import socket
  10. import math
  11. import errno
  12. import ssl
  13. __all__ = ['find_available_port', 'SocketFactory']
  14. class SocketFactory(object):
  15. """
  16. Object used to create a socket and connect it.
  17. This is, more or less, a specialized partial() for socket creation.
  18. Additionally, it provides methods and attributes for abstracting
  19. exception management on socket operation.
  20. """
  21. fatal_exception_messages = {
  22. errno.ECONNRESET : 'server explicitly closed the connection',
  23. errno.EPIPE : 'broken connection detected on send',
  24. errno.ECONNREFUSED : 'server refused connection',
  25. }
  26. timeout_exception = socket.timeout
  27. fatal_exception = socket.error
  28. try_again_exception = socket.error
  29. def timed_out(self, err) -> bool:
  30. return type(err) is self.timeout_exception
  31. def try_again(self, err) -> bool:
  32. """
  33. Does the error indicate that the operation should be
  34. tried again?
  35. """
  36. return getattr(err, 'errno', 0) == errno.EINTR
  37. def connection_refused(self, err) -> bool:
  38. """
  39. Does the error indicate that the connection was explicitly
  40. refused by the server?
  41. """
  42. return getattr(err, 'errno', 0) == errno.ECONNREFUSED
  43. @classmethod
  44. def fatal_exception_message(typ, err) -> (str, None):
  45. """
  46. If the exception was fatal to the connection,
  47. what message should be given to the user?
  48. """
  49. return typ.fatal_exception_messages.get(err.errno)
  50. def secure(self, socket : socket.socket) -> ssl.SSLSocket:
  51. "secure a socket with SSL"
  52. if self.socket_secure is not None:
  53. return ssl.wrap_socket(socket, **self.socket_secure)
  54. else:
  55. return ssl.wrap_socket(socket)
  56. def __call__(self, timeout = None):
  57. s = socket.socket(*self.socket_create)
  58. s.settimeout(float(timeout) if timeout is not None else None)
  59. s.connect(self.socket_connect)
  60. s.settimeout(None)
  61. return s
  62. def __init__(self,
  63. socket_create : "positional parameters given to socket.socket()",
  64. socket_connect : "parameter given to socket.connect()",
  65. socket_secure : "keywords given to ssl.wrap_socket" = None,
  66. ):
  67. self.socket_create = socket_create
  68. self.socket_connect = socket_connect
  69. self.socket_secure = socket_secure
  70. def __str__(self):
  71. return 'socket' + repr(self.socket_connect)
  72. def find_available_port(
  73. interface : "attempt to bind to interface" = 'localhost',
  74. address_family : "address family to use (default: AF_INET)" = socket.AF_INET,
  75. limit : "Number tries to make before giving up" = 1024,
  76. port_range = (6600, 56600)
  77. ) -> (int, None):
  78. """
  79. Find an available port on the given interface for the given address family.
  80. Returns a port number that was successfully bound to or `None` if the
  81. attempt limit was reached.
  82. """
  83. i = 0
  84. while i < limit:
  85. i += 1
  86. port = (
  87. math.floor(
  88. random.random() * (port_range[1] - port_range[0])
  89. ) + port_range[0]
  90. )
  91. s = socket.socket(address_family, socket.SOCK_STREAM,)
  92. try:
  93. s.bind(('localhost', port))
  94. except socket.error as e:
  95. if e.errno in (errno.EACCES, errno.EADDRINUSE, errno.EINTR):
  96. # try again
  97. continue
  98. finally:
  99. s.close()
  100. break
  101. else:
  102. port = None
  103. return port