diff --git a/freezing/nq/api/health.py b/freezing/nq/api/health.py index 50b3944..da96b67 100644 --- a/freezing/nq/api/health.py +++ b/freezing/nq/api/health.py @@ -2,9 +2,8 @@ class HealthResource: - def on_get(self, req: falcon.Request, resp: falcon.Response): """ Just an arbitrary response for now. """ - resp.media = {'status': 'Server is happy.'} \ No newline at end of file + resp.media = {"status": "Server is happy."} diff --git a/freezing/nq/api/webhook.py b/freezing/nq/api/webhook.py index cdf9f64..9be948e 100644 --- a/freezing/nq/api/webhook.py +++ b/freezing/nq/api/webhook.py @@ -5,13 +5,17 @@ from freezing.nq.config import config from freezing.nq.publish import ActivityPublisher -from freezing.model.msg.strava import SubscriptionUpdate, SubscriptionUpdateSchema, SubscriptionCallbackSchema, \ - SubscriptionCallback, ObjectType +from freezing.model.msg.strava import ( + SubscriptionUpdate, + SubscriptionUpdateSchema, + SubscriptionCallbackSchema, + SubscriptionCallback, + ObjectType, +) from freezing.model.msg.mq import DefinedTubes, ActivityUpdate, ActivityUpdateSchema class WebhookResource: - def __init__(self, publisher: ActivityPublisher): self.publisher = publisher @@ -22,14 +26,17 @@ def on_get(self, req: falcon.Request, resp: falcon.Response): See: http://strava.github.io/api/partner/v3/events/ """ - strava_request = {k: req.get_param(k) for k in ('hub.challenge', 'hub.mode', 'hub.verify_token')} + strava_request = { + k: req.get_param(k) + for k in ("hub.challenge", "hub.mode", "hub.verify_token") + } schema = SubscriptionCallbackSchema() callback: SubscriptionCallback = schema.load(strava_request).data assert config.STRAVA_VERIFY_TOKEN == callback.hub_verify_token resp.status = falcon.HTTP_200 - resp.body = json.dumps({'hub.challenge': callback.hub_challenge}) + resp.body = json.dumps({"hub.challenge": callback.hub_challenge}) def on_post(self, req: falcon.Request, resp: falcon.Response): """ @@ -66,5 +73,4 @@ def on_post(self, req: falcon.Request, resp: falcon.Response): json_data = ActivityUpdateSchema().dump(message) log.info("Publishing activity-update: {}".format(message)) - self.publisher.publish_message(json_data, - dest=DefinedTubes.activity_update) + self.publisher.publish_message(json_data, dest=DefinedTubes.activity_update) diff --git a/freezing/nq/app.py b/freezing/nq/app.py index f946266..aaed005 100644 --- a/freezing/nq/app.py +++ b/freezing/nq/app.py @@ -8,33 +8,36 @@ class RequireJSON: - def process_request(self, req, resp): if not req.client_accepts_json: raise falcon.HTTPNotAcceptable( - 'This API only supports responses encoded as JSON.', - href='http://docs.examples.com/api/json') + "This API only supports responses encoded as JSON.", + href="http://docs.examples.com/api/json", + ) - if req.method in ('POST', 'PUT'): - if 'application/json' not in req.content_type: + if req.method in ("POST", "PUT"): + if "application/json" not in req.content_type: raise falcon.HTTPUnsupportedMediaType( - 'This API only supports requests encoded as JSON.', - href='http://docs.examples.com/api/json') + "This API only supports requests encoded as JSON.", + href="http://docs.examples.com/api/json", + ) -def make_app(publisher:ActivityPublisher = None) -> falcon.API: +def make_app(publisher: ActivityPublisher = None) -> falcon.API: """ Builds the WSGI application we'll be serving. """ if publisher is None: publisher = configured_publisher() - app = falcon.API(middleware=[ - RequireJSON(), - ]) + app = falcon.API( + middleware=[ + RequireJSON(), + ] + ) - app.add_route('/health', HealthResource()) - app.add_route('/webhook', WebhookResource(publisher=publisher)) + app.add_route("/health", HealthResource()) + app.add_route("/webhook", WebhookResource(publisher=publisher)) return app @@ -43,6 +46,6 @@ def make_app(publisher:ActivityPublisher = None) -> falcon.API: # can also use Gunicorn to host your app. Gunicorn can be configured to # auto-restart workers when it detects a code change, and it also works # with pdb. -if __name__ == '__main__': - httpd = simple_server.make_server('127.0.0.1', 8000, make_app()) +if __name__ == "__main__": + httpd = simple_server.make_server("127.0.0.1", 8000, make_app()) httpd.serve_forever() diff --git a/freezing/nq/autolog.py b/freezing/nq/autolog.py index 9bf2816..0bf093d 100644 --- a/freezing/nq/autolog.py +++ b/freezing/nq/autolog.py @@ -40,7 +40,7 @@ def _eagerFormat(self, msg, level, args): # Otherwise, just drop the message completely to avoid anything going # wrong in the future. This text shoudl clue one in to what's going # on in the bizarre edge case where this ever does show up. - return '(log message suppressed due to insufficient log level)' + return "(log message suppressed due to insufficient log level)" def _getUnterpolatedMessage(self, msg, args): """ @@ -64,7 +64,10 @@ def _getUnterpolatedMessage(self, msg, args): # by casting the left side (the "msg" variable) in this context # to unicode. So we'll do that here - if sys.version_info >= (3, 0,): + if sys.version_info >= ( + 3, + 0, + ): # this is most likely unnecessary on python 3, but it's here # for completeness, in the case of someone manually creating # a bytestring @@ -81,7 +84,7 @@ def _getUnterpolatedMessage(self, msg, args): # From PEP-3101, value errors are of the type raised by the format # method itself, so see if we should fall back to original # formatting since there was an issue - if '%' in msg: + if "%" in msg: msg = msg % args else: # we should NOT fall back, since there's no possible string @@ -89,7 +92,7 @@ def _getUnterpolatedMessage(self, msg, args): # message raise - if msg == original_msg and '%' in msg: + if msg == original_msg and "%" in msg: # there must have been no string formatting methods # used, given the presence of args without a change in the msg # fall back to original formatting, including the special case @@ -191,15 +194,20 @@ def __init__(self, adapter_class=None, adapter_args=None, adapter_kwargs=None): self.adapter_kwargs = adapter_kwargs def __getattr__(self, name): - if 'self' in inspect.currentframe().f_locals: - other = inspect.currentframe().f_locals['self'] - caller_name = '%s.%s' % (other.__class__.__module__, other.__class__.__name__) + if "self" in inspect.currentframe().f_locals: + other = inspect.currentframe().f_locals["self"] + caller_name = "%s.%s" % ( + other.__class__.__module__, + other.__class__.__name__, + ) else: - caller_name = inspect.currentframe(1).f_globals['__name__'] + caller_name = inspect.currentframe(1).f_globals["__name__"] logger = logging.getLogger(caller_name) if self.adapter_class: - logger = self.adapter_class(logger, *self.adapter_args, **self.adapter_kwargs) + logger = self.adapter_class( + logger, *self.adapter_args, **self.adapter_kwargs + ) return getattr(logger, name) @@ -208,7 +216,7 @@ def __getattr__(self, name): def log_exceptions(fn): - """ A decorator designed to wrap a function and log any exception that method produces. + """A decorator designed to wrap a function and log any exception that method produces. The exception will still be raised after being logged. @@ -224,7 +232,7 @@ def wrapper(*args, **kwargs): a = [str(x)[:255] for x in a] kw = kwargs or {} kw = dict([(str(k)[:255], str(v)[:255]) for k, v in kw.items()]) - log.debug('Calling %s.%s %r %r' % (fn.__module__, fn.__name__, a, kw)) + log.debug("Calling %s.%s %r %r" % (fn.__module__, fn.__name__, a, kw)) return fn(*args, **kwargs) except Exception as e: log.error("Error calling function %s: %s" % (fn.__name__, e)) diff --git a/freezing/nq/config.py b/freezing/nq/config.py index c7a6df4..30c4e45 100644 --- a/freezing/nq/config.py +++ b/freezing/nq/config.py @@ -2,17 +2,17 @@ import logging from envparse import env -envfile = os.environ.get('APP_SETTINGS', os.path.join(os.getcwd(), '.env')) +envfile = os.environ.get("APP_SETTINGS", os.path.join(os.getcwd(), ".env")) if os.path.exists(envfile): env.read_envfile(envfile) class Config: - DEBUG: bool = env('DEBUG', default=False) - STRAVA_VERIFY_TOKEN: str = env('STRAVA_VERIFY_TOKEN', default='STRAVA') - BEANSTALKD_HOST: str = env('BEANSTALKD_HOST', default='127.0.0.1') - BEANSTALKD_PORT: int = env('BEANSTALKD_PORT', cast=int, default=11300) + DEBUG: bool = env("DEBUG", default=False) + STRAVA_VERIFY_TOKEN: str = env("STRAVA_VERIFY_TOKEN", default="STRAVA") + BEANSTALKD_HOST: str = env("BEANSTALKD_HOST", default="127.0.0.1") + BEANSTALKD_PORT: int = env("BEANSTALKD_PORT", cast=int, default=11300) config = Config() diff --git a/freezing/nq/publish.py b/freezing/nq/publish.py index ff78616..d89d6c0 100644 --- a/freezing/nq/publish.py +++ b/freezing/nq/publish.py @@ -15,7 +15,7 @@ class ActivityPublisher: (Currently uses beanstalkd, but that may change. May also switch to using threads & queues to speed up.) """ - def __init__(self, host:str, port:int): + def __init__(self, host: str, port: int): self.host = host self.port = port @@ -25,7 +25,7 @@ def serialize_message(self, message) -> str: else: return json.dumps(message) - def publish_message(self, message:Any, dest: DefinedTubes): + def publish_message(self, message: Any, dest: DefinedTubes): """ Publish the json-serializable message object (e.g. dict) to configured destination (e.g. queue, tube). @@ -41,4 +41,4 @@ def publish_message(self, message:Any, dest: DefinedTubes): def configured_publisher() -> ActivityPublisher: - return ActivityPublisher(host=config.BEANSTALKD_HOST, port=config.BEANSTALKD_PORT) \ No newline at end of file + return ActivityPublisher(host=config.BEANSTALKD_HOST, port=config.BEANSTALKD_PORT) diff --git a/tests/test_api.py b/tests/test_api.py index 13552d6..6eb766e 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -12,54 +12,57 @@ def test_get_webhook(client): + d = { + "hub.challenge": "asdf", + "hub.mode": "mode", + "hub.verify_token": config.STRAVA_VERIFY_TOKEN, + } - d = {'hub.challenge': 'asdf', 'hub.mode': 'mode', 'hub.verify_token': config.STRAVA_VERIFY_TOKEN} - - result = client.simulate_get('/webhook', params=d) + result = client.simulate_get("/webhook", params=d) print(result) - assert result.json == {'hub.challenge': 'asdf'} + assert result.json == {"hub.challenge": "asdf"} def test_get_webhook_bad_token(client): - - d = {'hub.challenge': 'asdf', 'hub.mode': 'mode', 'hub.verify_token': 'wrong'} + d = {"hub.challenge": "asdf", "hub.mode": "mode", "hub.verify_token": "wrong"} with pytest.raises(AssertionError): - result = client.simulate_get('/webhook', params=d) - + result = client.simulate_get("/webhook", params=d) -def test_post_webhook(client, publisher:ActivityPublisher): +def test_post_webhook(client, publisher: ActivityPublisher): d = dict( subscription_id=111, owner_id=222, - object_type='activity', + object_type="activity", object_id=999, - aspect_type='update', - updates={'title': 'Hello world.'}, + aspect_type="update", + updates={"title": "Hello world."}, event_time=1358919359, ) - result = client.simulate_post('/webhook', body=json.dumps(d), headers={'content-type': 'application/json'} - + result = client.simulate_post( + "/webhook", body=json.dumps(d), headers={"content-type": "application/json"} ) - message = ActivityUpdate() - message.athlete_id = d['owner_id'] - message.event_time = arrow.get(d['event_time']).datetime - message.activity_id = d['object_id'] - message.operation = AspectType(d['aspect_type']) - message.updates = d['updates'] + message = ActivityUpdate() + message.athlete_id = d["owner_id"] + message.event_time = arrow.get(d["event_time"]).datetime + message.activity_id = d["object_id"] + message.operation = AspectType(d["aspect_type"]) + message.updates = d["updates"] called_with = dict( - activity_id=d['object_id'], - athlete_id=d['owner_id'], - operation=d['aspect_type'], - event_time='2013-01-23T05:35:59+00:00', - updates=d['updates'] + activity_id=d["object_id"], + athlete_id=d["owner_id"], + operation=d["aspect_type"], + event_time="2013-01-23T05:35:59+00:00", + updates=d["updates"], ) - publisher.publish_message.assert_called_with(called_with, dest=DefinedTubes.activity_update) + publisher.publish_message.assert_called_with( + called_with, dest=DefinedTubes.activity_update + ) # def test_post_webhook_noop(client, publisher:ActivityPublisher): @@ -76,4 +79,4 @@ def test_post_webhook(client, publisher:ActivityPublisher): # # client.simulate_post('/webhook', body=json.dumps(d), headers={'content-type': 'application/json'}) # -# publisher.publish_message.assert_not_called() \ No newline at end of file +# publisher.publish_message.assert_not_called()