Пример #1
0
    def __init__(self,
                 host=None,
                 port=None,
                 socket_timeout=None,
                 socket_connect_timeout=None,
                 socket_keepalive=None,
                 socket_keepalive_options=None,
                 connection=None,
                 **kwargs):
        if not connection:
            self.socket_timeout = socket_timeout
            kwargs2 = {
                'host': host,
                'port': int(port),
                'socket_connect_timeout': socket_connect_timeout,
                'socket_keepalive': socket_keepalive,
                'socket_keepalive_options': socket_keepalive_options,
                'socket_timeout': socket_timeout,
            }

            connection = Connection(**kwargs2)
        self.conn_queue = LifoQueue()
        self.conn_queue.put_nowait(connection)
        self._connection = connection
        self.response_callbacks = self.__class__.RESPONSE_CALLBACKS.copy()
        self.expected_ok = self.__class__.EXPECTED_OK.copy()
        self.expected_err = self.__class__.EXPECTED_ERR.copy()
Пример #2
0
class Beanstalk(object):
    RESPONSE_CALLBACKS = dict_merge({
        'list-tubes': parse_yaml,
        'peek': parse_body,
        'peek-buried': parse_body,
        'peek-delayed': parse_body,
        'peek-ready': parse_body,
        'reserve': parse_body,
        'reserve-with-timeout': parse_body,
        'stats': parse_yaml,
        'stats-tube': parse_yaml
    })
    EXPECTED_OK = dict_merge({
        'bury': ['BURIED'],
        'delete': ['DELETED'],
        'list-tubes': ['OK'],
        'kick': ['KICKED'],
        'kick-job': ['KICKED'],
        'peek': ['FOUND'],
        'peek-buried': ['FOUND'],
        'peek-delayed': ['FOUND'],
        'peek-ready': ['FOUND'],
        'put': ['INSERTED'],
        'release': ['RELEASED'],
        'reserve': ['RESERVED'],
        'reserve-with-timeout': ['RESERVED'],
        'stats': ['OK'],
        'stats-tube': ['OK'],
        'use': ['USING'],
        'watch': ['WATCHING'],
    })
    EXPECTED_ERR = dict_merge({
        'bury': ['NOT_FOUND', 'OUT_OF_MEMORY'],
        'delete': ['NOT_FOUND'],
        'list-tubes': [],
        'kick': ['OUT_OF_MEMORY'],
        'kick-job': ['NOT_FOUND', 'OUT_OF_MEMORY'],
        'peek': ['NOT_FOUND'],
        'peek-buried': ['NOT_FOUND'],
        'peek-delayed': ['NOT_FOUND'],
        'peek-ready': ['NOT_FOUND'],
        'put': ['JOB_TOO_BIG', 'BURIED', 'DRAINING', 'OUT_OF_MEMORY'],
        'reserve': ['DEADLINE_SOON', 'TIMED_OUT'],
        'reserve-with-timeout': ['DEADLINE_SOON', 'TIMED_OUT'],
        'release': ['BURIED', 'NOT_FOUND', 'OUT_OF_MEMORY'],
        'stats': [],
        'stats-tube': ['NOT_FOUND'],
        'use': [],
        'watch': [],
    })

    @classmethod
    def from_url(cls, url, **kwargs):
        if url is None or not url:
            raise ConnectionError('Empty URL')
        if not url.startswith('beanstalk://'):
            import warnings
            warnings.warn(
                    'Invalid URL scheme, expecting beanstalk',
                    DeprecationWarning)
        connection = Connection.from_url(url, **kwargs)
        return cls(connection=connection)

    def __init__(self, host=None, port=None, socket_timeout=None,
                 socket_connect_timeout=None, socket_keepalive=None,
                 socket_keepalive_options=None, connection=None,
                 **kwargs):
        if not connection:
            self.socket_timeout = socket_timeout
            kwargs2 = {
                'host': host,
                'port': int(port),
                'socket_connect_timeout': socket_connect_timeout,
                'socket_keepalive': socket_keepalive,
                'socket_keepalive_options': socket_keepalive_options,
                'socket_timeout': socket_timeout,
            }

            connection = Connection(**kwargs2)
        self.conn_queue = LifoQueue()
        self.conn_queue.put_nowait(connection)
        self._connection = connection
        self.response_callbacks = self.__class__.RESPONSE_CALLBACKS.copy()
        self.expected_ok = self.__class__.EXPECTED_OK.copy()
        self.expected_err = self.__class__.EXPECTED_ERR.copy()

    def _get_connection(self):
        try:
            connection = self.conn_queue.get(block=True, timeout=None)
        except Empty:
            raise ConnectionError("No connection available")
        return connection

    def _release_connection(self, connection):
        self.conn_queue.put_nowait(connection)

    def execute_command(self, *args, **kwargs):
        connection = self._get_connection()
        command_name = args[0]
        try:
            connection.send_command(*args, **kwargs)
            return self.parse_response(connection, command_name, **kwargs)
        except (ConnectionError, TimeoutError):
            connection.disconnect()
            raise
        finally:
            self._release_connection(connection)

    def parse_response(self, connection, command_name, **kwargs):
        response = connection.read_response()
        status, results = response
        if status in self.expected_ok[command_name]:
            if command_name in self.response_callbacks:
                return self.response_callbacks[command_name](
                        connection, response, **kwargs)
            return response
        elif status in self.expected_err[command_name]:
            raise ResponseError(command_name, status, results)
        else:
            raise InvalidResponse(command_name, status, results)

        return response

    def put(self, body, priority=DEFAULT_PRIORITY, delay=0, ttr=DEFAULT_TTR):
        assert isinstance(body, str), 'body must be str'
        job_id = self.execute_command('put', priority, delay, ttr, body=body)
        return job_id

    def use(self, tube):
        self._connection.use(tube)

    def watch(self, tube):
        self._connection.watch(tube)

    def reserve(self, timeout=None):
        if timeout is not None:
            return self.execute_command('reserve-with-timeout', timeout)
        else:
            return self.execute_command('reserve')

    def bury(self, job_id, priority=DEFAULT_PRIORITY):
        self.execute_command('bury', job_id, priority)

    def release(self, job_id, priority=DEFAULT_PRIORITY, delay=0):
        self.execute_command('release', job_id, priority, delay)

    def delete(self, job_id):
        self.execute_command('delete', job_id)

    def _drain(self, fetch_func):
        try:
            job_id = True
            while job_id is not None:
                job_id, _ = fetch_func()
                self.delete(job_id)
        except ResponseError:
            pass

    def drain_buried(self, tube):
        self.use(tube)
        return self._drain(self.peek_buried)

    def drain_tube(self, tube):
        """Delete all jobs from the specified tube."""
        self.watch(tube)
        from functools import partial
        return self._drain(partial(self.reserve, timeout=0))

    def kick_job(self, job_id):
        """
        Variant of` kick` that operates with a single job.

        :param job_id: the job id to kick
        :type job_id: `str`
        """
        self.execute_command('kick-job', job_id)

    def kick(self, bound=1000):
        """
        Move jobs into the ready queue.
        If there are any buried jobs, it will only kick buried jobs.
        Otherwise it will kick delayed jobs.

        :param bound: upper bound on the number of jobs to kick
        :type bound: `int`
        """
        kicked = int(self.execute_command('kick', str(bound))[1][0])
        return kicked

    def _peek_generic(self, command_suffix=''):
        command = 'peek' + command_suffix
        try:
            return self.execute_command(command)
        except ResponseError as err:
            if err.args[0] == command and err.args[1] == 'NOT_FOUND':
                return None, None
            else:
                raise

    def peek_buried(self):
        """
        Read the next buried job without kicking it.
        """
        return self._peek_generic('-buried')

    def peek_ready(self):
        """
        read the next ready job without reserving it.
        """
        return self._peek_generic('-ready')

    def wait_until_empty(self, tube, timeout=float('inf'), poll_interval=0.2,
                         initial_delay=0.0):
        """
        Wait until the the specified tube is empty, or the timeout expires.
        """
        # TODO(FVE): check tube stats to ensure some jobs have passed through
        # and then get rid of the initial_delay
        # peek-ready requires "use", not "watch"
        self.use(tube)
        if initial_delay > 0.0:
            time.sleep(initial_delay)
        job_id, _ = self.peek_ready()
        deadline = time.time() + timeout
        while job_id is not None and time.time() < deadline:
            time.sleep(poll_interval)
            job_id, _ = self.peek_ready()

    def wait_for_ready_job(self, tube, timeout=float('inf'),
                           poll_interval=0.2):
        """
        Wait until the the specified tube has a ready job,
        or the timeout expires.
        """
        self.use(tube)
        job_id, data = self.peek_ready()
        deadline = time.time() + timeout
        while job_id is None and time.time() < deadline:
            time.sleep(poll_interval)
            job_id, data = self.peek_ready()
        return job_id, data

    def stats(self):
        return self.execute_command('stats')

    def stats_tube(self, tube):
        return self.execute_command('stats-tube', tube)

    def tubes(self):
        return self.execute_command('list-tubes')

    def close(self):
        if self._connection:
            self._connection.disconnect()
            self._connection = None