Update MembershipWorks module to v2 of the API, where available

still stuck using the v1 csv endpoint, haven't found an easy
alternative and it hasn't been changed yet for their v2
This commit is contained in:
Adam Goldsmith 2020-10-27 21:05:21 -04:00
parent cfccc433dd
commit 3849aca918

View File

@ -3,7 +3,7 @@ from io import StringIO
import requests
BASE_URL = "https://api.membershipworks.com/v1/"
BASE_URL = "https://api.membershipworks.com"
# extracted from `SF._is.crm` in https://cdn.membershipworks.com/all.js
CRM = {
@ -72,21 +72,30 @@ class MembershipWorksRemoteError(Exception):
class MembershipWorks:
def __init__(self):
self.sess = requests.Session()
self.org_info = None
self.auth_token = None
self.org_num = None
def login(self, username, password):
"""Authenticate against the membershipworks api"""
r = requests.post(
BASE_URL + "usr",
data={"_st": "all", "eml": username, "org": "10000", "pwd": password},
r = self.sess.post(
BASE_URL + "/v2/account/session",
data={"eml": username, "pwd": password},
headers={"X-Org": "10000"},
)
if r.status_code != 200 or "SF" not in r.json():
raise MembershipWorksRemoteError("login", r)
self.org_info = r.json()
self.auth_token = self.org_info["SF"]
self.org_num = self.org_info["org"]
self.sess.headers.update(
{
"X-Org": str(self.org_num),
"X-Role": "admin",
"Authorization": "Bearer " + self.auth_token,
}
)
def _inject_auth(self, kwargs):
# TODO: should probably be a decorator or something
@ -97,12 +106,12 @@ class MembershipWorks:
kwargs["params"] = {}
kwargs["params"]["SF"] = self.auth_token
def _get(self, *args, **kwargs):
def _get_v1(self, *args, **kwargs):
self._inject_auth(kwargs)
# TODO: should probably do some error handling in here
return requests.get(*args, **kwargs)
def _post(self, *args, **kwargs):
def _post_v1(self, *args, **kwargs):
self._inject_auth(kwargs)
# TODO: should probably do some error handling in here
return requests.post(*args, **kwargs)
@ -141,32 +150,30 @@ class MembershipWorks:
for dek in self.org_info["dek"]:
# TODO: there must be a better way. this is stupid
if dek["dek"] == 1:
ret["folders"][dek["lbl"]] = dek["_id"]
ret["folders"][dek["lbl"]] = dek["did"]
elif "cur" in dek:
ret["levels"][dek["lbl"]] = dek["_id"]
ret["levels"][dek["lbl"]] = dek["did"]
elif "mux" in dek:
ret["addons"][dek["lbl"]] = dek["_id"]
ret["addons"][dek["lbl"]] = dek["did"]
else:
ret["labels"][dek["lbl"]] = dek["_id"]
ret["labels"][dek["lbl"]] = dek["did"]
return ret
def get_member_ids(self, folders):
folder_map = self._parse_flags()["folders"]
r = self._get(
BASE_URL + "ylp",
params={
"lbl": ",".join([folder_map[f] for f in folders]),
"org": self.org_num,
"var": "_id,nam,ctc",
},
r = self.sess.get(
BASE_URL + "/v2/accounts",
params={"dek": ",".join([folder_map[f] for f in folders])},
)
if r.status_code != 200 or "usr" not in r.json():
raise MembershipWorksRemoteError("user listing", r)
# get list of member ID matching the search
return [user["uid"] for user in r.json()["usr"]]
# dedup with set() to work around people with alt uids
# TODO: figure out why people have alt uids
return set(user["uid"] for user in r.json()["usr"])
# TODO: has issues with aliasing header names:
# ex: "Personal Studio Space" Label vs Membership Addon/Field
@ -179,8 +186,8 @@ class MembershipWorks:
# get members CSV
# TODO: maybe can just use previous get instead? would return JSON
r = self._post(
BASE_URL + "csv",
r = self._post_v1(
BASE_URL + "/v1/csv",
data={
"_rt": "946702800", # unknown
"mux": "", # unknown
@ -200,8 +207,8 @@ class MembershipWorks:
json gets a different version of the transactions list,
which contains a different set information
"""
r = self._get(
BASE_URL + "csv",
r = self._get_v1(
BASE_URL + "/v1/csv",
params={
"crm": "12,13,14,18,19", # transaction types, see CRM
**({"txl": ""} if json else {}),