Skip to content

Conversation

@codybraun
Copy link
Contributor

@codybraun codybraun commented Jun 30, 2025

What is this change?

  • We've been seeing a good number of 5xx statuses back from NewMode. They seem to usually just clear on a retry so let's save people the trouble of running a manual retry
  • We are also not paginating- so only the first 50 submissions are being synced for example, when there are hundreds of them
  • I also added type hints because I was having a hard time following the various transformations we're doing on the Parsons Table

Considerations for discussion

  • I don't know that you would want the retry behavior by default on create requests- the only POST I see here though is to submit a job, where I would guess this is okay. Happy to update those assumptions though

How to test the changes (if needed)

  • pytest test
  • or
nm = Newmode(); subs = nm.get_submissions('4c18a593-75a3-4d61-aa87-200d7ed7a35f'); len(sub)

(obviously you'll need to add client id and secret to this and pick a campaign id that exists in that account)

@github-actions
Copy link

github-actions bot commented Jun 30, 2025

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  parsons/newmode
  newmode.py 452-453, 485, 720
Project Total  

This report was generated by python-coverage-comment-action

@codybraun codybraun requested a review from shaunagm June 30, 2025 15:28
@codybraun codybraun changed the title NewMode retries NewMode retries and pagination Jul 1, 2025
@shaunagm
Copy link
Collaborator

shaunagm commented Jul 1, 2025

@codybraun feel free to @ me once you're done adding things. also hope the linting and formatting checks aren't too burdensome - please let me know if they are!

@codybraun
Copy link
Contributor Author

@codybraun feel free to @ me once you're done adding things. also hope the linting and formatting checks aren't too burdensome - please let me know if they are!

@shaunagm I'm done! The CI checks are all very reasonable, I've just been too lazy to configure VSC with the different linting setup for Parsons, which is on me! I'll do that if I'm committing code regularly here

Copy link
Collaborator

@shaunagm shaunagm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I overall support this but I'm not sure I understand how base_request, paginated_request and converted_request relate to each other

logger.error("Response is not in JSON format.")
raise ValueError(f"API request encountered an error. Response: {response}")

def base_request(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did a couple of these params on base_request get removed (endpoint, supports_version, etc)? what's the reasoning there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah we were previously building the full URL in base_request- that's fine, except now we need some method that can take the already-full-url returned by "next". So we need a base method that doesn't do any decorating of the URL- the same thing should still be happening it is just a level higher in the paginator method

if params is None:
params = {}

for attempt in range(retries + 1):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why retries +1? and not just retries?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that just keeps this sort of semantically correct- so if you said retries=0, you probably still want it to attempt once and then fail. retries=1 should mean two total attempts, etc. - make sense?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, makes sense!

raise e
raise Exception(f"Failed to retrieve data from {url} after {retries} attempts.")

def paginate_request(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh is this where those other params ended up? Can you briefly at a high level explain the overall design/refactoring happening here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, basically we need somewhat of a refactor to handle the full URL returned by the "next" pointer to handle iteration. That next pointer already includes the API version, the endpoint, etc. so we want some method that just takes the URL and makes the request, then does our status checks and returns if those look good.

That just means when we are first assembling the URL for an initial request, we just need to do that a level higher (the new pagination wrapper method). Does that make sense? Open to other suggestions for how to refactor this but I don't see where we could handle the pagination without some changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and converted_request I didn't really mess with in part because I don't feel like I have a great understanding of Parsons tables- but basically that wraps paginate_request which wraps base_request

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change - this makes perfect sense, I just couldn't quite follow the logic (the type hints while a nice addition made it a little harder to parse what substantively was being changed)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah sorry, the typing added a bunch to the diff

params = {"action": campaign_id}
response = self.converted_request(endpoint="submission", method="GET", params=params)
response = self.converted_request(
endpoint="submission", method="GET", params=params, data_key=RESPONSE_DATA_KEY
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious why some calls to converted_request pass in data_key=RESPONSE_DATA_KEY and some don't

Copy link
Contributor Author

@codybraun codybraun Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I tried to avoid changing this as much as I could, but it looks like that's because in some places we're GETting campaigns/ which returns something like {"data": [{"campaign_id":1....}] and in other we're GETing /campaigns/1 which returns {"campaign_id":1....} (note that this is not nested)

@shaunagm shaunagm added the breaking change applied only to PRs, indicates when something has a breaking change to be flagged in release notes label Jul 1, 2025
@shaunagm
Copy link
Collaborator

shaunagm commented Jul 1, 2025

I'm adding the breaking change label just in case so I can flag it in the release notes, even though the signature we're changing is a private method and so it's not a big deal that we're breaking it

@shaunagm shaunagm merged commit 74e9cbd into main Jul 1, 2025
70 checks passed
@codybraun codybraun deleted the cody/fix/new-mode-retries branch July 1, 2025 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change applied only to PRs, indicates when something has a breaking change to be flagged in release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants