/vk_requests/streaming.py

https://github.com/prawn-cake/vk-requests · Python · 152 lines · 131 code · 9 blank · 12 comment · 2 complexity · 22fb8e281bedf915b5265b03ef70e785 MD5 · raw file

  1. # -*- coding: utf-8 -*-
  2. import sys
  3. if sys.version_info <= (3, 4):
  4. raise RuntimeError('Streaming API requires python version >= 3.4')
  5. import requests
  6. import websockets
  7. import asyncio
  8. import logging
  9. logger = logging.getLogger(__name__)
  10. class Stream(object):
  11. """Stream representation"""
  12. def __init__(self, conn_url):
  13. self._conn_url = conn_url
  14. self._consumer_fn = None
  15. def __repr__(self):
  16. return '%s(conn_url=%s)' % (self.__class__.__name__, self._conn_url)
  17. def consumer(self, fn):
  18. """Consumer decorator
  19. :param fn: coroutine consumer function
  20. Example:
  21. >>> api = StreamingAPI('my_service_key')
  22. >>> stream = api.get_stream()
  23. >>> @stream.consumer
  24. >>> @asyncio.coroutine
  25. >>> def handle_event(payload):
  26. >>> print(payload)
  27. """
  28. if self._consumer_fn is not None:
  29. raise ValueError('Consumer function is already defined for this '
  30. 'Stream instance')
  31. if not any([asyncio.iscoroutine(fn), asyncio.iscoroutinefunction(fn)]):
  32. raise ValueError('Consumer function must be a coroutine')
  33. self._consumer_fn = fn
  34. def consume(self, timeout=None, loop=None):
  35. """Start consuming the stream
  36. :param timeout: int: if it's given then it stops consumer after given
  37. number of seconds
  38. """
  39. if self._consumer_fn is None:
  40. raise ValueError('Consumer function is not defined yet')
  41. logger.info('Start consuming the stream')
  42. @asyncio.coroutine
  43. def worker(conn_url):
  44. extra_headers = {
  45. 'Connection': 'upgrade',
  46. 'Upgrade': 'websocket',
  47. 'Sec-Websocket-Version': 13,
  48. }
  49. ws = yield from websockets.connect(
  50. conn_url, extra_headers=extra_headers)
  51. if ws is None:
  52. raise RuntimeError("Couldn't connect to the '%s'" % conn_url)
  53. try:
  54. while True:
  55. message = yield from ws.recv()
  56. yield from self._consumer_fn(message)
  57. finally:
  58. yield from ws.close()
  59. if loop is None:
  60. loop = asyncio.new_event_loop()
  61. asyncio.set_event_loop(loop)
  62. try:
  63. task = worker(conn_url=self._conn_url)
  64. if timeout:
  65. logger.info('Running task with timeout %s sec', timeout)
  66. loop.run_until_complete(
  67. asyncio.wait_for(task, timeout=timeout))
  68. else:
  69. loop.run_until_complete(task)
  70. except asyncio.TimeoutError:
  71. logger.info('Timeout is reached. Closing the loop')
  72. loop.close()
  73. except KeyboardInterrupt:
  74. logger.info('Closing the loop')
  75. loop.close()
  76. class StreamingAPI(object):
  77. """VK Streaming API implementation
  78. Docs: https://vk.com/dev/streaming_api_docs
  79. """
  80. REQUEST_URL = 'https://{endpoint}/rules?key={key}'
  81. STREAM_URL = 'wss://{endpoint}/stream?key={key}'
  82. def __init__(self, service_token):
  83. if not service_token:
  84. raise ValueError('service_token is required')
  85. import vk_requests
  86. self.api = vk_requests.create_api(service_token=service_token)
  87. self._params = self.api.streaming.getServerUrl()
  88. def add_rule(self, value, tag):
  89. """Add a new rule
  90. :param value: str
  91. :param tag: str
  92. :return: dict of a json response
  93. """
  94. resp = requests.post(url=self.REQUEST_URL.format(**self._params),
  95. json={'rule': {'value': value, 'tag': tag}})
  96. return resp.json()
  97. def get_rules(self):
  98. resp = requests.get(url=self.REQUEST_URL.format(**self._params))
  99. return resp.json()
  100. def remove_rule(self, tag):
  101. """Remove a rule by tag
  102. """
  103. resp = requests.delete(url=self.REQUEST_URL.format(**self._params),
  104. json={'tag': tag})
  105. return resp.json()
  106. def get_stream(self):
  107. """Factory method to get a stream object
  108. :return Stream instance
  109. """
  110. return Stream(conn_url=self.STREAM_URL.format(**self._params))
  111. def get_settings(self):
  112. """Get settings object with monthly limit info
  113. """
  114. return self.api.streaming.getSettings()