From c5d525bd48be3327835ce04b8baa2bcf5dd010c2 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 29 Feb 2024 23:36:15 -0500 Subject: [PATCH 01/12] removed EPP connection pool, simplified epplibwrapper, removed socket --- src/epplibwrapper/client.py | 214 +++------ src/epplibwrapper/socket.py | 102 ----- src/epplibwrapper/tests/test_pool.py | 420 +++++++++--------- src/epplibwrapper/utility/pool.py | 151 ------- src/epplibwrapper/utility/pool_error.py | 46 -- src/epplibwrapper/utility/pool_status.py | 12 - src/registrar/config/settings.py | 14 - ...t_name_alter_contact_last_name_and_more.py | 1 - 8 files changed, 281 insertions(+), 679 deletions(-) delete mode 100644 src/epplibwrapper/socket.py delete mode 100644 src/epplibwrapper/utility/pool.py delete mode 100644 src/epplibwrapper/utility/pool_error.py delete mode 100644 src/epplibwrapper/utility/pool_status.py diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 9ed437aef..33875b012 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -2,10 +2,6 @@ import logging -from time import sleep -from gevent import Timeout -from epplibwrapper.utility.pool_status import PoolStatus - try: from epplib.client import Client from epplib import commands @@ -18,8 +14,6 @@ from django.conf import settings from .cert import Cert, Key from .errors import ErrorCode, LoginError, RegistryError -from .socket import Socket -from .utility.pool import EPPConnectionPool logger = logging.getLogger(__name__) @@ -43,8 +37,13 @@ class EPPLibWrapper: ATTN: This should not be used directly. Use `Domain` from domain.py. """ - def __init__(self, start_connection_pool=True) -> None: + def __init__(self) -> None: """Initialize settings which will be used for all connections.""" + # set _client to None initially. In the event that the __init__ fails + # before _client initializes, app should still start and be in a state + # that it can attempt _client initialization on send attempts + logger.info("__init__ called") + self._client = None # prepare (but do not send) a Login command self._login = commands.Login( cl_id=settings.SECRET_REGISTRY_CL_ID, @@ -54,7 +53,16 @@ class EPPLibWrapper: "urn:ietf:params:xml:ns:contact-1.0", ], ) + try: + self._initialize_client() + except Exception: + logger.warning("Unable to configure epplib. Registrar cannot contact registry.", exc_info=True) + def _initialize_client(self) -> None: + """Initialize a client, assuming _login defined. Sets _client to initialized + client. Raises errors if initialization fails. + This method will be called at app initialization, and also during retries.""" + logger.info("_initialize_client called") # establish a client object with a TCP socket transport self._client = Client( SocketTransport( @@ -64,50 +72,44 @@ class EPPLibWrapper: password=settings.SECRET_REGISTRY_KEY_PASSPHRASE, ) ) + try: + # use the _client object to connect + self._client.connect() + response = self._client.send(self._login) + if response.code >= 2000: # type: ignore + self._client.close() + raise LoginError(response.msg) # type: ignore + except TransportError as err: + message = "_initialize_client failed to execute due to a connection error." + logger.error(f"{message} Error: {err}", exc_info=True) + raise RegistryError(message, code=ErrorCode.TRANSPORT_ERROR) from err + except LoginError as err: + raise err + except Exception as err: + message = "_initialize_client failed to execute due to an unknown error." + logger.error(f"{message} Error: {err}", exc_info=True) + raise RegistryError(message) from err - self.pool_options = { - # Pool size - "size": settings.EPP_CONNECTION_POOL_SIZE, - # Which errors the pool should look out for. - # Avoid changing this unless necessary, - # it can and will break things. - "exc_classes": (TransportError,), - # Occasionally pings the registry to keep the connection alive. - # Value in seconds => (keepalive / size) - "keepalive": settings.POOL_KEEP_ALIVE, - } - - self._pool = None - - # Tracks the status of the pool - self.pool_status = PoolStatus() - - if start_connection_pool: - self.start_connection_pool() + def _disconnect(self) -> None: + """Close the connection.""" + logger.info("_disconnect called") + try: + self._client.send(commands.Logout()) + self._client.close() + except Exception: + logger.warning("Connection to registry was not cleanly closed.") def _send(self, command): """Helper function used by `send`.""" + logger.info("_send called") cmd_type = command.__class__.__name__ - # Start a timeout to check if the pool is hanging - timeout = Timeout(settings.POOL_TIMEOUT) - timeout.start() - try: - if not self.pool_status.connection_success: - raise LoginError("Couldn't connect to the registry after three attempts") - with self._pool.get() as connection: - response = connection.send(command) - except Timeout as t: - # If more than one pool exists, - # multiple timeouts can be floating around. - # We need to be specific as to which we are targeting. - if t is timeout: - # Flag that the pool is frozen, - # then restart the pool. - self.pool_status.pool_hanging = True - logger.error("Pool timed out") - self.start_connection_pool() + # check for the condition that the _client was not initialized properly + # at app initialization + if self._client is None: + self._initialize_client() + response = self._client.send(command) except (ValueError, ParsingError) as err: message = f"{cmd_type} failed to execute due to some syntax error." logger.error(f"{message} Error: {err}", exc_info=True) @@ -131,109 +133,38 @@ class EPPLibWrapper: raise RegistryError(response.msg, code=response.code) else: return response - finally: - # Close the timeout no matter what happens - timeout.close() - def send(self, command, *, cleaned=False): - """Login, send the command, then close the connection. Tries 3 times.""" + def _retry(self, command, *, cleaned=False): + """Retry sending a command through EPP by re-initializing the client + and then sending the command.""" + logger.info("_retry called") + # re-initialize by disconnecting and initial + self._disconnect() + self._initialize_client() # try to prevent use of this method without appropriate safeguards if not cleaned: raise ValueError("Please sanitize user input before sending it.") + return self._send(command) - # Reopen the pool if its closed - # Only occurs when a login error is raised, after connection is successful - if not self.pool_status.pool_running: - # We want to reopen the connection pool, - # but we don't want the end user to wait while it opens. - # Raise syntax doesn't allow this, so we use a try/catch - # block. - try: - logger.error("Can't contact the Registry. Pool was not running.") - raise RegistryError("Can't contact the Registry. Pool was not running.") - except RegistryError as err: + def send(self, command, *, cleaned=False): + """Login, send the command, then close the connection. Tries 3 times.""" + logger.info("send called") + # try to prevent use of this method without appropriate safeguards + if not cleaned: + raise ValueError("Please sanitize user input before sending it.") + try: + return self._send(command) + except RegistryError as err: + if ( + err.is_transport_error() + or err.is_connection_error() + or err.is_session_error() + or err.is_server_error() + or err.should_retry() + ): + return self._retry(command) + else: raise err - finally: - # Code execution will halt after here. - # The end user will need to recall .send. - self.start_connection_pool() - - counter = 0 # we'll try 3 times - while True: - try: - return self._send(command) - except RegistryError as err: - if counter < 3 and (err.should_retry() or err.is_transport_error()): - logger.info(f"Retrying transport error. Attempt #{counter+1} of 3.") - counter += 1 - sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms - else: # don't try again - raise err - - def get_pool(self): - """Get the current pool instance""" - return self._pool - - def _create_pool(self, client, login, options): - """Creates and returns new pool instance""" - logger.info("New pool was created") - return EPPConnectionPool(client, login, options) - - def start_connection_pool(self, restart_pool_if_exists=True): - """Starts a connection pool for the registry. - - restart_pool_if_exists -> bool: - If an instance of the pool already exists, - then then that instance will be killed first. - It is generally recommended to keep this enabled. - """ - # Since we reuse the same creds for each pool, we can test on - # one socket, and if successful, then we know we can connect. - if not self._test_registry_connection_success(): - logger.warning("start_connection_pool() -> Cannot contact the Registry") - self.pool_status.connection_success = False - else: - self.pool_status.connection_success = True - - # If this function is reinvoked, then ensure - # that we don't have duplicate data sitting around. - if self._pool is not None and restart_pool_if_exists: - logger.info("Connection pool restarting...") - self.kill_pool() - logger.info("Old pool killed") - - self._pool = self._create_pool(self._client, self._login, self.pool_options) - - self.pool_status.pool_running = True - self.pool_status.pool_hanging = False - - logger.info("Connection pool started") - - def kill_pool(self): - """Kills the existing pool. Use this instead - of self._pool = None, as that doesn't clear - gevent instances.""" - if self._pool is not None: - self._pool.kill_all_connections() - self._pool = None - self.pool_status.pool_running = False - return None - logger.info("kill_pool() was invoked but there was no pool to delete") - - def _test_registry_connection_success(self): - """Check that determines if our login - credentials are valid, and/or if the Registrar - can be contacted - """ - # This is closed in test_connection_success - socket = Socket(self._client, self._login) - can_login = False - - # Something went wrong if this doesn't exist - if hasattr(socket, "test_connection_success"): - can_login = socket.test_connection_success() - - return can_login try: @@ -241,5 +172,4 @@ try: CLIENT = EPPLibWrapper() logger.info("registry client initialized") except Exception: - CLIENT = None # type: ignore logger.warning("Unable to configure epplib. Registrar cannot contact registry.", exc_info=True) diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py deleted file mode 100644 index 79c44aa9a..000000000 --- a/src/epplibwrapper/socket.py +++ /dev/null @@ -1,102 +0,0 @@ -import logging -from time import sleep - -try: - from epplib import commands - from epplib.client import Client -except ImportError: - pass - -from .errors import LoginError - - -logger = logging.getLogger(__name__) - - -class Socket: - """Context manager which establishes a TCP connection with registry.""" - - def __init__(self, client: Client, login: commands.Login) -> None: - """Save the epplib client and login details.""" - self.client = client - self.login = login - - def __enter__(self): - """Runs connect(), which opens a connection with EPPLib.""" - self.connect() - - def __exit__(self, *args, **kwargs): - """Runs disconnect(), which closes a connection with EPPLib.""" - self.disconnect() - - def connect(self): - """Use epplib to connect.""" - logger.info("Opening socket on connection pool") - self.client.connect() - response = self.client.send(self.login) - if self.is_login_error(response.code): - self.client.close() - raise LoginError(response.msg) - return self.client - - def disconnect(self): - """Close the connection.""" - logger.info("Closing socket on connection pool") - try: - self.client.send(commands.Logout()) - self.client.close() - except Exception as err: - logger.warning("Connection to registry was not cleanly closed.") - logger.error(err) - - def send(self, command): - """Sends a command to the registry. - If the RegistryError code is >= 2000, - then this function raises a LoginError. - The calling function should handle this.""" - response = self.client.send(command) - if self.is_login_error(response.code): - self.client.close() - raise LoginError(response.msg) - - return response - - def is_login_error(self, code): - """Returns the result of code >= 2000 for RegistryError. - This indicates that something weird happened on the Registry, - and that we should return a LoginError.""" - return code >= 2000 - - def test_connection_success(self): - """Tests if a successful connection can be made with the registry. - Tries 3 times.""" - # Something went wrong if this doesn't exist - if not hasattr(self.client, "connect"): - logger.warning("self.client does not have a connect attribute") - return False - - counter = 0 # we'll try 3 times - while True: - try: - self.client.connect() - response = self.client.send(self.login) - except (LoginError, OSError) as err: - logger.error(err) - should_retry = True - if isinstance(err, LoginError): - should_retry = err.should_retry() - if should_retry and counter < 3: - counter += 1 - sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms - else: # don't try again - return False - else: - # If we encounter a login error, fail - if self.is_login_error(response.code): - logger.warning("A login error was found in test_connection_success") - return False - - # Otherwise, just return true - return True - finally: - self.disconnect() diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index f8e556445..0cf589ec6 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -5,8 +5,6 @@ from dateutil.tz import tzlocal # type: ignore from django.test import TestCase from epplibwrapper.client import EPPLibWrapper from epplibwrapper.errors import RegistryError -from epplibwrapper.socket import Socket -from epplibwrapper.utility.pool import EPPConnectionPool from registrar.models.domain import registry from contextlib import ExitStack from .common import less_console_noise @@ -27,236 +25,236 @@ logger = logging.getLogger(__name__) class TestConnectionPool(TestCase): """Tests for our connection pooling behaviour""" - def setUp(self): - # Mimic the settings added to settings.py - self.pool_options = { - # Current pool size - "size": 1, - # Which errors the pool should look out for - "exc_classes": (TransportError,), - # Occasionally pings the registry to keep the connection alive. - # Value in seconds => (keepalive / size) - "keepalive": 60, - } + # def setUp(self): + # # Mimic the settings added to settings.py + # self.pool_options = { + # # Current pool size + # "size": 1, + # # Which errors the pool should look out for + # "exc_classes": (TransportError,), + # # Occasionally pings the registry to keep the connection alive. + # # Value in seconds => (keepalive / size) + # "keepalive": 60, + # } - def fake_socket(self, login, client): - # Linter reasons - pw = "none" - # Create a fake client object - fake_client = Client( - SocketTransport( - "none", - cert_file="path/to/cert_file", - key_file="path/to/key_file", - password=pw, - ) - ) + # def fake_socket(self, login, client): + # # Linter reasons + # pw = "none" + # # Create a fake client object + # fake_client = Client( + # SocketTransport( + # "none", + # cert_file="path/to/cert_file", + # key_file="path/to/key_file", + # password=pw, + # ) + # ) - return Socket(fake_client, MagicMock()) + # return Socket(fake_client, MagicMock()) - def patch_success(self): - return True + # def patch_success(self): + # return True - def fake_send(self, command, cleaned=None): - mock = MagicMock( - code=1000, - msg="Command completed successfully", - res_data=None, - cl_tr_id="xkw1uo#2023-10-17T15:29:09.559376", - sv_tr_id="5CcH4gxISuGkq8eqvr1UyQ==-35a", - extensions=[], - msg_q=None, - ) - return mock + # def fake_send(self, command, cleaned=None): + # mock = MagicMock( + # code=1000, + # msg="Command completed successfully", + # res_data=None, + # cl_tr_id="xkw1uo#2023-10-17T15:29:09.559376", + # sv_tr_id="5CcH4gxISuGkq8eqvr1UyQ==-35a", + # extensions=[], + # msg_q=None, + # ) + # return mock - def fake_client(mock_client): - pw = "none" - client = Client( - SocketTransport( - "none", - cert_file="path/to/cert_file", - key_file="path/to/key_file", - password=pw, - ) - ) - return client + # def fake_client(mock_client): + # pw = "none" + # client = Client( + # SocketTransport( + # "none", + # cert_file="path/to/cert_file", + # key_file="path/to/key_file", + # password=pw, + # ) + # ) + # return client - @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) - def test_pool_sends_data(self): - """A .send is invoked on the pool successfully""" - expected_result = { - "cl_tr_id": None, - "code": 1000, - "extensions": [], - "msg": "Command completed successfully", - "msg_q": None, - "res_data": [ - info.InfoDomainResultData( - roid="DF1340360-GOV", - statuses=[ - common.Status( - state="serverTransferProhibited", - description=None, - lang="en", - ), - common.Status(state="inactive", description=None, lang="en"), - ], - cl_id="gov2023-ote", - cr_id="gov2023-ote", - cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()), - up_id="gov2023-ote", - up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()), - tr_date=None, - name="test3.gov", - registrant="TuaWnx9hnm84GCSU", - admins=[], - nsset=None, - keyset=None, - ex_date=datetime.date(2024, 8, 15), - auth_info=info.DomainAuthInfo(pw="2fooBAR123fooBaz"), - ) - ], - "sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a", - } + # @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) + # def test_pool_sends_data(self): + # """A .send is invoked on the pool successfully""" + # expected_result = { + # "cl_tr_id": None, + # "code": 1000, + # "extensions": [], + # "msg": "Command completed successfully", + # "msg_q": None, + # "res_data": [ + # info.InfoDomainResultData( + # roid="DF1340360-GOV", + # statuses=[ + # common.Status( + # state="serverTransferProhibited", + # description=None, + # lang="en", + # ), + # common.Status(state="inactive", description=None, lang="en"), + # ], + # cl_id="gov2023-ote", + # cr_id="gov2023-ote", + # cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()), + # up_id="gov2023-ote", + # up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()), + # tr_date=None, + # name="test3.gov", + # registrant="TuaWnx9hnm84GCSU", + # admins=[], + # nsset=None, + # keyset=None, + # ex_date=datetime.date(2024, 8, 15), + # auth_info=info.DomainAuthInfo(pw="2fooBAR123fooBaz"), + # ) + # ], + # "sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a", + # } - # Mock a response from EPP - def fake_receive(command, cleaned=None): - location = Path(__file__).parent / "utility" / "infoDomain.xml" - xml = (location).read_bytes() - return xml + # # Mock a response from EPP + # def fake_receive(command, cleaned=None): + # location = Path(__file__).parent / "utility" / "infoDomain.xml" + # xml = (location).read_bytes() + # return xml - def do_nothing(command): - pass + # def do_nothing(command): + # pass - # Mock what happens inside the "with" - with ExitStack() as stack: - stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) - stack.enter_context(patch.object(Socket, "connect", self.fake_client)) - stack.enter_context(patch.object(EPPConnectionPool, "kill_all_connections", do_nothing)) - stack.enter_context(patch.object(SocketTransport, "send", self.fake_send)) - stack.enter_context(patch.object(SocketTransport, "receive", fake_receive)) - with less_console_noise(): - # Restart the connection pool - registry.start_connection_pool() - # Pool should be running, and be the right size - self.assertEqual(registry.pool_status.connection_success, True) - self.assertEqual(registry.pool_status.pool_running, True) + # # Mock what happens inside the "with" + # with ExitStack() as stack: + # stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) + # stack.enter_context(patch.object(Socket, "connect", self.fake_client)) + # stack.enter_context(patch.object(EPPConnectionPool, "kill_all_connections", do_nothing)) + # stack.enter_context(patch.object(SocketTransport, "send", self.fake_send)) + # stack.enter_context(patch.object(SocketTransport, "receive", fake_receive)) + # with less_console_noise(): + # # Restart the connection pool + # registry.start_connection_pool() + # # Pool should be running, and be the right size + # self.assertEqual(registry.pool_status.connection_success, True) + # self.assertEqual(registry.pool_status.pool_running, True) - # Send a command - result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) + # # Send a command + # result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - # Should this ever fail, it either means that the schema has changed, - # or the pool is broken. - # If the schema has changed: Update the associated infoDomain.xml file - self.assertEqual(result.__dict__, expected_result) + # # Should this ever fail, it either means that the schema has changed, + # # or the pool is broken. + # # If the schema has changed: Update the associated infoDomain.xml file + # self.assertEqual(result.__dict__, expected_result) - # The number of open pools should match the number of requested ones. - # If it is 0, then they failed to open - self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) - # Kill the connection pool - registry.kill_pool() + # # The number of open pools should match the number of requested ones. + # # If it is 0, then they failed to open + # self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) + # # Kill the connection pool + # registry.kill_pool() - @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) - def test_pool_restarts_on_send(self): - """A .send is invoked, but the pool isn't running. - The pool should restart.""" - expected_result = { - "cl_tr_id": None, - "code": 1000, - "extensions": [], - "msg": "Command completed successfully", - "msg_q": None, - "res_data": [ - info.InfoDomainResultData( - roid="DF1340360-GOV", - statuses=[ - common.Status( - state="serverTransferProhibited", - description=None, - lang="en", - ), - common.Status(state="inactive", description=None, lang="en"), - ], - cl_id="gov2023-ote", - cr_id="gov2023-ote", - cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()), - up_id="gov2023-ote", - up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()), - tr_date=None, - name="test3.gov", - registrant="TuaWnx9hnm84GCSU", - admins=[], - nsset=None, - keyset=None, - ex_date=datetime.date(2024, 8, 15), - auth_info=info.DomainAuthInfo(pw="2fooBAR123fooBaz"), - ) - ], - "sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a", - } + # @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) + # def test_pool_restarts_on_send(self): + # """A .send is invoked, but the pool isn't running. + # The pool should restart.""" + # expected_result = { + # "cl_tr_id": None, + # "code": 1000, + # "extensions": [], + # "msg": "Command completed successfully", + # "msg_q": None, + # "res_data": [ + # info.InfoDomainResultData( + # roid="DF1340360-GOV", + # statuses=[ + # common.Status( + # state="serverTransferProhibited", + # description=None, + # lang="en", + # ), + # common.Status(state="inactive", description=None, lang="en"), + # ], + # cl_id="gov2023-ote", + # cr_id="gov2023-ote", + # cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()), + # up_id="gov2023-ote", + # up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()), + # tr_date=None, + # name="test3.gov", + # registrant="TuaWnx9hnm84GCSU", + # admins=[], + # nsset=None, + # keyset=None, + # ex_date=datetime.date(2024, 8, 15), + # auth_info=info.DomainAuthInfo(pw="2fooBAR123fooBaz"), + # ) + # ], + # "sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a", + # } - # Mock a response from EPP - def fake_receive(command, cleaned=None): - location = Path(__file__).parent / "utility" / "infoDomain.xml" - xml = (location).read_bytes() - return xml + # # Mock a response from EPP + # def fake_receive(command, cleaned=None): + # location = Path(__file__).parent / "utility" / "infoDomain.xml" + # xml = (location).read_bytes() + # return xml - def do_nothing(command): - pass + # def do_nothing(command): + # pass - # Mock what happens inside the "with" - with ExitStack() as stack: - stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) - stack.enter_context(patch.object(Socket, "connect", self.fake_client)) - stack.enter_context(patch.object(EPPConnectionPool, "kill_all_connections", do_nothing)) - stack.enter_context(patch.object(SocketTransport, "send", self.fake_send)) - stack.enter_context(patch.object(SocketTransport, "receive", fake_receive)) - with less_console_noise(): - # Start the connection pool - registry.start_connection_pool() - # Kill the connection pool - registry.kill_pool() + # # Mock what happens inside the "with" + # with ExitStack() as stack: + # stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) + # stack.enter_context(patch.object(Socket, "connect", self.fake_client)) + # stack.enter_context(patch.object(EPPConnectionPool, "kill_all_connections", do_nothing)) + # stack.enter_context(patch.object(SocketTransport, "send", self.fake_send)) + # stack.enter_context(patch.object(SocketTransport, "receive", fake_receive)) + # with less_console_noise(): + # # Start the connection pool + # registry.start_connection_pool() + # # Kill the connection pool + # registry.kill_pool() - self.assertEqual(registry.pool_status.pool_running, False) + # self.assertEqual(registry.pool_status.pool_running, False) - # An exception should be raised as end user will be informed - # that they cannot connect to EPP - with self.assertRaises(RegistryError): - expected = "InfoDomain failed to execute due to a connection error." - result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - self.assertEqual(result, expected) + # # An exception should be raised as end user will be informed + # # that they cannot connect to EPP + # with self.assertRaises(RegistryError): + # expected = "InfoDomain failed to execute due to a connection error." + # result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) + # self.assertEqual(result, expected) - # A subsequent command should be successful, as the pool restarts - result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - # Should this ever fail, it either means that the schema has changed, - # or the pool is broken. - # If the schema has changed: Update the associated infoDomain.xml file - self.assertEqual(result.__dict__, expected_result) + # # A subsequent command should be successful, as the pool restarts + # result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) + # # Should this ever fail, it either means that the schema has changed, + # # or the pool is broken. + # # If the schema has changed: Update the associated infoDomain.xml file + # self.assertEqual(result.__dict__, expected_result) - # The number of open pools should match the number of requested ones. - # If it is 0, then they failed to open - self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) - # Kill the connection pool - registry.kill_pool() + # # The number of open pools should match the number of requested ones. + # # If it is 0, then they failed to open + # self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) + # # Kill the connection pool + # registry.kill_pool() - @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) - def test_raises_connection_error(self): - """A .send is invoked on the pool, but registry connection is lost - right as we send a command.""" + # @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) + # def test_raises_connection_error(self): + # """A .send is invoked on the pool, but registry connection is lost + # right as we send a command.""" - with ExitStack() as stack: - stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) - stack.enter_context(patch.object(Socket, "connect", self.fake_client)) - with less_console_noise(): - # Start the connection pool - registry.start_connection_pool() + # with ExitStack() as stack: + # stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) + # stack.enter_context(patch.object(Socket, "connect", self.fake_client)) + # with less_console_noise(): + # # Start the connection pool + # registry.start_connection_pool() - # Pool should be running - self.assertEqual(registry.pool_status.connection_success, True) - self.assertEqual(registry.pool_status.pool_running, True) + # # Pool should be running + # self.assertEqual(registry.pool_status.connection_success, True) + # self.assertEqual(registry.pool_status.pool_running, True) - # Try to send a command out - should fail - with self.assertRaises(RegistryError): - expected = "InfoDomain failed to execute due to a connection error." - result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - self.assertEqual(result, expected) + # # Try to send a command out - should fail + # with self.assertRaises(RegistryError): + # expected = "InfoDomain failed to execute due to a connection error." + # result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) + # self.assertEqual(result, expected) diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py deleted file mode 100644 index 4f54e14ce..000000000 --- a/src/epplibwrapper/utility/pool.py +++ /dev/null @@ -1,151 +0,0 @@ -import logging -from typing import List -import gevent -from geventconnpool import ConnectionPool -from epplibwrapper.socket import Socket -from epplibwrapper.utility.pool_error import PoolError, PoolErrorCodes - -try: - from epplib.commands import Hello - from epplib.exceptions import TransportError -except ImportError: - pass - -from gevent.lock import BoundedSemaphore -from collections import deque - -logger = logging.getLogger(__name__) - - -class EPPConnectionPool(ConnectionPool): - """A connection pool for EPPLib. - - Args: - client (Client): The client - login (commands.Login): Login creds - options (dict): Options for the ConnectionPool - base class - """ - - def __init__(self, client, login, options: dict): - # For storing shared credentials - self._client = client - self._login = login - - # Keep track of each greenlet - self.greenlets: List[gevent.Greenlet] = [] - - # Define optional pool settings. - # Kept in a dict so that the parent class, - # client.py, can maintain seperation/expandability - self.size = 1 - if "size" in options: - self.size = options["size"] - - self.exc_classes = tuple((TransportError,)) - if "exc_classes" in options: - self.exc_classes = options["exc_classes"] - - self.keepalive = None - if "keepalive" in options: - self.keepalive = options["keepalive"] - - # Determines the period in which new - # gevent threads are spun up. - # This time period is in seconds. So for instance, .1 would be .1 seconds. - self.spawn_frequency = 0.1 - if "spawn_frequency" in options: - self.spawn_frequency = options["spawn_frequency"] - - self.conn: deque = deque() - self.lock = BoundedSemaphore(self.size) - - self.populate_all_connections() - - def _new_connection(self): - socket = self._create_socket(self._client, self._login) - try: - connection = socket.connect() - return connection - except Exception as err: - message = f"Failed to execute due to a registry error: {err}" - logger.error(message, exc_info=True) - # We want to raise a pool error rather than a LoginError here - # because if this occurs internally, we should handle this - # differently than we otherwise would for LoginError. - raise PoolError(code=PoolErrorCodes.NEW_CONNECTION_FAILED) from err - - def _keepalive(self, c): - """Sends a command to the server to keep the connection alive.""" - try: - # Sends a ping to the registry via EPPLib - c.send(Hello()) - except Exception as err: - message = "Failed to keep the connection alive." - logger.error(message, exc_info=True) - raise PoolError(code=PoolErrorCodes.KEEP_ALIVE_FAILED) from err - - def _keepalive_periodic(self): - """Overriding _keepalive_periodic from geventconnpool so that PoolErrors - are properly handled, as opposed to printing to stdout""" - delay = float(self.keepalive) / self.size - while 1: - try: - with self.get() as c: - self._keepalive(c) - except PoolError as err: - logger.error(err.message, exc_info=True) - except self.exc_classes: - # Nothing to do, the pool will generate a new connection later - pass - gevent.sleep(delay) - - def _create_socket(self, client, login) -> Socket: - """Creates and returns a socket instance""" - socket = Socket(client, login) - return socket - - def get_connections(self): - """Returns the connection queue""" - return self.conn - - def kill_all_connections(self): - """Kills all active connections in the pool.""" - try: - if len(self.conn) > 0 or len(self.greenlets) > 0: - logger.info("Attempting to kill connections") - gevent.killall(self.greenlets) - - self.greenlets.clear() - for connection in self.conn: - connection.disconnect() - self.conn.clear() - - # Clear the semaphore - self.lock = BoundedSemaphore(self.size) - logger.info("Finished killing connections") - else: - logger.info("No connections to kill.") - except Exception as err: - logger.error("Could not kill all connections.") - raise PoolError(code=PoolErrorCodes.KILL_ALL_FAILED) from err - - def populate_all_connections(self): - """Generates the connection pool. - If any connections exist, kill them first. - Based off of the __init__ definition for geventconnpool. - """ - if len(self.conn) > 0 or len(self.greenlets) > 0: - self.kill_all_connections() - - # Setup the lock - for i in range(self.size): - self.lock.acquire() - - # Open multiple connections - for i in range(self.size): - self.greenlets.append(gevent.spawn_later(self.spawn_frequency * i, self._addOne)) - - # Open a "keepalive" thread if we want to ping open connections - if self.keepalive: - self.greenlets.append(gevent.spawn(self._keepalive_periodic)) diff --git a/src/epplibwrapper/utility/pool_error.py b/src/epplibwrapper/utility/pool_error.py deleted file mode 100644 index bdf955afe..000000000 --- a/src/epplibwrapper/utility/pool_error.py +++ /dev/null @@ -1,46 +0,0 @@ -from enum import IntEnum - - -class PoolErrorCodes(IntEnum): - """Used in the PoolError class for - error mapping. - - Overview of contact error codes: - - 2000 KILL_ALL_FAILED - - 2001 NEW_CONNECTION_FAILED - - 2002 KEEP_ALIVE_FAILED - """ - - KILL_ALL_FAILED = 2000 - NEW_CONNECTION_FAILED = 2001 - KEEP_ALIVE_FAILED = 2002 - - -class PoolError(Exception): - """ - Overview of contact error codes: - - 2000 KILL_ALL_FAILED - - 2001 NEW_CONNECTION_FAILED - - 2002 KEEP_ALIVE_FAILED - - Note: These are separate from the error codes returned from EppLib - """ - - # Used variables due to linter requirements - kill_failed = "Could not kill all connections. Are multiple pools running?" - conn_failed = "Failed to execute due to a registry error. See previous logs to determine the cause of the error." - alive_failed = "Failed to keep the connection alive. It is likely that the registry returned a LoginError." - _error_mapping = { - PoolErrorCodes.KILL_ALL_FAILED: kill_failed, - PoolErrorCodes.NEW_CONNECTION_FAILED: conn_failed, - PoolErrorCodes.KEEP_ALIVE_FAILED: alive_failed, - } - - def __init__(self, *args, code=None, **kwargs): - super().__init__(*args, **kwargs) - self.code = code - if self.code in self._error_mapping: - self.message = self._error_mapping.get(self.code) - - def __str__(self): - return f"{self.message}" diff --git a/src/epplibwrapper/utility/pool_status.py b/src/epplibwrapper/utility/pool_status.py deleted file mode 100644 index 3a0ae750f..000000000 --- a/src/epplibwrapper/utility/pool_status.py +++ /dev/null @@ -1,12 +0,0 @@ -class PoolStatus: - """A list of Booleans to keep track of Pool Status. - - pool_running -> bool: Tracks if the pool itself is active or not. - connection_success -> bool: Tracks if connection is possible with the registry. - pool_hanging -> pool: Tracks if the pool has exceeded its timeout period. - """ - - def __init__(self): - self.pool_running = False - self.connection_success = False - self.pool_hanging = False diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index bb8e22ad7..3107661b9 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -601,20 +601,6 @@ SECRET_REGISTRY_KEY = secret_registry_key SECRET_REGISTRY_KEY_PASSPHRASE = secret_registry_key_passphrase SECRET_REGISTRY_HOSTNAME = secret_registry_hostname -# Use this variable to set the size of our connection pool in client.py -# WARNING: Setting this value too high could cause frequent app crashes! -# Having too many connections open could cause the sandbox to timeout, -# as the spinup time could exceed the timeout time. -EPP_CONNECTION_POOL_SIZE = 1 - -# Determines the interval in which we ping open connections in seconds -# Calculated as POOL_KEEP_ALIVE / EPP_CONNECTION_POOL_SIZE -POOL_KEEP_ALIVE = 60 - -# Determines how long we try to keep a pool alive for, -# before restarting it. -POOL_TIMEOUT = 60 - # endregion # region: Security and Privacy----------------------------------------------### diff --git a/src/registrar/migrations/0071_alter_contact_first_name_alter_contact_last_name_and_more.py b/src/registrar/migrations/0071_alter_contact_first_name_alter_contact_last_name_and_more.py index bc594138e..9e5eddfc3 100644 --- a/src/registrar/migrations/0071_alter_contact_first_name_alter_contact_last_name_and_more.py +++ b/src/registrar/migrations/0071_alter_contact_first_name_alter_contact_last_name_and_more.py @@ -5,7 +5,6 @@ import phonenumber_field.modelfields class Migration(migrations.Migration): - dependencies = [ ("registrar", "0070_domainapplication_rejection_reason"), ] From 99d34682b1a79d0751e56669e1f3b91554f9d36f Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 1 Mar 2024 22:18:41 -0500 Subject: [PATCH 02/12] test cases written; cleanup; small fix to _retry method signature --- src/epplibwrapper/client.py | 27 +-- src/epplibwrapper/tests/test_client.py | 259 ++++++++++++++++++++++++ src/epplibwrapper/tests/test_pool.py | 260 ------------------------- 3 files changed, 268 insertions(+), 278 deletions(-) create mode 100644 src/epplibwrapper/tests/test_client.py delete mode 100644 src/epplibwrapper/tests/test_pool.py diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 33875b012..408f537a1 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -42,7 +42,6 @@ class EPPLibWrapper: # set _client to None initially. In the event that the __init__ fails # before _client initializes, app should still start and be in a state # that it can attempt _client initialization on send attempts - logger.info("__init__ called") self._client = None # prepare (but do not send) a Login command self._login = commands.Login( @@ -56,13 +55,12 @@ class EPPLibWrapper: try: self._initialize_client() except Exception: - logger.warning("Unable to configure epplib. Registrar cannot contact registry.", exc_info=True) + logger.warning("Unable to configure epplib. Registrar cannot contact registry.") def _initialize_client(self) -> None: """Initialize a client, assuming _login defined. Sets _client to initialized client. Raises errors if initialization fails. This method will be called at app initialization, and also during retries.""" - logger.info("_initialize_client called") # establish a client object with a TCP socket transport self._client = Client( SocketTransport( @@ -81,18 +79,17 @@ class EPPLibWrapper: raise LoginError(response.msg) # type: ignore except TransportError as err: message = "_initialize_client failed to execute due to a connection error." - logger.error(f"{message} Error: {err}", exc_info=True) + logger.error(f"{message} Error: {err}") raise RegistryError(message, code=ErrorCode.TRANSPORT_ERROR) from err except LoginError as err: raise err except Exception as err: message = "_initialize_client failed to execute due to an unknown error." - logger.error(f"{message} Error: {err}", exc_info=True) + logger.error(f"{message} Error: {err}") raise RegistryError(message) from err def _disconnect(self) -> None: """Close the connection.""" - logger.info("_disconnect called") try: self._client.send(commands.Logout()) self._client.close() @@ -101,7 +98,6 @@ class EPPLibWrapper: def _send(self, command): """Helper function used by `send`.""" - logger.info("_send called") cmd_type = command.__class__.__name__ try: @@ -112,21 +108,21 @@ class EPPLibWrapper: response = self._client.send(command) except (ValueError, ParsingError) as err: message = f"{cmd_type} failed to execute due to some syntax error." - logger.error(f"{message} Error: {err}", exc_info=True) + logger.error(f"{message} Error: {err}") raise RegistryError(message) from err except TransportError as err: message = f"{cmd_type} failed to execute due to a connection error." - logger.error(f"{message} Error: {err}", exc_info=True) + logger.error(f"{message} Error: {err}") raise RegistryError(message, code=ErrorCode.TRANSPORT_ERROR) from err except LoginError as err: # For linter due to it not liking this line length text = "failed to execute due to a registry login error." message = f"{cmd_type} {text}" - logger.error(f"{message} Error: {err}", exc_info=True) + logger.error(f"{message} Error: {err}") raise RegistryError(message) from err except Exception as err: message = f"{cmd_type} failed to execute due to an unknown error." - logger.error(f"{message} Error: {err}", exc_info=True) + logger.error(f"{message} Error: {err}") raise RegistryError(message) from err else: if response.code >= 2000: @@ -134,21 +130,16 @@ class EPPLibWrapper: else: return response - def _retry(self, command, *, cleaned=False): + def _retry(self, command): """Retry sending a command through EPP by re-initializing the client and then sending the command.""" - logger.info("_retry called") # re-initialize by disconnecting and initial self._disconnect() self._initialize_client() - # try to prevent use of this method without appropriate safeguards - if not cleaned: - raise ValueError("Please sanitize user input before sending it.") return self._send(command) def send(self, command, *, cleaned=False): """Login, send the command, then close the connection. Tries 3 times.""" - logger.info("send called") # try to prevent use of this method without appropriate safeguards if not cleaned: raise ValueError("Please sanitize user input before sending it.") @@ -172,4 +163,4 @@ try: CLIENT = EPPLibWrapper() logger.info("registry client initialized") except Exception: - logger.warning("Unable to configure epplib. Registrar cannot contact registry.", exc_info=True) + logger.warning("Unable to configure epplib. Registrar cannot contact registry.") diff --git a/src/epplibwrapper/tests/test_client.py b/src/epplibwrapper/tests/test_client.py new file mode 100644 index 000000000..e72d48629 --- /dev/null +++ b/src/epplibwrapper/tests/test_client.py @@ -0,0 +1,259 @@ +from unittest.mock import MagicMock, patch +from django.test import TestCase +from epplibwrapper.client import EPPLibWrapper +from epplibwrapper.errors import RegistryError, LoginError +from .common import less_console_noise +import logging + +try: + from epplib import commands + from epplib.client import Client + from epplib.exceptions import TransportError + from epplib.transport import SocketTransport + from epplib.models import common, info + from epplib.responses import Result +except ImportError: + pass + +logger = logging.getLogger(__name__) + + +class TestClient(TestCase): + """Test the EPPlibwrapper client""" + + def fake_result(self, code, msg): + """Helper function to create a fake Result object""" + return Result( + code=code, + msg=msg, + res_data=[], + cl_tr_id="cl_tr_id", + sv_tr_id="sv_tr_id" + ) + + @patch("epplibwrapper.client.Client") + def test_initialize_client_success(self, mock_client): + """Test when the initialize_client is successful""" + with less_console_noise(): + # Mock the Client instance and its methods + mock_connect = MagicMock() + # Create a mock Result instance + mock_result = MagicMock(spec=Result) + mock_result.code = 200 + mock_result.msg = "Success" + mock_result.res_data = ["data1", "data2"] + mock_result.cl_tr_id = "client_id" + mock_result.sv_tr_id = "server_id" + mock_send = MagicMock(return_value=mock_result) + mock_client.return_value.connect = mock_connect + mock_client.return_value.send = mock_send + + # Create EPPLibWrapper instance and initialize client + wrapper = EPPLibWrapper() + + # Assert that connect method is called once + mock_connect.assert_called_once() + # Assert that _client is not None after initialization + self.assertIsNotNone(wrapper._client) + + @patch("epplibwrapper.client.Client") + def test_initialize_client_transport_error(self, mock_client): + """Test when the send(login) step of initialize_client raises a TransportError.""" + with less_console_noise(): + # Mock the Client instance and its methods + mock_connect = MagicMock() + mock_send = MagicMock(side_effect=TransportError("Transport error")) + mock_client.return_value.connect = mock_connect + mock_client.return_value.send = mock_send + + with self.assertRaises(RegistryError): + # Create EPPLibWrapper instance and initialize client + # if functioning as expected, initial __init__ should except + # and log any Exception raised + wrapper = EPPLibWrapper() + # so call _initialize_client a second time directly to test + # the raised exception + wrapper._initialize_client() + + @patch("epplibwrapper.client.Client") + def test_initialize_client_login_error(self, mock_client): + """Test when the send(login) step of initialize_client returns (2400) comamnd failed code.""" + with less_console_noise(): + # Mock the Client instance and its methods + mock_connect = MagicMock() + # Create a mock Result instance + mock_result = MagicMock(spec=Result) + mock_result.code = 2400 + mock_result.msg = "Login failed" + mock_result.res_data = ["data1", "data2"] + mock_result.cl_tr_id = "client_id" + mock_result.sv_tr_id = "server_id" + mock_send = MagicMock(return_value=mock_result) + mock_client.return_value.connect = mock_connect + mock_client.return_value.send = mock_send + + with self.assertRaises(LoginError): + # Create EPPLibWrapper instance and initialize client + # if functioning as expected, initial __init__ should except + # and log any Exception raised + wrapper = EPPLibWrapper() + # so call _initialize_client a second time directly to test + # the raised exception + wrapper._initialize_client() + + @patch("epplibwrapper.client.Client") + def test_initialize_client_unknown_exception(self, mock_client): + """Test when the send(login) step of initialize_client raises an unexpected Exception.""" + with less_console_noise(): + # Mock the Client instance and its methods + mock_connect = MagicMock() + mock_send = MagicMock(side_effect=Exception("Unknown exception")) + mock_client.return_value.connect = mock_connect + mock_client.return_value.send = mock_send + + with self.assertRaises(RegistryError): + # Create EPPLibWrapper instance and initialize client + # if functioning as expected, initial __init__ should except + # and log any Exception raised + wrapper = EPPLibWrapper() + # so call _initialize_client a second time directly to test + # the raised exception + wrapper._initialize_client() + + @patch("epplibwrapper.client.Client") + def test_initialize_client_fails_recovers_with_send_command(self, mock_client): + """Test when the initialize_client fails on the connect() step. And then a subsequent + call to send() should recover and re-initialize the client and properly return + the successful send command. + Flow: + Initialization step fails at app init + Send command fails (with 2400 code) prompting retry + Client closes and re-initializes, and command is sent successfully""" + with less_console_noise(): + # Mock the Client instance and its methods + # close() should return successfully + mock_close = MagicMock() + mock_client.return_value.close = mock_close + # Create success and failure results + command_success_result = self.fake_result(1000, "Command completed successfully") + command_failure_result = self.fake_result(2400, "Command failed") + # side_effect for the connect() calls + # first connect() should raise an Exception + # subsequent connect() calls should return success + connect_call_count = 0 + def connect_side_effect(*args, **kwargs): + nonlocal connect_call_count + connect_call_count += 1 + if connect_call_count == 1: + raise Exception("Connection failed") + else: + return command_success_result + mock_connect = MagicMock(side_effect=connect_side_effect) + mock_client.return_value.connect = mock_connect + # side_effect for the send() calls + # first send will be the send("InfoDomainCommand") and should fail + # subsequend send() calls should return success + send_call_count = 0 + def send_side_effect(*args, **kwargs): + nonlocal send_call_count + send_call_count += 1 + if send_call_count == 1: + return command_failure_result + else: + return command_success_result + mock_send = MagicMock(side_effect=send_side_effect) + mock_client.return_value.send = mock_send + # Create EPPLibWrapper instance and call send command + wrapper = EPPLibWrapper() + wrapper.send("InfoDomainCommand", cleaned=True) + # two connect() calls should be made, the initial failed connect() + # and the successful connect() during retry() + self.assertEquals(mock_connect.call_count,2) + # close() should only be called once, during retry() + mock_close.assert_called_once() + # send called 4 times: failed send("InfoDomainCommand"), passed send(logout), + # passed send(login), passed send("InfoDomainCommand") + self.assertEquals(mock_send.call_count,4) + + @patch("epplibwrapper.client.Client") + def test_send_command_failed_retries_and_fails_again(self, mock_client): + """Test when the send("InfoDomainCommand) call fails with a 2400, prompting a retry + and the subsequent send("InfoDomainCommand) call also fails with a 2400, raise + a RegistryError + Flow: + Initialization succeeds + Send command fails (with 2400 code) prompting retry + Client closes and re-initializes, and command fails again with 2400""" + with less_console_noise(): + # Mock the Client instance and its methods + # connect() and close() should succeed throughout + mock_connect = MagicMock() + mock_close = MagicMock() + # Create a mock Result instance + send_command_success_result = self.fake_result(1000, "Command completed successfully") + send_command_failure_result = self.fake_result(2400, "Command failed") + # side_effect for send command, passes for all other sends (login, logout), but + # fails for send("InfoDomainCommand") + def side_effect(*args, **kwargs): + if args[0] == "InfoDomainCommand": + return send_command_failure_result + else: + return send_command_success_result + mock_send = MagicMock(side_effect=side_effect) + mock_client.return_value.connect = mock_connect + mock_client.return_value.close = mock_close + mock_client.return_value.send = mock_send + + with self.assertRaises(RegistryError): + # Create EPPLibWrapper instance and initialize client + wrapper = EPPLibWrapper() + # call send, which should throw a RegistryError (after retry) + wrapper.send("InfoDomainCommand", cleaned=True) + # connect() should be called twice, once during initialization, second time + # during retry + self.assertEquals(mock_connect.call_count,2) + # close() is called once during retry + mock_close.assert_called_once() + # send() is called 5 times: send(login), send(command) fails, send(logout) + # send(login), send(command) + self.assertEquals(mock_send.call_count,5) + + @patch("epplibwrapper.client.Client") + def test_send_command_failure_prompts_successful_retry(self, mock_client): + """Test when the send("InfoDomainCommand) call fails with a 2400, prompting a retry + and the subsequent send("InfoDomainCommand) call succeeds + Flow: + Initialization succeeds + Send command fails (with 2400 code) prompting retry + Client closes and re-initializes, and command succeeds""" + with less_console_noise(): + # Mock the Client instance and its methods + # connect() and close() should succeed throughout + mock_connect = MagicMock() + mock_close = MagicMock() + # create success and failure result messages + send_command_success_result = self.fake_result(1000, "Command completed successfully") + send_command_failure_result = self.fake_result(2400, "Command failed") + # side_effect for send call, initial send(login) succeeds during initialization, next send(command) + # fails, subsequent sends (logout, login, command) all succeed + send_call_count = 0 + def side_effect(*args, **kwargs): + nonlocal send_call_count + send_call_count += 1 + if send_call_count == 2: + return send_command_failure_result + else: + return send_command_success_result + mock_send = MagicMock(side_effect=side_effect) + mock_client.return_value.connect = mock_connect + mock_client.return_value.close = mock_close + mock_client.return_value.send = mock_send + # Create EPPLibWrapper instance and initialize client + wrapper = EPPLibWrapper() + wrapper.send("InfoDomainCommand", cleaned=True) + # connect() is called twice, once during initialization of app, once during retry + self.assertEquals(mock_connect.call_count,2) + # close() is called once, during retry + mock_close.assert_called_once() + # send() is called 5 times: send(login), send(command) fail, send(logout), send(login), send(command) + self.assertEquals(mock_send.call_count,5) \ No newline at end of file diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py deleted file mode 100644 index 0cf589ec6..000000000 --- a/src/epplibwrapper/tests/test_pool.py +++ /dev/null @@ -1,260 +0,0 @@ -import datetime -from pathlib import Path -from unittest.mock import MagicMock, patch -from dateutil.tz import tzlocal # type: ignore -from django.test import TestCase -from epplibwrapper.client import EPPLibWrapper -from epplibwrapper.errors import RegistryError -from registrar.models.domain import registry -from contextlib import ExitStack -from .common import less_console_noise -import logging - -try: - from epplib import commands - from epplib.client import Client - from epplib.exceptions import TransportError - from epplib.transport import SocketTransport - from epplib.models import common, info -except ImportError: - pass - -logger = logging.getLogger(__name__) - - -class TestConnectionPool(TestCase): - """Tests for our connection pooling behaviour""" - - # def setUp(self): - # # Mimic the settings added to settings.py - # self.pool_options = { - # # Current pool size - # "size": 1, - # # Which errors the pool should look out for - # "exc_classes": (TransportError,), - # # Occasionally pings the registry to keep the connection alive. - # # Value in seconds => (keepalive / size) - # "keepalive": 60, - # } - - # def fake_socket(self, login, client): - # # Linter reasons - # pw = "none" - # # Create a fake client object - # fake_client = Client( - # SocketTransport( - # "none", - # cert_file="path/to/cert_file", - # key_file="path/to/key_file", - # password=pw, - # ) - # ) - - # return Socket(fake_client, MagicMock()) - - # def patch_success(self): - # return True - - # def fake_send(self, command, cleaned=None): - # mock = MagicMock( - # code=1000, - # msg="Command completed successfully", - # res_data=None, - # cl_tr_id="xkw1uo#2023-10-17T15:29:09.559376", - # sv_tr_id="5CcH4gxISuGkq8eqvr1UyQ==-35a", - # extensions=[], - # msg_q=None, - # ) - # return mock - - # def fake_client(mock_client): - # pw = "none" - # client = Client( - # SocketTransport( - # "none", - # cert_file="path/to/cert_file", - # key_file="path/to/key_file", - # password=pw, - # ) - # ) - # return client - - # @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) - # def test_pool_sends_data(self): - # """A .send is invoked on the pool successfully""" - # expected_result = { - # "cl_tr_id": None, - # "code": 1000, - # "extensions": [], - # "msg": "Command completed successfully", - # "msg_q": None, - # "res_data": [ - # info.InfoDomainResultData( - # roid="DF1340360-GOV", - # statuses=[ - # common.Status( - # state="serverTransferProhibited", - # description=None, - # lang="en", - # ), - # common.Status(state="inactive", description=None, lang="en"), - # ], - # cl_id="gov2023-ote", - # cr_id="gov2023-ote", - # cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()), - # up_id="gov2023-ote", - # up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()), - # tr_date=None, - # name="test3.gov", - # registrant="TuaWnx9hnm84GCSU", - # admins=[], - # nsset=None, - # keyset=None, - # ex_date=datetime.date(2024, 8, 15), - # auth_info=info.DomainAuthInfo(pw="2fooBAR123fooBaz"), - # ) - # ], - # "sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a", - # } - - # # Mock a response from EPP - # def fake_receive(command, cleaned=None): - # location = Path(__file__).parent / "utility" / "infoDomain.xml" - # xml = (location).read_bytes() - # return xml - - # def do_nothing(command): - # pass - - # # Mock what happens inside the "with" - # with ExitStack() as stack: - # stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) - # stack.enter_context(patch.object(Socket, "connect", self.fake_client)) - # stack.enter_context(patch.object(EPPConnectionPool, "kill_all_connections", do_nothing)) - # stack.enter_context(patch.object(SocketTransport, "send", self.fake_send)) - # stack.enter_context(patch.object(SocketTransport, "receive", fake_receive)) - # with less_console_noise(): - # # Restart the connection pool - # registry.start_connection_pool() - # # Pool should be running, and be the right size - # self.assertEqual(registry.pool_status.connection_success, True) - # self.assertEqual(registry.pool_status.pool_running, True) - - # # Send a command - # result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - - # # Should this ever fail, it either means that the schema has changed, - # # or the pool is broken. - # # If the schema has changed: Update the associated infoDomain.xml file - # self.assertEqual(result.__dict__, expected_result) - - # # The number of open pools should match the number of requested ones. - # # If it is 0, then they failed to open - # self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) - # # Kill the connection pool - # registry.kill_pool() - - # @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) - # def test_pool_restarts_on_send(self): - # """A .send is invoked, but the pool isn't running. - # The pool should restart.""" - # expected_result = { - # "cl_tr_id": None, - # "code": 1000, - # "extensions": [], - # "msg": "Command completed successfully", - # "msg_q": None, - # "res_data": [ - # info.InfoDomainResultData( - # roid="DF1340360-GOV", - # statuses=[ - # common.Status( - # state="serverTransferProhibited", - # description=None, - # lang="en", - # ), - # common.Status(state="inactive", description=None, lang="en"), - # ], - # cl_id="gov2023-ote", - # cr_id="gov2023-ote", - # cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()), - # up_id="gov2023-ote", - # up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()), - # tr_date=None, - # name="test3.gov", - # registrant="TuaWnx9hnm84GCSU", - # admins=[], - # nsset=None, - # keyset=None, - # ex_date=datetime.date(2024, 8, 15), - # auth_info=info.DomainAuthInfo(pw="2fooBAR123fooBaz"), - # ) - # ], - # "sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a", - # } - - # # Mock a response from EPP - # def fake_receive(command, cleaned=None): - # location = Path(__file__).parent / "utility" / "infoDomain.xml" - # xml = (location).read_bytes() - # return xml - - # def do_nothing(command): - # pass - - # # Mock what happens inside the "with" - # with ExitStack() as stack: - # stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) - # stack.enter_context(patch.object(Socket, "connect", self.fake_client)) - # stack.enter_context(patch.object(EPPConnectionPool, "kill_all_connections", do_nothing)) - # stack.enter_context(patch.object(SocketTransport, "send", self.fake_send)) - # stack.enter_context(patch.object(SocketTransport, "receive", fake_receive)) - # with less_console_noise(): - # # Start the connection pool - # registry.start_connection_pool() - # # Kill the connection pool - # registry.kill_pool() - - # self.assertEqual(registry.pool_status.pool_running, False) - - # # An exception should be raised as end user will be informed - # # that they cannot connect to EPP - # with self.assertRaises(RegistryError): - # expected = "InfoDomain failed to execute due to a connection error." - # result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - # self.assertEqual(result, expected) - - # # A subsequent command should be successful, as the pool restarts - # result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - # # Should this ever fail, it either means that the schema has changed, - # # or the pool is broken. - # # If the schema has changed: Update the associated infoDomain.xml file - # self.assertEqual(result.__dict__, expected_result) - - # # The number of open pools should match the number of requested ones. - # # If it is 0, then they failed to open - # self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) - # # Kill the connection pool - # registry.kill_pool() - - # @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) - # def test_raises_connection_error(self): - # """A .send is invoked on the pool, but registry connection is lost - # right as we send a command.""" - - # with ExitStack() as stack: - # stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) - # stack.enter_context(patch.object(Socket, "connect", self.fake_client)) - # with less_console_noise(): - # # Start the connection pool - # registry.start_connection_pool() - - # # Pool should be running - # self.assertEqual(registry.pool_status.connection_success, True) - # self.assertEqual(registry.pool_status.pool_running, True) - - # # Try to send a command out - should fail - # with self.assertRaises(RegistryError): - # expected = "InfoDomain failed to execute due to a connection error." - # result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - # self.assertEqual(result, expected) From 78c4bb4a8d01e10d0fa4504e4c74def9334f0a23 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 1 Mar 2024 22:25:58 -0500 Subject: [PATCH 03/12] format changes for satisfying lint, ignore types mainly --- src/epplibwrapper/client.py | 14 +++++----- src/epplibwrapper/tests/test_client.py | 36 ++++++++++++-------------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 408f537a1..bd39a37d6 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -42,7 +42,7 @@ class EPPLibWrapper: # set _client to None initially. In the event that the __init__ fails # before _client initializes, app should still start and be in a state # that it can attempt _client initialization on send attempts - self._client = None + self._client = None # type: ignore # prepare (but do not send) a Login command self._login = commands.Login( cl_id=settings.SECRET_REGISTRY_CL_ID, @@ -62,7 +62,7 @@ class EPPLibWrapper: client. Raises errors if initialization fails. This method will be called at app initialization, and also during retries.""" # establish a client object with a TCP socket transport - self._client = Client( + self._client = Client( # type: ignore SocketTransport( settings.SECRET_REGISTRY_HOSTNAME, cert_file=CERT.filename, @@ -72,10 +72,10 @@ class EPPLibWrapper: ) try: # use the _client object to connect - self._client.connect() - response = self._client.send(self._login) + self._client.connect() # type: ignore + response = self._client.send(self._login) # type:ignore if response.code >= 2000: # type: ignore - self._client.close() + self._client.close() # type:ignore raise LoginError(response.msg) # type: ignore except TransportError as err: message = "_initialize_client failed to execute due to a connection error." @@ -91,8 +91,8 @@ class EPPLibWrapper: def _disconnect(self) -> None: """Close the connection.""" try: - self._client.send(commands.Logout()) - self._client.close() + self._client.send(commands.Logout()) # type: ignore + self._client.close() # type: ignore except Exception: logger.warning("Connection to registry was not cleanly closed.") diff --git a/src/epplibwrapper/tests/test_client.py b/src/epplibwrapper/tests/test_client.py index e72d48629..f95b37dcd 100644 --- a/src/epplibwrapper/tests/test_client.py +++ b/src/epplibwrapper/tests/test_client.py @@ -6,11 +6,7 @@ from .common import less_console_noise import logging try: - from epplib import commands - from epplib.client import Client from epplib.exceptions import TransportError - from epplib.transport import SocketTransport - from epplib.models import common, info from epplib.responses import Result except ImportError: pass @@ -23,14 +19,8 @@ class TestClient(TestCase): def fake_result(self, code, msg): """Helper function to create a fake Result object""" - return Result( - code=code, - msg=msg, - res_data=[], - cl_tr_id="cl_tr_id", - sv_tr_id="sv_tr_id" - ) - + return Result(code=code, msg=msg, res_data=[], cl_tr_id="cl_tr_id", sv_tr_id="sv_tr_id") + @patch("epplibwrapper.client.Client") def test_initialize_client_success(self, mock_client): """Test when the initialize_client is successful""" @@ -141,6 +131,7 @@ class TestClient(TestCase): # first connect() should raise an Exception # subsequent connect() calls should return success connect_call_count = 0 + def connect_side_effect(*args, **kwargs): nonlocal connect_call_count connect_call_count += 1 @@ -148,12 +139,14 @@ class TestClient(TestCase): raise Exception("Connection failed") else: return command_success_result + mock_connect = MagicMock(side_effect=connect_side_effect) mock_client.return_value.connect = mock_connect # side_effect for the send() calls # first send will be the send("InfoDomainCommand") and should fail # subsequend send() calls should return success send_call_count = 0 + def send_side_effect(*args, **kwargs): nonlocal send_call_count send_call_count += 1 @@ -161,19 +154,20 @@ class TestClient(TestCase): return command_failure_result else: return command_success_result + mock_send = MagicMock(side_effect=send_side_effect) - mock_client.return_value.send = mock_send + mock_client.return_value.send = mock_send # Create EPPLibWrapper instance and call send command wrapper = EPPLibWrapper() wrapper.send("InfoDomainCommand", cleaned=True) # two connect() calls should be made, the initial failed connect() # and the successful connect() during retry() - self.assertEquals(mock_connect.call_count,2) + self.assertEquals(mock_connect.call_count, 2) # close() should only be called once, during retry() mock_close.assert_called_once() # send called 4 times: failed send("InfoDomainCommand"), passed send(logout), # passed send(login), passed send("InfoDomainCommand") - self.assertEquals(mock_send.call_count,4) + self.assertEquals(mock_send.call_count, 4) @patch("epplibwrapper.client.Client") def test_send_command_failed_retries_and_fails_again(self, mock_client): @@ -192,6 +186,7 @@ class TestClient(TestCase): # Create a mock Result instance send_command_success_result = self.fake_result(1000, "Command completed successfully") send_command_failure_result = self.fake_result(2400, "Command failed") + # side_effect for send command, passes for all other sends (login, logout), but # fails for send("InfoDomainCommand") def side_effect(*args, **kwargs): @@ -199,6 +194,7 @@ class TestClient(TestCase): return send_command_failure_result else: return send_command_success_result + mock_send = MagicMock(side_effect=side_effect) mock_client.return_value.connect = mock_connect mock_client.return_value.close = mock_close @@ -211,12 +207,12 @@ class TestClient(TestCase): wrapper.send("InfoDomainCommand", cleaned=True) # connect() should be called twice, once during initialization, second time # during retry - self.assertEquals(mock_connect.call_count,2) + self.assertEquals(mock_connect.call_count, 2) # close() is called once during retry mock_close.assert_called_once() # send() is called 5 times: send(login), send(command) fails, send(logout) # send(login), send(command) - self.assertEquals(mock_send.call_count,5) + self.assertEquals(mock_send.call_count, 5) @patch("epplibwrapper.client.Client") def test_send_command_failure_prompts_successful_retry(self, mock_client): @@ -237,6 +233,7 @@ class TestClient(TestCase): # side_effect for send call, initial send(login) succeeds during initialization, next send(command) # fails, subsequent sends (logout, login, command) all succeed send_call_count = 0 + def side_effect(*args, **kwargs): nonlocal send_call_count send_call_count += 1 @@ -244,6 +241,7 @@ class TestClient(TestCase): return send_command_failure_result else: return send_command_success_result + mock_send = MagicMock(side_effect=side_effect) mock_client.return_value.connect = mock_connect mock_client.return_value.close = mock_close @@ -252,8 +250,8 @@ class TestClient(TestCase): wrapper = EPPLibWrapper() wrapper.send("InfoDomainCommand", cleaned=True) # connect() is called twice, once during initialization of app, once during retry - self.assertEquals(mock_connect.call_count,2) + self.assertEquals(mock_connect.call_count, 2) # close() is called once, during retry mock_close.assert_called_once() # send() is called 5 times: send(login), send(command) fail, send(logout), send(login), send(command) - self.assertEquals(mock_send.call_count,5) \ No newline at end of file + self.assertEquals(mock_send.call_count, 5) From 7fd664b94aea1f3f8a65715af52c2763b1026ada Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 4 Mar 2024 13:49:47 -0500 Subject: [PATCH 04/12] small fix to tests outside of the scope of this ticket --- src/registrar/tests/test_admin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index d76f12f35..3010247e7 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -1542,6 +1542,8 @@ class DomainInvitationAdminTest(TestCase): def tearDown(self): """Delete all DomainInvitation objects""" DomainInvitation.objects.all().delete() + User.objects.all().delete() + Contact.objects.all().delete() def test_get_filters(self): """Ensures that our filters are displaying correctly""" From 579d1db2e360c4c4e20bdc198317f156fa0c0012 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 4 Mar 2024 13:54:17 -0500 Subject: [PATCH 05/12] attempting to fix tests broken in pipeline --- src/registrar/tests/test_admin.py | 66 +++++++++++++++---------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 3010247e7..dd78d569c 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -1816,41 +1816,41 @@ class ListHeaderAdminTest(TestCase): self.superuser = create_superuser() def test_changelist_view(self): - with less_console_noise(): - # Have to get creative to get past linter - p = "adminpass" - self.client.login(username="superuser", password=p) - # Mock a user - user = mock_user() - # Make the request using the Client class - # which handles CSRF - # Follow=True handles the redirect - response = self.client.get( - "/admin/registrar/domainapplication/", + # with less_console_noise(): + # Have to get creative to get past linter + p = "adminpass" + self.client.login(username="superuser", password=p) + # Mock a user + user = mock_user() + # Make the request using the Client class + # which handles CSRF + # Follow=True handles the redirect + response = self.client.get( + "/admin/registrar/domainapplication/", + { + "status__exact": "started", + "investigator__id__exact": user.id, + "q": "Hello", + }, + follow=True, + ) + # Assert that the filters and search_query are added to the extra_context + self.assertIn("filters", response.context) + self.assertIn("search_query", response.context) + # Assert the content of filters and search_query + filters = response.context["filters"] + search_query = response.context["search_query"] + self.assertEqual(search_query, "Hello") + self.assertEqual( + filters, + [ + {"parameter_name": "status", "parameter_value": "started"}, { - "status__exact": "started", - "investigator__id__exact": user.id, - "q": "Hello", + "parameter_name": "investigator", + "parameter_value": user.first_name + " " + user.last_name, }, - follow=True, - ) - # Assert that the filters and search_query are added to the extra_context - self.assertIn("filters", response.context) - self.assertIn("search_query", response.context) - # Assert the content of filters and search_query - filters = response.context["filters"] - search_query = response.context["search_query"] - self.assertEqual(search_query, "Hello") - self.assertEqual( - filters, - [ - {"parameter_name": "status", "parameter_value": "started"}, - { - "parameter_name": "investigator", - "parameter_value": user.first_name + " " + user.last_name, - }, - ], - ) + ], + ) def test_get_filters(self): with less_console_noise(): From 0171e57cefa517361d817aee75d2be9f6cdb16e2 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 4 Mar 2024 14:02:42 -0500 Subject: [PATCH 06/12] adding tblib to diagnose test failures better --- src/Pipfile | 1 + src/Pipfile.lock | 579 ++++++++++++++++++++++--------------------- src/requirements.txt | 35 +-- 3 files changed, 314 insertions(+), 301 deletions(-) diff --git a/src/Pipfile b/src/Pipfile index 51417d578..80a0761a1 100644 --- a/src/Pipfile +++ b/src/Pipfile @@ -30,6 +30,7 @@ greenlet = "*" gevent = "*" fred-epplib = {git = "https://github.com/cisagov/epplib.git", ref = "master"} geventconnpool = {git = "https://github.com/rasky/geventconnpool.git", ref = "1bbb93a714a331a069adf27265fe582d9ba7ecd4"} +tblib = "*" [dev-packages] django-debug-toolbar = "*" diff --git a/src/Pipfile.lock b/src/Pipfile.lock index 7d511a0e5..2e08b0628 100644 --- a/src/Pipfile.lock +++ b/src/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "a672aeb8951fd850e90ad87c6f03cf71e2fc2b387d56fd3942361cb0b45bb449" + "sha256": "b5d93b1b9ccafc37019276a222957544bab3f1f46b5dab8a0f2ffc2e5c9e1678" }, "pipfile-spec": 6, "requires": {}, @@ -32,29 +32,29 @@ }, "boto3": { "hashes": [ - "sha256:65acfe7f1cf2a9b7df3d4edb87c8022e02685825bd1957e7bb678cc0d09f5e5f", - "sha256:73f5ec89cb3ddb3ed577317889fd2f2df783f66b6502a9a4239979607e33bf74" + "sha256:8b3f5cc7fbedcbb22271c328039df8a6ab343001e746e0cdb24774c426cadcf8", + "sha256:f201b6a416f809283d554c652211eecec9fe3a52ed4063dab3f3e7aea7571d9c" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.34.37" + "version": "==1.34.54" }, "botocore": { "hashes": [ - "sha256:2a5bf33aacd2d970afd3d492e179e06ea98a5469030d5cfe7a2ad9995f7bb2ef", - "sha256:3c46ddb1679e6ef45ca78b48665398636bda532a07cd476e4b500697d13d9a99" + "sha256:4061ff4be3efcf53547ebadf2c94d419dfc8be7beec24e9fa1819599ffd936fa", + "sha256:bf215d93e9d5544c593962780d194e74c6ee40b883d0b885e62ef35fc0ec01e5" ], "markers": "python_version >= '3.8'", - "version": "==1.34.37" + "version": "==1.34.54" }, "cachetools": { "hashes": [ - "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2", - "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1" + "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945", + "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==5.3.2" + "version": "==5.3.3" }, "certifi": { "hashes": [ @@ -228,41 +228,41 @@ }, "cryptography": { "hashes": [ - "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380", - "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589", - "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea", - "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65", - "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a", - "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3", - "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008", - "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1", - "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2", - "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635", - "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2", - "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90", - "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee", - "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a", - "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242", - "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12", - "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2", - "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d", - "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be", - "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee", - "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6", - "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529", - "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929", - "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1", - "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6", - "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a", - "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446", - "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9", - "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888", - "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4", - "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33", - "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f" + "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee", + "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576", + "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d", + "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30", + "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413", + "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb", + "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da", + "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4", + "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd", + "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc", + "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8", + "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1", + "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc", + "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e", + "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8", + "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940", + "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400", + "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7", + "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16", + "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278", + "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74", + "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec", + "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1", + "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2", + "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c", + "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922", + "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a", + "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6", + "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1", + "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e", + "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac", + "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7" ], "markers": "python_version >= '3.7'", - "version": "==42.0.2" + "version": "==42.0.5" }, "defusedxml": { "hashes": [ @@ -330,11 +330,11 @@ }, "django-csp": { "hashes": [ - "sha256:01443a07723f9a479d498bd7bb63571aaa771e690f64bde515db6cdb76e8041a", - "sha256:01eda02ad3f10261c74131cdc0b5a6a62b7c7ad4fd017fbefb7a14776e0a9727" + "sha256:19b2978b03fcd73517d7d67acbc04fbbcaec0facc3e83baa502965892d1e0719", + "sha256:ef0f1a9f7d8da68ae6e169c02e9ac661c0ecf04db70e0d1d85640512a68471c0" ], "index": "pypi", - "version": "==3.7" + "version": "==3.8" }, "django-fsm": { "hashes": [ @@ -384,12 +384,12 @@ }, "faker": { "hashes": [ - "sha256:60e89e5c0b584e285a7db05eceba35011a241954afdab2853cb246c8a56700a2", - "sha256:b7f76bb1b2ac4cdc54442d955e36e477c387000f31ce46887fb9722a041be60b" + "sha256:117ce1a2805c1bc5ca753b3dc6f9d567732893b2294b827d3164261ee8f20267", + "sha256:458d93580de34403a8dec1e8d5e6be2fee96c4deca63b95d71df7a6a80a690de" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==23.1.0" + "version": "==23.3.0" }, "fred-epplib": { "git": "https://github.com/cisagov/epplib.git", @@ -404,57 +404,59 @@ }, "future": { "hashes": [ - "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307" + "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", + "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05" ], "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.18.3" + "version": "==1.0.0" }, "gevent": { "hashes": [ - "sha256:272cffdf535978d59c38ed837916dfd2b5d193be1e9e5dcc60a5f4d5025dd98a", - "sha256:2c7b5c9912378e5f5ccf180d1fdb1e83f42b71823483066eddbe10ef1a2fcaa2", - "sha256:36a549d632c14684bcbbd3014a6ce2666c5f2a500f34d58d32df6c9ea38b6535", - "sha256:4368f341a5f51611411ec3fc62426f52ac3d6d42eaee9ed0f9eebe715c80184e", - "sha256:43daf68496c03a35287b8b617f9f91e0e7c0d042aebcc060cadc3f049aadd653", - "sha256:455e5ee8103f722b503fa45dedb04f3ffdec978c1524647f8ba72b4f08490af1", - "sha256:45792c45d60f6ce3d19651d7fde0bc13e01b56bb4db60d3f32ab7d9ec467374c", - "sha256:4e24c2af9638d6c989caffc691a039d7c7022a31c0363da367c0d32ceb4a0648", - "sha256:52b4abf28e837f1865a9bdeef58ff6afd07d1d888b70b6804557e7908032e599", - "sha256:52e9f12cd1cda96603ce6b113d934f1aafb873e2c13182cf8e86d2c5c41982ea", - "sha256:5f3c781c84794926d853d6fb58554dc0dcc800ba25c41d42f6959c344b4db5a6", - "sha256:62d121344f7465e3739989ad6b91f53a6ca9110518231553fe5846dbe1b4518f", - "sha256:65883ac026731ac112184680d1f0f1e39fa6f4389fd1fc0bf46cc1388e2599f9", - "sha256:707904027d7130ff3e59ea387dddceedb133cc742b00b3ffe696d567147a9c9e", - "sha256:72c002235390d46f94938a96920d8856d4ffd9ddf62a303a0d7c118894097e34", - "sha256:7532c17bc6c1cbac265e751b95000961715adef35a25d2b0b1813aa7263fb397", - "sha256:78eebaf5e73ff91d34df48f4e35581ab4c84e22dd5338ef32714264063c57507", - "sha256:7c1abc6f25f475adc33e5fc2dbcc26a732608ac5375d0d306228738a9ae14d3b", - "sha256:7c28e38dcde327c217fdafb9d5d17d3e772f636f35df15ffae2d933a5587addd", - "sha256:7ccf0fd378257cb77d91c116e15c99e533374a8153632c48a3ecae7f7f4f09fe", - "sha256:921dda1c0b84e3d3b1778efa362d61ed29e2b215b90f81d498eb4d8eafcd0b7a", - "sha256:a2898b7048771917d85a1d548fd378e8a7b2ca963db8e17c6d90c76b495e0e2b", - "sha256:a3c5e9b1f766a7a64833334a18539a362fb563f6c4682f9634dea72cbe24f771", - "sha256:ada07076b380918829250201df1d016bdafb3acf352f35e5693b59dceee8dd2e", - "sha256:b101086f109168b23fa3586fccd1133494bdb97f86920a24dc0b23984dc30b69", - "sha256:bf456bd6b992eb0e1e869e2fd0caf817f0253e55ca7977fd0e72d0336a8c1c6a", - "sha256:bf7af500da05363e66f122896012acb6e101a552682f2352b618e541c941a011", - "sha256:c3e5d2fa532e4d3450595244de8ccf51f5721a05088813c1abd93ad274fe15e7", - "sha256:c84d34256c243b0a53d4335ef0bc76c735873986d478c53073861a92566a8d71", - "sha256:d163d59f1be5a4c4efcdd13c2177baaf24aadf721fdf2e1af9ee54a998d160f5", - "sha256:d57737860bfc332b9b5aa438963986afe90f49645f6e053140cfa0fa1bdae1ae", - "sha256:dbb22a9bbd6a13e925815ce70b940d1578dbe5d4013f20d23e8a11eddf8d14a7", - "sha256:dcb8612787a7f4626aa881ff15ff25439561a429f5b303048f0fca8a1c781c39", - "sha256:dd6c32ab977ecf7c7b8c2611ed95fa4aaebd69b74bf08f4b4960ad516861517d", - "sha256:de350fde10efa87ea60d742901e1053eb2127ebd8b59a7d3b90597eb4e586599", - "sha256:e1ead6863e596a8cc2a03e26a7a0981f84b6b3e956101135ff6d02df4d9a6b07", - "sha256:ed7a048d3e526a5c1d55c44cb3bc06cfdc1947d06d45006cc4cf60dedc628904", - "sha256:f632487c87866094546a74eefbca2c74c1d03638b715b6feb12e80120960185a", - "sha256:fae8d5b5b8fa2a8f63b39f5447168b02db10c888a3e387ed7af2bd1b8612e543", - "sha256:fde6402c5432b835fbb7698f1c7f2809c8d6b2bd9d047ac1f5a7c1d5aa569303" + "sha256:03aa5879acd6b7076f6a2a307410fb1e0d288b84b03cdfd8c74db8b4bc882fc5", + "sha256:117e5837bc74a1673605fb53f8bfe22feb6e5afa411f524c835b2ddf768db0de", + "sha256:141a2b24ad14f7b9576965c0c84927fc85f824a9bb19f6ec1e61e845d87c9cd8", + "sha256:14532a67f7cb29fb055a0e9b39f16b88ed22c66b96641df8c04bdc38c26b9ea5", + "sha256:1dffb395e500613e0452b9503153f8f7ba587c67dd4a85fc7cd7aa7430cb02cc", + "sha256:2955eea9c44c842c626feebf4459c42ce168685aa99594e049d03bedf53c2800", + "sha256:2ae3a25ecce0a5b0cd0808ab716bfca180230112bb4bc89b46ae0061d62d4afe", + "sha256:2e9ac06f225b696cdedbb22f9e805e2dd87bf82e8fa5e17756f94e88a9d37cf7", + "sha256:368a277bd9278ddb0fde308e6a43f544222d76ed0c4166e0d9f6b036586819d9", + "sha256:3adfb96637f44010be8abd1b5e73b5070f851b817a0b182e601202f20fa06533", + "sha256:3d5325ccfadfd3dcf72ff88a92fb8fc0b56cacc7225f0f4b6dcf186c1a6eeabc", + "sha256:432fc76f680acf7cf188c2ee0f5d3ab73b63c1f03114c7cd8a34cebbe5aa2056", + "sha256:44098038d5e2749b0784aabb27f1fcbb3f43edebedf64d0af0d26955611be8d6", + "sha256:5a1df555431f5cd5cc189a6ee3544d24f8c52f2529134685f1e878c4972ab026", + "sha256:6c47ae7d1174617b3509f5d884935e788f325eb8f1a7efc95d295c68d83cce40", + "sha256:6f947a9abc1a129858391b3d9334c45041c08a0f23d14333d5b844b6e5c17a07", + "sha256:782a771424fe74bc7e75c228a1da671578c2ba4ddb2ca09b8f959abdf787331e", + "sha256:7899a38d0ae7e817e99adb217f586d0a4620e315e4de577444ebeeed2c5729be", + "sha256:7b00f8c9065de3ad226f7979154a7b27f3b9151c8055c162332369262fc025d8", + "sha256:8f4b8e777d39013595a7740b4463e61b1cfe5f462f1b609b28fbc1e4c4ff01e5", + "sha256:90cbac1ec05b305a1b90ede61ef73126afdeb5a804ae04480d6da12c56378df1", + "sha256:918cdf8751b24986f915d743225ad6b702f83e1106e08a63b736e3a4c6ead789", + "sha256:9202f22ef811053077d01f43cc02b4aaf4472792f9fd0f5081b0b05c926cca19", + "sha256:94138682e68ec197db42ad7442d3cf9b328069c3ad8e4e5022e6b5cd3e7ffae5", + "sha256:968581d1717bbcf170758580f5f97a2925854943c45a19be4d47299507db2eb7", + "sha256:9d8d0642c63d453179058abc4143e30718b19a85cbf58c2744c9a63f06a1d388", + "sha256:a7ceb59986456ce851160867ce4929edaffbd2f069ae25717150199f8e1548b8", + "sha256:b9913c45d1be52d7a5db0c63977eebb51f68a2d5e6fd922d1d9b5e5fd758cc98", + "sha256:bde283313daf0b34a8d1bab30325f5cb0f4e11b5869dbe5bc61f8fe09a8f66f3", + "sha256:bf5b9c72b884c6f0c4ed26ef204ee1f768b9437330422492c319470954bc4cc7", + "sha256:ca80b121bbec76d7794fcb45e65a7eca660a76cc1a104ed439cdbd7df5f0b060", + "sha256:cdf66977a976d6a3cfb006afdf825d1482f84f7b81179db33941f2fc9673bb1d", + "sha256:d4faf846ed132fd7ebfbbf4fde588a62d21faa0faa06e6f468b7faa6f436b661", + "sha256:d7f87c2c02e03d99b95cfa6f7a776409083a9e4d468912e18c7680437b29222c", + "sha256:dd23df885318391856415e20acfd51a985cba6919f0be78ed89f5db9ff3a31cb", + "sha256:f5de3c676e57177b38857f6e3cdfbe8f38d1cd754b63200c0615eaa31f514b4f", + "sha256:f5e8e8d60e18d5f7fd49983f0c4696deeddaf6e608fbab33397671e2fcc6cc91", + "sha256:f7cac622e11b4253ac4536a654fe221249065d9a69feb6cdcd4d9af3503602e0", + "sha256:f8a04cf0c5b7139bc6368b461257d4a757ea2fe89b3773e494d235b7dd51119f", + "sha256:f8bb35ce57a63c9a6896c71a285818a3922d8ca05d150fd1fe49a7f57287b836", + "sha256:fbfdce91239fe306772faab57597186710d5699213f4df099d1612da7320d682" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==23.9.1" + "version": "==24.2.1" }, "geventconnpool": { "git": "https://github.com/rasky/geventconnpool.git", @@ -710,11 +712,11 @@ }, "marshmallow": { "hashes": [ - "sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd", - "sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9" + "sha256:20f53be28c6e374a711a16165fb22a8dc6003e3f7cda1285e3ca777b9193885b", + "sha256:e7997f83571c7fd476042c2c188e4ee8a78900ca5e74bd9c8097afa56624e9bd" ], "markers": "python_version >= '3.8'", - "version": "==3.20.2" + "version": "==3.21.0" }, "oic": { "hashes": [ @@ -742,10 +744,10 @@ }, "phonenumberslite": { "hashes": [ - "sha256:2b04a53401d01ab42564c1abc762fc9808ad398e71dacfa3b38d4321e112ecb3", - "sha256:74e3ee63dfa2bb562ce2e6ce74ce76ae74a2f81472005b80343235fb43426db4" + "sha256:137d53d5d78dca30bc2becf81a3e2ac74deb8f0997e9bbe44de515ece4bd92bd", + "sha256:e1f4359bff90c86d1b52db0e726d3334df00cc7d9c9c2ef66561d5f7a774d4ba" ], - "version": "==8.13.29" + "version": "==8.13.31" }, "psycopg2-binary": { "hashes": [ @@ -874,104 +876,104 @@ }, "pydantic": { "hashes": [ - "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f", - "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9" + "sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a", + "sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f" ], "markers": "python_version >= '3.8'", - "version": "==2.6.1" + "version": "==2.6.3" }, "pydantic-core": { "hashes": [ - "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379", - "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06", - "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05", - "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7", - "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753", - "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a", - "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731", - "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc", - "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380", - "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3", - "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c", - "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11", - "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990", - "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a", - "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2", - "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8", - "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97", - "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a", - "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8", - "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef", - "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77", - "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33", - "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82", - "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5", - "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b", - "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55", - "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e", - "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b", - "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7", - "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec", - "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc", - "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469", - "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b", - "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20", - "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e", - "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d", - "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f", - "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b", - "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039", - "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e", - "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2", - "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f", - "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b", - "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc", - "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8", - "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522", - "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e", - "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784", - "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a", - "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890", - "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485", - "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545", - "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f", - "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943", - "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878", - "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f", - "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17", - "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7", - "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286", - "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c", - "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb", - "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646", - "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978", - "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8", - "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15", - "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272", - "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2", - "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55", - "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf", - "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545", - "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4", - "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a", - "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804", - "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4", - "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0", - "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a", - "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113", - "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d", - "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25" + "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a", + "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed", + "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979", + "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff", + "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5", + "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45", + "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340", + "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad", + "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23", + "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6", + "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7", + "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241", + "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda", + "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187", + "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba", + "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c", + "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2", + "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c", + "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132", + "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf", + "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972", + "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db", + "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade", + "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4", + "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8", + "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f", + "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9", + "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48", + "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec", + "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d", + "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9", + "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb", + "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4", + "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89", + "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c", + "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9", + "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da", + "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac", + "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b", + "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf", + "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e", + "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137", + "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1", + "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b", + "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8", + "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e", + "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053", + "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01", + "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe", + "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd", + "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805", + "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183", + "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8", + "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99", + "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820", + "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074", + "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256", + "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8", + "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975", + "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad", + "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e", + "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca", + "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df", + "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b", + "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a", + "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a", + "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721", + "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a", + "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f", + "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2", + "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97", + "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6", + "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed", + "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc", + "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1", + "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe", + "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120", + "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f", + "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a" ], "markers": "python_version >= '3.8'", - "version": "==2.16.2" + "version": "==2.16.3" }, "pydantic-settings": { "hashes": [ - "sha256:26b1492e0a24755626ac5e6d715e9077ab7ad4fb5f19a8b7ed7011d52f36141c", - "sha256:7621c0cb5d90d1140d2f0ef557bdf03573aac7035948109adf2574770b77605a" + "sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed", + "sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091" ], "markers": "python_version >= '3.8'", - "version": "==2.1.0" + "version": "==2.2.1" }, "pyjwkest": { "hashes": [ @@ -982,11 +984,11 @@ }, "python-dateutil": { "hashes": [ - "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", - "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8.2" + "version": "==2.9.0.post0" }, "python-dotenv": { "hashes": [ @@ -1015,11 +1017,11 @@ }, "setuptools": { "hashes": [ - "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05", - "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78" + "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56", + "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8" ], "markers": "python_version >= '3.8'", - "version": "==69.0.3" + "version": "==69.1.1" }, "six": { "hashes": [ @@ -1037,14 +1039,23 @@ "markers": "python_version >= '3.5'", "version": "==0.4.4" }, - "typing-extensions": { + "tblib": { "hashes": [ - "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783", - "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd" + "sha256:80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129", + "sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==4.9.0" + "version": "==3.0.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475", + "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==4.10.0" }, "urllib3": { "hashes": [ @@ -1073,45 +1084,45 @@ }, "zope.interface": { "hashes": [ - "sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff", - "sha256:13b7d0f2a67eb83c385880489dbb80145e9d344427b4262c49fbf2581677c11c", - "sha256:1f294a15f7723fc0d3b40701ca9b446133ec713eafc1cc6afa7b3d98666ee1ac", - "sha256:239a4a08525c080ff833560171d23b249f7f4d17fcbf9316ef4159f44997616f", - "sha256:2f8d89721834524a813f37fa174bac074ec3d179858e4ad1b7efd4401f8ac45d", - "sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309", - "sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736", - "sha256:387545206c56b0315fbadb0431d5129c797f92dc59e276b3ce82db07ac1c6179", - "sha256:43b576c34ef0c1f5a4981163b551a8781896f2a37f71b8655fd20b5af0386abb", - "sha256:57d0a8ce40ce440f96a2c77824ee94bf0d0925e6089df7366c2272ccefcb7941", - "sha256:5a804abc126b33824a44a7aa94f06cd211a18bbf31898ba04bd0924fbe9d282d", - "sha256:67be3ca75012c6e9b109860820a8b6c9a84bfb036fbd1076246b98e56951ca92", - "sha256:6af47f10cfc54c2ba2d825220f180cc1e2d4914d783d6fc0cd93d43d7bc1c78b", - "sha256:6dc998f6de015723196a904045e5a2217f3590b62ea31990672e31fbc5370b41", - "sha256:70d2cef1bf529bff41559be2de9d44d47b002f65e17f43c73ddefc92f32bf00f", - "sha256:7ebc4d34e7620c4f0da7bf162c81978fce0ea820e4fa1e8fc40ee763839805f3", - "sha256:964a7af27379ff4357dad1256d9f215047e70e93009e532d36dcb8909036033d", - "sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8", - "sha256:9b9bc671626281f6045ad61d93a60f52fd5e8209b1610972cf0ef1bbe6d808e3", - "sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1", - "sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1", - "sha256:a41f87bb93b8048fe866fa9e3d0c51e27fe55149035dcf5f43da4b56732c0a40", - "sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d", - "sha256:ad54ed57bdfa3254d23ae04a4b1ce405954969c1b0550cc2d1d2990e8b439de1", - "sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605", - "sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7", - "sha256:bbe81def9cf3e46f16ce01d9bfd8bea595e06505e51b7baf45115c77352675fd", - "sha256:c9559138690e1bd4ea6cd0954d22d1e9251e8025ce9ede5d0af0ceae4a401e43", - "sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0", - "sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b", - "sha256:e441e8b7d587af0414d25e8d05e27040d78581388eed4c54c30c0c91aad3a379", - "sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a", - "sha256:ef43ee91c193f827e49599e824385ec7c7f3cd152d74cb1dfe02cb135f264d83", - "sha256:ef467d86d3cfde8b39ea1b35090208b0447caaabd38405420830f7fd85fbdd56", - "sha256:f89b28772fc2562ed9ad871c865f5320ef761a7fcc188a935e21fe8b31a38ca9", - "sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de" + "sha256:02adbab560683c4eca3789cc0ac487dcc5f5a81cc48695ec247f00803cafe2fe", + "sha256:14e02a6fc1772b458ebb6be1c276528b362041217b9ca37e52ecea2cbdce9fac", + "sha256:25e0af9663eeac6b61b231b43c52293c2cb7f0c232d914bdcbfd3e3bd5c182ad", + "sha256:2606955a06c6852a6cff4abeca38346ed01e83f11e960caa9a821b3626a4467b", + "sha256:396f5c94654301819a7f3a702c5830f0ea7468d7b154d124ceac823e2419d000", + "sha256:3b240883fb43160574f8f738e6d09ddbdbf8fa3e8cea051603d9edfd947d9328", + "sha256:3b6c62813c63c543a06394a636978b22dffa8c5410affc9331ce6cdb5bfa8565", + "sha256:4ae9793f114cee5c464cc0b821ae4d36e1eba961542c6086f391a61aee167b6f", + "sha256:4bce517b85f5debe07b186fc7102b332676760f2e0c92b7185dd49c138734b70", + "sha256:4d45d2ba8195850e3e829f1f0016066a122bfa362cc9dc212527fc3d51369037", + "sha256:4dd374927c00764fcd6fe1046bea243ebdf403fba97a937493ae4be2c8912c2b", + "sha256:506f5410b36e5ba494136d9fa04c548eaf1a0d9c442b0b0e7a0944db7620e0ab", + "sha256:59f7374769b326a217d0b2366f1c176a45a4ff21e8f7cebb3b4a3537077eff85", + "sha256:5ee9789a20b0081dc469f65ff6c5007e67a940d5541419ca03ef20c6213dd099", + "sha256:6fc711acc4a1c702ca931fdbf7bf7c86f2a27d564c85c4964772dadf0e3c52f5", + "sha256:75d2ec3d9b401df759b87bc9e19d1b24db73083147089b43ae748aefa63067ef", + "sha256:76e0531d86523be7a46e15d379b0e975a9db84316617c0efe4af8338dc45b80c", + "sha256:8af82afc5998e1f307d5e72712526dba07403c73a9e287d906a8aa2b1f2e33dd", + "sha256:8f5d2c39f3283e461de3655e03faf10e4742bb87387113f787a7724f32db1e48", + "sha256:97785604824981ec8c81850dd25c8071d5ce04717a34296eeac771231fbdd5cd", + "sha256:a3046e8ab29b590d723821d0785598e0b2e32b636a0272a38409be43e3ae0550", + "sha256:abb0b3f2cb606981c7432f690db23506b1db5899620ad274e29dbbbdd740e797", + "sha256:ac7c2046d907e3b4e2605a130d162b1b783c170292a11216479bb1deb7cadebe", + "sha256:af27b3fe5b6bf9cd01b8e1c5ddea0a0d0a1b8c37dc1c7452f1e90bf817539c6d", + "sha256:b386b8b9d2b6a5e1e4eadd4e62335571244cb9193b7328c2b6e38b64cfda4f0e", + "sha256:b66335bbdbb4c004c25ae01cc4a54fd199afbc1fd164233813c6d3c2293bb7e1", + "sha256:d54f66c511ea01b9ef1d1a57420a93fbb9d48a08ec239f7d9c581092033156d0", + "sha256:de125151a53ecdb39df3cb3deb9951ed834dd6a110a9e795d985b10bb6db4532", + "sha256:de7916380abaef4bb4891740879b1afcba2045aee51799dfd6d6ca9bdc71f35f", + "sha256:e2fefad268ff5c5b314794e27e359e48aeb9c8bb2cbb5748a071757a56f6bb8f", + "sha256:e7b2bed4eea047a949296e618552d3fed00632dc1b795ee430289bdd0e3717f3", + "sha256:e87698e2fea5ca2f0a99dff0a64ce8110ea857b640de536c76d92aaa2a91ff3a", + "sha256:ede888382882f07b9e4cd942255921ffd9f2901684198b88e247c7eabd27a000", + "sha256:f444de0565db46d26c9fa931ca14f497900a295bd5eba480fc3fad25af8c763e", + "sha256:fa994e8937e8ccc7e87395b7b35092818905cf27c651e3ff3e7f29729f5ce3ce", + "sha256:febceb04ee7dd2aef08c2ff3d6f8a07de3052fc90137c507b0ede3ea80c21440" ], "markers": "python_version >= '3.7'", - "version": "==6.1" + "version": "==6.2" } }, "develop": { @@ -1142,32 +1153,32 @@ }, "black": { "hashes": [ - "sha256:0269dfdea12442022e88043d2910429bed717b2d04523867a85dacce535916b8", - "sha256:07204d078e25327aad9ed2c64790d681238686bce254c910de640c7cc4fc3aa6", - "sha256:08b34e85170d368c37ca7bf81cf67ac863c9d1963b2c1780c39102187ec8dd62", - "sha256:1a95915c98d6e32ca43809d46d932e2abc5f1f7d582ffbe65a5b4d1588af7445", - "sha256:2588021038bd5ada078de606f2a804cadd0a3cc6a79cb3e9bb3a8bf581325a4c", - "sha256:2fa6a0e965779c8f2afb286f9ef798df770ba2b6cee063c650b96adec22c056a", - "sha256:34afe9da5056aa123b8bfda1664bfe6fb4e9c6f311d8e4a6eb089da9a9173bf9", - "sha256:3897ae5a21ca132efa219c029cce5e6bfc9c3d34ed7e892113d199c0b1b444a2", - "sha256:40657e1b78212d582a0edecafef133cf1dd02e6677f539b669db4746150d38f6", - "sha256:48b5760dcbfe5cf97fd4fba23946681f3a81514c6ab8a45b50da67ac8fbc6c7b", - "sha256:5242ecd9e990aeb995b6d03dc3b2d112d4a78f2083e5a8e86d566340ae80fec4", - "sha256:5cdc2e2195212208fbcae579b931407c1fa9997584f0a415421748aeafff1168", - "sha256:5d7b06ea8816cbd4becfe5f70accae953c53c0e53aa98730ceccb0395520ee5d", - "sha256:7258c27115c1e3b5de9ac6c4f9957e3ee2c02c0b39222a24dc7aa03ba0e986f5", - "sha256:854c06fb86fd854140f37fb24dbf10621f5dab9e3b0c29a690ba595e3d543024", - "sha256:a21725862d0e855ae05da1dd25e3825ed712eaaccef6b03017fe0853a01aa45e", - "sha256:a83fe522d9698d8f9a101b860b1ee154c1d25f8a82ceb807d319f085b2627c5b", - "sha256:b3d64db762eae4a5ce04b6e3dd745dcca0fb9560eb931a5be97472e38652a161", - "sha256:e298d588744efda02379521a19639ebcd314fba7a49be22136204d7ed1782717", - "sha256:e2c8dfa14677f90d976f68e0c923947ae68fa3961d61ee30976c388adc0b02c8", - "sha256:ecba2a15dfb2d97105be74bbfe5128bc5e9fa8477d8c46766505c1dda5883aac", - "sha256:fc1ec9aa6f4d98d022101e015261c056ddebe3da6a8ccfc2c792cbe0349d48b7" + "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8", + "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8", + "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd", + "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9", + "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31", + "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92", + "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f", + "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29", + "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4", + "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693", + "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218", + "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a", + "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23", + "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0", + "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982", + "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894", + "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540", + "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430", + "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b", + "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2", + "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6", + "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==24.1.1" + "version": "==24.2.0" }, "blinker": { "hashes": [ @@ -1179,12 +1190,12 @@ }, "boto3": { "hashes": [ - "sha256:65acfe7f1cf2a9b7df3d4edb87c8022e02685825bd1957e7bb678cc0d09f5e5f", - "sha256:73f5ec89cb3ddb3ed577317889fd2f2df783f66b6502a9a4239979607e33bf74" + "sha256:8b3f5cc7fbedcbb22271c328039df8a6ab343001e746e0cdb24774c426cadcf8", + "sha256:f201b6a416f809283d554c652211eecec9fe3a52ed4063dab3f3e7aea7571d9c" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.34.37" + "version": "==1.34.54" }, "boto3-mocking": { "hashes": [ @@ -1197,28 +1208,28 @@ }, "boto3-stubs": { "hashes": [ - "sha256:97b5ca3d3145385acde5af46ca2da3fc74f433545034c36183f389e99771516e", - "sha256:c6618c7126bac0337c05e161e9c428febc57d6a24d7ff62de46e67761f402c57" + "sha256:7db5194e47f76e0010cd00b6ad9725db114d6a3fd04e52ceed3ef1181fe326bc", + "sha256:c7b2e8b99f4896cf1226df47d4badaaa8df7426008c96a428bf00205695669e9" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.34.37" + "version": "==1.34.54" }, "botocore": { "hashes": [ - "sha256:2a5bf33aacd2d970afd3d492e179e06ea98a5469030d5cfe7a2ad9995f7bb2ef", - "sha256:3c46ddb1679e6ef45ca78b48665398636bda532a07cd476e4b500697d13d9a99" + "sha256:4061ff4be3efcf53547ebadf2c94d419dfc8be7beec24e9fa1819599ffd936fa", + "sha256:bf215d93e9d5544c593962780d194e74c6ee40b883d0b885e62ef35fc0ec01e5" ], "markers": "python_version >= '3.8'", - "version": "==1.34.37" + "version": "==1.34.54" }, "botocore-stubs": { "hashes": [ - "sha256:087cd42973edcb5527dc97eec87fa29fffecc39691249486e02045677d4a2dbe", - "sha256:d6bcea8a6872aa46d389027dc5c022241fd0a2047a8b858aa5005e6151ed30a7" + "sha256:958f0084322dc9e549f73151b686fa51b15858fb2b3a573b9f4367f073fff463", + "sha256:bcc35bfbd14d1261813681c40108f2ce85fdf082c15b0a04016d3c22dd93b73f" ], "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==1.34.37" + "version": "==1.34.54" }, "click": { "hashes": [ @@ -1427,11 +1438,11 @@ }, "python-dateutil": { "hashes": [ - "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", - "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8.2" + "version": "==2.9.0.post0" }, "pyyaml": { "hashes": [ @@ -1492,11 +1503,11 @@ }, "rich": { "hashes": [ - "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa", - "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235" + "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", + "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432" ], "markers": "python_full_version >= '3.7.0'", - "version": "==13.7.0" + "version": "==13.7.1" }, "s3transfer": { "hashes": [ @@ -1532,11 +1543,11 @@ }, "stevedore": { "hashes": [ - "sha256:8cc040628f3cea5d7128f2e76cf486b2251a4e543c7b938f58d9a377f6694a2d", - "sha256:a54534acf9b89bc7ed264807013b505bf07f74dbe4bcfa37d32bd063870b087c" + "sha256:1c15d95766ca0569cad14cb6272d4d31dae66b011a929d7c18219c176ea1b5c9", + "sha256:46b93ca40e1114cea93d738a6c1e365396981bb6bb78c27045b7587c9473544d" ], "markers": "python_version >= '3.8'", - "version": "==5.1.0" + "version": "==5.2.0" }, "tomli": { "hashes": [ @@ -1548,11 +1559,11 @@ }, "types-awscrt": { "hashes": [ - "sha256:06a859189a329ca8e66d56ceeef2391488e39b878fbd2141f115eab4d416fe22", - "sha256:f61a120d3e98ee1387bc5ca4b93437f258cc5c2af1f55f8634ec4cee5729f178" + "sha256:61811bbf4de95248939f9276a434be93d2b95f6ccfe8aa94e56999e9778cfcc2", + "sha256:79d5bfb01f64701b6cf442e89a37d9c4dc6dbb79a46f2f611739b2418d30ecfd" ], "markers": "python_version >= '3.7' and python_version < '4.0'", - "version": "==0.20.3" + "version": "==0.20.5" }, "types-cachetools": { "hashes": [ @@ -1580,12 +1591,12 @@ }, "types-requests": { "hashes": [ - "sha256:03a28ce1d7cd54199148e043b2079cdded22d6795d19a2c2a6791a4b2b5e2eb5", - "sha256:9592a9a4cb92d6d75d9b491a41477272b710e021011a2a3061157e2fb1f1a5d1" + "sha256:a82807ec6ddce8f00fe0e949da6d6bc1fbf1715420218a9640d695f70a9e5a9b", + "sha256:f1721dba8385958f504a5386240b92de4734e047a08a40751c1654d1ac3349c5" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==2.31.0.20240125" + "version": "==2.31.0.20240218" }, "types-s3transfer": { "hashes": [ @@ -1597,12 +1608,12 @@ }, "typing-extensions": { "hashes": [ - "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783", - "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd" + "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475", + "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==4.9.0" + "version": "==4.10.0" }, "urllib3": { "hashes": [ diff --git a/src/requirements.txt b/src/requirements.txt index a6130a3bf..8523835ef 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,14 +1,14 @@ -i https://pypi.python.org/simple annotated-types==0.6.0; python_version >= '3.8' asgiref==3.7.2; python_version >= '3.7' -boto3==1.34.37; python_version >= '3.8' -botocore==1.34.37; python_version >= '3.8' -cachetools==5.3.2; python_version >= '3.7' +boto3==1.34.54; python_version >= '3.8' +botocore==1.34.54; python_version >= '3.8' +cachetools==5.3.3; python_version >= '3.7' certifi==2024.2.2; python_version >= '3.6' cfenv==0.5.3 cffi==1.16.0; platform_python_implementation != 'PyPy' charset-normalizer==3.3.2; python_full_version >= '3.7.0' -cryptography==42.0.2; python_version >= '3.7' +cryptography==42.0.5; python_version >= '3.7' defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' dj-database-url==2.1.0 dj-email-url==1.0.6 @@ -17,17 +17,17 @@ django-allow-cidr==0.7.1 django-auditlog==2.3.0; python_version >= '3.7' django-cache-url==3.4.5 django-cors-headers==4.3.1; python_version >= '3.8' -django-csp==3.7 +django-csp==3.8 django-fsm==2.8.1 django-login-required-middleware==0.9.0 django-phonenumber-field[phonenumberslite]==7.3.0; python_version >= '3.8' django-widget-tweaks==1.5.0; python_version >= '3.8' environs[django]==10.3.0; python_version >= '3.8' -faker==23.1.0; python_version >= '3.8' +faker==23.3.0; python_version >= '3.8' fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c furl==2.1.3 -future==0.18.3; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' -gevent==23.9.1; python_version >= '3.8' +future==1.0.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' +gevent==24.2.1; python_version >= '3.8' geventconnpool@ git+https://github.com/rasky/geventconnpool.git@1bbb93a714a331a069adf27265fe582d9ba7ecd4 greenlet==3.0.3; python_version >= '3.7' gunicorn==21.2.0; python_version >= '3.5' @@ -36,27 +36,28 @@ jmespath==1.0.1; python_version >= '3.7' lxml==5.1.0; python_version >= '3.6' mako==1.3.2; python_version >= '3.8' markupsafe==2.1.5; python_version >= '3.7' -marshmallow==3.20.2; python_version >= '3.8' +marshmallow==3.21.0; python_version >= '3.8' oic==1.6.1; python_version ~= '3.7' orderedmultidict==1.0.1 packaging==23.2; python_version >= '3.7' -phonenumberslite==8.13.29 +phonenumberslite==8.13.31 psycopg2-binary==2.9.9; python_version >= '3.7' pycparser==2.21 pycryptodomex==3.20.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' -pydantic==2.6.1; python_version >= '3.8' -pydantic-core==2.16.2; python_version >= '3.8' -pydantic-settings==2.1.0; python_version >= '3.8' +pydantic==2.6.3; python_version >= '3.8' +pydantic-core==2.16.3; python_version >= '3.8' +pydantic-settings==2.2.1; python_version >= '3.8' pyjwkest==1.4.2 -python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' python-dotenv==1.0.1; python_version >= '3.8' requests==2.31.0; python_version >= '3.7' s3transfer==0.10.0; python_version >= '3.8' -setuptools==69.0.3; python_version >= '3.8' +setuptools==69.1.1; python_version >= '3.8' six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' sqlparse==0.4.4; python_version >= '3.5' -typing-extensions==4.9.0; python_version >= '3.8' +tblib==3.0.0; python_version >= '3.8' +typing-extensions==4.10.0; python_version >= '3.8' urllib3==2.0.7; python_version >= '3.7' whitenoise==6.6.0; python_version >= '3.8' zope.event==5.0; python_version >= '3.7' -zope.interface==6.1; python_version >= '3.7' +zope.interface==6.2; python_version >= '3.7' From 64c936b55382a747a50587b692ce0f05a8b85ad7 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 4 Mar 2024 14:27:34 -0500 Subject: [PATCH 07/12] fixed a test that was failing --- src/registrar/tests/common.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index ee1ab8b68..be3643a51 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -475,7 +475,6 @@ class AuditedAdminMockData: def mock_user(): """A simple user.""" user_kwargs = dict( - id=4, first_name="Jeff", last_name="Lebowski", ) From 2b3c7ae0fd4baeeec173af30f1641da68b3b2106 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 4 Mar 2024 14:34:32 -0500 Subject: [PATCH 08/12] cleanup of debugging on failing test outside of scope of this ticket --- src/registrar/tests/test_admin.py | 66 +++++++++++++++---------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index dd78d569c..3010247e7 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -1816,41 +1816,41 @@ class ListHeaderAdminTest(TestCase): self.superuser = create_superuser() def test_changelist_view(self): - # with less_console_noise(): - # Have to get creative to get past linter - p = "adminpass" - self.client.login(username="superuser", password=p) - # Mock a user - user = mock_user() - # Make the request using the Client class - # which handles CSRF - # Follow=True handles the redirect - response = self.client.get( - "/admin/registrar/domainapplication/", - { - "status__exact": "started", - "investigator__id__exact": user.id, - "q": "Hello", - }, - follow=True, - ) - # Assert that the filters and search_query are added to the extra_context - self.assertIn("filters", response.context) - self.assertIn("search_query", response.context) - # Assert the content of filters and search_query - filters = response.context["filters"] - search_query = response.context["search_query"] - self.assertEqual(search_query, "Hello") - self.assertEqual( - filters, - [ - {"parameter_name": "status", "parameter_value": "started"}, + with less_console_noise(): + # Have to get creative to get past linter + p = "adminpass" + self.client.login(username="superuser", password=p) + # Mock a user + user = mock_user() + # Make the request using the Client class + # which handles CSRF + # Follow=True handles the redirect + response = self.client.get( + "/admin/registrar/domainapplication/", { - "parameter_name": "investigator", - "parameter_value": user.first_name + " " + user.last_name, + "status__exact": "started", + "investigator__id__exact": user.id, + "q": "Hello", }, - ], - ) + follow=True, + ) + # Assert that the filters and search_query are added to the extra_context + self.assertIn("filters", response.context) + self.assertIn("search_query", response.context) + # Assert the content of filters and search_query + filters = response.context["filters"] + search_query = response.context["search_query"] + self.assertEqual(search_query, "Hello") + self.assertEqual( + filters, + [ + {"parameter_name": "status", "parameter_value": "started"}, + { + "parameter_name": "investigator", + "parameter_value": user.first_name + " " + user.last_name, + }, + ], + ) def test_get_filters(self): with less_console_noise(): From a75caa2fdb7b48dc50bbd3203cc1ca927244b20e Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 5 Mar 2024 14:02:14 -0500 Subject: [PATCH 09/12] removed geventconnectionpool --- src/Pipfile | 1 - src/Pipfile.lock | 4 ---- src/requirements.txt | 1 - 3 files changed, 6 deletions(-) diff --git a/src/Pipfile b/src/Pipfile index 80a0761a1..f24fbd550 100644 --- a/src/Pipfile +++ b/src/Pipfile @@ -29,7 +29,6 @@ django-login-required-middleware = "*" greenlet = "*" gevent = "*" fred-epplib = {git = "https://github.com/cisagov/epplib.git", ref = "master"} -geventconnpool = {git = "https://github.com/rasky/geventconnpool.git", ref = "1bbb93a714a331a069adf27265fe582d9ba7ecd4"} tblib = "*" [dev-packages] diff --git a/src/Pipfile.lock b/src/Pipfile.lock index 2e08b0628..1c8eac0a0 100644 --- a/src/Pipfile.lock +++ b/src/Pipfile.lock @@ -458,10 +458,6 @@ "markers": "python_version >= '3.8'", "version": "==24.2.1" }, - "geventconnpool": { - "git": "https://github.com/rasky/geventconnpool.git", - "ref": "1bbb93a714a331a069adf27265fe582d9ba7ecd4" - }, "greenlet": { "hashes": [ "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67", diff --git a/src/requirements.txt b/src/requirements.txt index 8523835ef..5696864bd 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -28,7 +28,6 @@ fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a furl==2.1.3 future==1.0.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' gevent==24.2.1; python_version >= '3.8' -geventconnpool@ git+https://github.com/rasky/geventconnpool.git@1bbb93a714a331a069adf27265fe582d9ba7ecd4 greenlet==3.0.3; python_version >= '3.7' gunicorn==21.2.0; python_version >= '3.5' idna==3.6; python_version >= '3.5' From 6e133451610022be1ec0ec757c6bd868ebc13636 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 5 Mar 2024 14:50:20 -0500 Subject: [PATCH 10/12] noting why type is ignored --- src/epplibwrapper/client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index bd39a37d6..951e612bc 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -62,6 +62,8 @@ class EPPLibWrapper: client. Raises errors if initialization fails. This method will be called at app initialization, and also during retries.""" # establish a client object with a TCP socket transport + # note that type: ignore added in several places because linter complains + # about _client initially being set to None, and None type doesn't match code self._client = Client( # type: ignore SocketTransport( settings.SECRET_REGISTRY_HOSTNAME, @@ -73,9 +75,9 @@ class EPPLibWrapper: try: # use the _client object to connect self._client.connect() # type: ignore - response = self._client.send(self._login) # type:ignore + response = self._client.send(self._login) # type: ignore if response.code >= 2000: # type: ignore - self._client.close() # type:ignore + self._client.close() # type: ignore raise LoginError(response.msg) # type: ignore except TransportError as err: message = "_initialize_client failed to execute due to a connection error." From d542e7a74bb8b205a040826d56ff195d58662502 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 5 Mar 2024 18:46:22 -0500 Subject: [PATCH 11/12] updated comment --- src/epplibwrapper/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 951e612bc..b08a9cb8a 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -141,7 +141,7 @@ class EPPLibWrapper: return self._send(command) def send(self, command, *, cleaned=False): - """Login, send the command, then close the connection. Tries 3 times.""" + """Login, the send the command. Retry once if an error is found""" # try to prevent use of this method without appropriate safeguards if not cleaned: raise ValueError("Please sanitize user input before sending it.") From efd3f62f0b6507a501260d74382e5a52eb924833 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 5 Mar 2024 18:50:42 -0500 Subject: [PATCH 12/12] added logging for retries --- src/epplibwrapper/client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index b08a9cb8a..a7856298b 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -143,6 +143,7 @@ class EPPLibWrapper: def send(self, command, *, cleaned=False): """Login, the send the command. Retry once if an error is found""" # try to prevent use of this method without appropriate safeguards + cmd_type = command.__class__.__name__ if not cleaned: raise ValueError("Please sanitize user input before sending it.") try: @@ -155,6 +156,8 @@ class EPPLibWrapper: or err.is_server_error() or err.should_retry() ): + message = f"{cmd_type} failed and will be retried" + logger.info(f"{message} Error: {err}") return self._retry(command) else: raise err