11import operator
2+ from enum import StrEnum
23from logging import Logger , getLogger
4+ from pathlib import Path
35from typing import Any
46
57import requests
68from cachetools import TTLCache , cachedmethod
9+ from requests import Response
10+
11+ from daq_config_server .app import ValidAcceptHeaders
712
813from .constants import ENDPOINTS
914
1015
16+ class RequestedResponseFormats (StrEnum ):
17+ DICT = ValidAcceptHeaders .JSON # Convert to dict using Response.json()
18+ DECODED_STRING = ValidAcceptHeaders .PLAIN_TEXT # Use utf-8 decoding in response
19+ RAW_BYTE_STRING = ValidAcceptHeaders .RAW_BYTES # Use raw bytes in response
20+
21+
1122class ConfigServer :
1223 def __init__ (
1324 self ,
@@ -27,77 +38,105 @@ def __init__(
2738 """
2839 self ._url = url .rstrip ("/" )
2940 self ._log = log if log else getLogger ("daq_config_server.client" )
30- self ._cache : TTLCache [tuple [str , str | None ], str ] = TTLCache (
41+ self ._cache : TTLCache [tuple [str , str , Path ], str ] = TTLCache (
3142 maxsize = cache_size , ttl = cache_lifetime_s
3243 )
3344
34- def _get (
35- self ,
36- endpoint : str ,
37- item : str | None = None ,
38- reset_cached_result : bool = False ,
39- ) -> Any :
40- """
41- Get data from the config server with cache management.
42- If a cached response doesn't already exist, makes a request to
43- the config server.
44- If reset_cached_result is true, remove the cache entry for that request and
45- make a new request
46-
47- Args:
48- endpoint: API endpoint.
49- item: Optional item identifier.
50- reset_cached_result: Whether to reset cache.
51-
52- Returns:
53- The response data.
54- """
55-
56- if (endpoint , item ) in self ._cache and reset_cached_result :
57- del self ._cache [(endpoint , item )]
58- return self ._cached_get (endpoint , item )
59-
6045 @cachedmethod (cache = operator .attrgetter ("_cache" ))
6146 def _cached_get (
6247 self ,
6348 endpoint : str ,
64- item : str | None = None ,
65- ) -> Any :
49+ accept_header : str ,
50+ file_path : Path ,
51+ ) -> Response :
6652 """
6753 Get data from the config server and cache it.
6854
6955 Args:
7056 endpoint: API endpoint.
71- item: Optional item identifier.
57+ file_path: absolute path to the file which will be read
7258
7359 Returns:
7460 The response data.
7561 """
76- url = self ._url + endpoint + (f"/{ item } " if item else "" )
7762
7863 try :
79- r = requests .get (url )
64+ request_url = self ._url + endpoint + (f"/{ file_path } " )
65+ r = requests .get (request_url , headers = {"Accept" : accept_header })
8066 r .raise_for_status ()
81- data = r .json ()
82- self ._log .debug (f"Cache set for { endpoint } /{ item } ." )
83- return data
67+ self ._log .debug (f"Cache set for { request_url } ." )
68+ return r
8469 except requests .exceptions .HTTPError as e :
8570 self ._log .error (f"HTTP error: { e } " )
8671 raise
8772
88- def read_unformatted_file (
89- self , file_path : str , reset_cached_result : bool = False
73+ def _get (
74+ self ,
75+ endpoint : str ,
76+ accept_header : str ,
77+ file_path : Path ,
78+ reset_cached_result : bool = False ,
79+ ):
80+ """
81+ Get data from the config server with cache management and use
82+ the content-type response header to format the return value.
83+ If data parsing fails, return the response contents in bytes
84+ """
85+ if (endpoint , accept_header , file_path ) in self ._cache and reset_cached_result :
86+ del self ._cache [(endpoint , accept_header , file_path )]
87+ r = self ._cached_get (endpoint , accept_header , file_path )
88+
89+ content_type = r .headers ["content-type" ].split (";" )[0 ].strip ()
90+
91+ if content_type != accept_header :
92+ self ._log .warning (
93+ f"Server failed to parse the file as requested. Requested \
94+ { accept_header } but response came as content-type { content_type } "
95+ )
96+
97+ try :
98+ match content_type :
99+ case ValidAcceptHeaders .JSON :
100+ content = r .json ()
101+ case ValidAcceptHeaders .PLAIN_TEXT :
102+ content = r .text
103+ case _:
104+ content = r .content
105+ except Exception as e :
106+ self ._log .warning (
107+ f"Failed trying to convert to content-type { content_type } due to\
108+ exception { e } \n Returning as bytes instead"
109+ )
110+ content = r .content
111+
112+ return content
113+
114+ def get_file_contents (
115+ self ,
116+ file_path : Path ,
117+ requested_response_format : RequestedResponseFormats = (
118+ RequestedResponseFormats .DECODED_STRING
119+ ),
120+ reset_cached_result : bool = False ,
90121 ) -> Any :
91122 """
92- Read an unformatted file from the config server.
123+ Get contents of a file from the config server in the format specified.
124+ If data parsing fails, contents will return as raw bytes. Optionally look
125+ for cached result before making request.
93126
94127 Args:
95128 file_path: Path to the file.
96- reset_cached_result: Whether to reset cache.
97-
129+ requested_response_format: Specify how to parse the response.
130+ reset_cached_result: If true, make a request and store response in cache,
131+ otherwise look for cached response before making
132+ new request
98133 Returns:
99- The file content .
134+ The file contents, in the format specified .
100135 """
136+
101137 return self ._get (
102- ENDPOINTS .CONFIG , file_path , reset_cached_result = reset_cached_result
138+ ENDPOINTS .CONFIG ,
139+ requested_response_format ,
140+ file_path ,
141+ reset_cached_result = reset_cached_result ,
103142 )
0 commit comments