import json import requests from typing import Optional from .ethwallet import EthWallet from .endpoints import endPoints from .errors import Errors from .schema import Schema class App(): def __init__(self, tier, app_private_key, app_handle, debug: bool = False): """Initalize the application This lets users initialize the application by providing the tier, application privatekey and application handle Args: tier : SANDBOX,PROD etc app_private_key : ethereum privat key for the application app_handle : application sila handle (app.silamoney.eth) """ self.session = requests.Session() self.tier = tier.lower() self.app_private_key = app_private_key self.app_handle = app_handle self.debug = debug self.updateSchema() def updateSchema(self): """updates schema.py on initialization of app This lets users initialize the schema into schema.py for ease of use Args: None """ endpoint = endPoints["schemaUrl"] message = ["header", "entity", "identity", "crypto", "linkAccount"] for i in message: response = self.get( endpoint % i) sch = {response["message"]: response} Schema.append(sch) def getUrl(self): """construct the url endpoint to make api calls Args: app: the initialized applications """ url = endPoints["apiUrl"] if self.tier == "prod": apiurl = url % "api" else: apiurl = url % self.tier return apiurl def post(self, path, payload, header): """makes a post request to the sila_apis Args: path : path to the endpoint being called payload : json msg to be posted header : contains the usersignature and authsignature """ url = self.getUrl() endpoint = url + path data1 = json.dumps(payload) response = self.session.post( endpoint, data=data1, headers=header) output = response.json() try: output['status_code'] = response.status_code output['headers'] = dict(response.headers) except: pass return output def postFile(self, path, payload, header, fileContents): url = self.getUrl() endpoint = url + path message = json.dumps(payload) if type(fileContents) is dict: files = fileContents else: files = {'file': fileContents} response = requests.post( endpoint, data={'data': message}, headers=header, files=files ) output = response.json() return output def postFileResponse(self, path: str, payload: dict, header: dict) -> requests.Response: url = self.getUrl() endpoint = url + path data = json.dumps(payload) response = self.session.post( endpoint, data=data, headers=header ) if (response.status_code == 200): return response else: return response.json() def postPlaid(self, url, payload): """makes a post request to the sila_apis Args: path : path to the endpoint being called payload : json msg to be posted header : contains the usersignature and authsignature """ content = json.dumps(payload) response = self.session.post( url, data=content, headers={ "Content-Type": "application/json" }) output = response.json() return output def get(self, path): """make a get request using this function Args: path : path to the endpoint """ endpoint = path response = self.session.get(endpoint) return checkResponse(response) def setHeader(self, msg, key: Optional[str] = None, business_key: Optional[str] = None, content_type: Optional[str] = None): """set the application header with usersignature and authsignature Args: key : ethereum private key for the user msg : message being sent should be signed by user """ appsignature = EthWallet.signMessage(msg, self.app_private_key) header = { "authsignature": appsignature, "User-Agent": 'SilaSDK-python/0.2.48' } if content_type is not None and content_type == 'multipart/form-data': pass else: header["Content-Type"]: 'application/json' if content_type is None else content_type if key is not None and len(key.strip()) > 0: header["usersignature"] = EthWallet.signMessage(msg, key.lower()) if business_key is not None and len(business_key.strip()) > 0: header["businesssignature"] = EthWallet.signMessage( msg, business_key.lower()) return header def checkResponse(response): """ check if response is in json or not Args: response : response from the output of other function """ try: response_data = response.json() except json.decoder.JSONDecodeError: response_data = response return response_data
Public https://github.com/Sila-Money/sila-sdk-python/blob/master/silasdk/client.py
Dataclasses let you get rid of many boilerplate code, most often the "init hell": def __init__(self, a): self.a = a
. With dataclasses, it's all done automatically!
Type hints help humans and linters (like mypy
) to understand what to expect "in" and "out" for a function. Not only it serves as a documentation for others (and you after some time, when the code is wiped from your "brain cache"), but also allows using automated tools to find type errors.
This code is not really needed or may be simplified
api_url = url % ('api' if self.tier == 'prod' else self.tier)
By looking at function name, stranger should roughly understand what the function does. If he doesn't, then the name should be changed.
Why data1
?
Surprise! Python's requests
module does not have a timeout by default! So if you don't use the timeout
parameter when making a request and if remote resource hangs forever (or is very slow), your code will hang, too. Always use reasonable timeouts. A good value may be 5 seconds.
Seems like things could be organized in a better way.
How is this supposed to work? If something goes horribly wrong (btw what can happen here?), silently ignore it?
Idiomatic python type check is if isinstance(type, variable)
or if isinstance((type1, type2), variable)
. Using type(variable) == SomeClass
smells because it won't work if variable is a subclass of SomeClass
.
Whatever you want to code, usually someone has already done that. And usually there are some tools to cover your case. And usually you can find them using Google.
Good requests may be not only 200, but 201 etc.
Suggested change:if response.ok:
This code is not really needed or may be simplified
response = self.session.post( url, json=payload, ... )
Key constants of a class should be so-called "class variables" instead of using/defining them inside methods. If you create a child class and want to change those variables, you'll have to rewrite entire methods. With class vars, you only have to overwrite those class vars in child. Example: class MyClass: SOME_VALUE = 1
and class MySubclass(Class): SOME_VALUE = 2
requests
module has built-in method to raise exception on bad response: response.raise_for_status()
. If you need to perform some additional actions on bad response, use if not response.ok: ...
This does not check response status, so even if response is 404 NOT FOUND, the code will still try to decode response content.
Suggested change:response.raise_for_status()
By looking at function name, stranger should roughly understand what the function does. If he doesn't, then the name should be changed.
The checkResponse
function doesn't check the response - instead it parses it and returns response data. Funny fact is, the response isn't checked for bad status (so, for example, response can return error 400 with correct json data, and the library won't catch the error).
Create new review request