Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance EveryAction activist code handling with caching and improved … #5

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion target_everyaction/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@ def validate_response(self, response: requests.Response) -> None:
msg = self.response_error_message(response)
raise FatalAPIError(msg)

def request_api(self, method, request_data, endpoint):
def request_api(self, method, endpoint, request_data=None, params=None):
url = f"{self.base_url}{endpoint}"
LOGGER.info(self.__auth)
response = requests.request(
method,
url,
json=request_data,
params=params,
auth=self.__auth,
headers={"Content-Type": "application/json", "Accept": "application/json"},
)
Expand Down
81 changes: 68 additions & 13 deletions target_everyaction/sinks.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,11 @@ def _get_or_create_code(self, code_payload: dict) -> Optional[str]:
item["name"].lower(): item["codeId"]
for item in data["items"]
})
if "nextPageLink" not in data:
if "nextPageLink" in data and data["nextPageLink"]:
response = self.request_api("GET",
endpoint=f"codes?{data['nextPageLink'].split('?')[1]}")
else:
break
response = self.request_api("GET",
endpoint=f"codes?{data['nextPageLink'].split('?')[1]}")
else:
break

Expand All @@ -101,6 +102,62 @@ def _get_or_create_code(self, code_payload: dict) -> Optional[str]:
request_data=code_payload)
return response.json() if response.ok else None

def _get_all_activist_codes(self) -> dict:
"""Get all activist codes and cache them in memory."""
all_codes = {}
params = {
"statuses": "Active,Archived",
"$top": 200
}

response = self.request_api("GET", endpoint="activistCodes", params=params)
while True:
if response.ok:
data = response.json()
# Store codes with lowercase names for case-insensitive lookup
for item in data["items"]:
all_codes[item["name"].lower()] = item["activistCodeId"]

# Extract next page URL and make request
if "nextPageLink" in data and data["nextPageLink"]:
next_page = data["nextPageLink"].split("?")[1]
response = self.request_api("GET", endpoint=f"activistCodes?{next_page}")
else:
break
else:
break

return all_codes

def _process_activist_codes(self, person_id: int, code_names: list, cached_codes: dict):
"""Process activist codes for a person."""
missing_codes = []

# Build payload for existing codes
responses = []
for code_name in code_names:
code_id = cached_codes.get(code_name.lower())
if code_id:
responses.append({
"activistCodeId": code_id,
"action": "Apply",
"type": "ActivistCode"
})
else:
missing_codes.append(code_name)
LOGGER.warning(f"Activist code '{code_name}' not found")

# Apply existing codes in a single request if any exist
if responses:
payload = {"responses": responses}
self.request_api(
"POST",
endpoint=f"people/{person_id}/canvassResponses",
request_data=payload
)

return missing_codes

def upsert_record(self, record: dict, context: dict):
method = "POST"
state_dict = dict()
Expand All @@ -113,16 +170,14 @@ def upsert_record(self, record: dict, context: dict):
if hasattr(self, "pending_codes"):
# Activist codes
if self.pending_codes.get("activist"):
for code in self.pending_codes["activist"]:
payload = {
"responses": [{
"activistCodeId": code,
"action": "Apply",
"type": "ActivistCode"
}]
}
self.request_api("POST", endpoint=f"people/{id}/canvassResponses",
request_data=payload)
# Get all activist codes once
cached_codes = self._get_all_activist_codes()
# Process all codes for this person
missing_codes = self._process_activist_codes(
id,
self.pending_codes["activist"],
cached_codes
)

# Handle both Source Codes and Tags
for code_type, codes in self.pending_codes.items():
Expand Down