11import functools
2- import json
32import logging
43from datetime import datetime
4+ from typing import Dict
5+ from typing import Iterable
6+ from typing import List
7+ from typing import Tuple
8+ from typing import Union
59
610import aioredis
711import requests
812from aioredis .exceptions import ResponseError
13+ from fastapi import Depends
914from fastapi import FastAPI
1015from pydantic import BaseSettings
1116
1217
13- TIMESERIES_KEY = 'is-bitcoin-lit:sentiment:mean:{time}'
14- SUMMARY_KEY = 'is-bitcoin-lit:summary:hourly:{time}'
15- SENTIMENT_API_URL = 'https://api.senticrypt.com/v1/history/bitcoin-{time}.json'
18+ DEFAULT_KEY_PREFIX = 'is-bitcoin-lit'
19+ SENTIMENT_API_URL = 'HTTps://api.senticrypt.com/v1/history/bitcoin-{time}.json'
1620TIME_FORMAT_STRING = '%Y-%m-%d_%H'
1721
1822
23+ def prefixed_key (f ):
24+ """
25+ A method decorator that prefixes return values.
26+ Prefixes any string that the decorated method `f` returns with the value of
27+ the `prefix` attribute on the owner object `self`.
28+ """
29+
30+ def prefixed_method (self , * args , ** kwargs ):
31+ key = f (self , * args , ** kwargs )
32+ return f'{ self .prefix } :{ key } '
33+
34+ return prefixed_method
35+
36+
37+ class Keys :
38+ """Methods to generate key names for Redis data structures."""
39+
40+ def __init__ (self , prefix : str = DEFAULT_KEY_PREFIX ):
41+ self .prefix = prefix
42+
43+ @prefixed_key
44+ def timeseries_sentiment_key (self ) -> str :
45+ return f'sentiment:mean'
46+
47+ @prefixed_key
48+ def timeseries_price_key (self ) -> str :
49+ return f'price:mean'
50+
51+ @prefixed_key
52+ def summary_key (self ) -> str :
53+ return f'summary:hourly'
54+
55+
1956class Config (BaseSettings ):
2057 redis_url : str = 'redis://redis:6379'
2158
@@ -27,17 +64,44 @@ class Config(BaseSettings):
2764
2865
2966def make_summary (data ):
67+ """Take a series of averages and summarize them as means of means."""
3068 return {
3169 'time' : datetime .now ().timestamp (),
32- 'mean_sentiment' : sum (d ['mean' ] for d in data ) / len (data ),
70+ 'mean_of_means_sentiment' : sum (d ['mean' ] for d in data ) / len (data ),
71+ 'mean_of_means_price' : sum (float (d ['btc_price' ]) for d in data ) / len (data ),
3372 }
3473
3574
75+ async def add_many_to_timeseries (
76+ key_pairs : Iterable [Tuple [str , str ]],
77+ data : List [Dict [str , Union [str , float ]]]
78+ ):
79+ """
80+ Add many samples to a single timeseries key.
81+
82+ `key_pairs` is an iteratble of tuples containing in the 0th position the
83+ timestamp key into which to insert entries and the 1th position the name
84+ of the key within th e`data` dict to find the sample.
85+ """
86+ partial = functools .partial (redis .execute_command , 'TS.MADD' )
87+ for datapoint in data :
88+ for key , attr in key_pairs :
89+ partial = functools .partial (
90+ partial , key , datapoint ['timestamp' ], datapoint [attr ],
91+ )
92+ return await partial ()
93+
94+
95+ def make_keys ():
96+ return Keys ()
97+
98+
3699@app .get ('/is-bitcoin-lit' )
37- async def bitcoin ():
100+ async def bitcoin (keys : Keys = Depends ( make_keys ) ):
38101 sentiment_time = datetime .now ().strftime (TIME_FORMAT_STRING )
39- summary_key = SUMMARY_KEY .format (time = sentiment_time )
40- ts_key = TIMESERIES_KEY .format (time = sentiment_time )
102+ summary_key = keys .summary_key ()
103+ ts_price_key = keys .timeseries_price_key ()
104+ ts_sentiment_key = keys .timeseries_sentiment_key ()
41105 url = SENTIMENT_API_URL .format (time = sentiment_time )
42106
43107 summary = await redis .hgetall (summary_key )
@@ -48,20 +112,21 @@ async def bitcoin():
48112 summary = make_summary (data )
49113 await redis .hset (summary_key , mapping = summary )
50114 await redis .expire (summary_key , 60 )
51- partial = functools . partial ( redis . execute_command , 'TS.MADD' , ts_key )
52- for datapoint in data :
53- partial = functools . partial (
54- partial , datapoint [ 'timestamp' ], datapoint [ ' mean'] ,
55- )
56- await partial ( )
115+ await add_many_to_timeseries (
116+ (
117+ ( ts_price_key , 'btc_price' ),
118+ ( ts_sentiment_key , ' mean') ,
119+ ), data ,
120+ )
57121
58122 return summary
59123
60124
61125@app .on_event ('startup' )
62- async def startup_event ():
126+ async def startup_event (keys : Keys = Depends ( make_keys ) ):
63127 try :
64- redis .execute_command ('TS.CREATE' , TIMESERIES_KEY )
128+ redis .execute_command ('TS.CREATE' , keys .timeseries_sentiment_key ())
129+ redis .execute_command ('TS.CREATE' , keys .timeseries_price_key ())
65130 except ResponseError :
66131 # Time series already exists
67132 pass
0 commit comments