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

Feature/api refactor #604

Merged
merged 78 commits into from
Dec 20, 2022
Merged

Feature/api refactor #604

merged 78 commits into from
Dec 20, 2022

Conversation

Magnus167
Copy link
Member

No description provided.

@lsimonsen lsimonsen added the enhancement New feature or request label Dec 1, 2022
@lsimonsen lsimonsen self-requested a review December 1, 2022 16:01
@lsimonsen
Copy link
Member

Suggestion for refactoring of overwall structure`

  • download
    • dq_auth.py
    • dq_api.py
    • jpmaqs.py

and include a stub dataquery folder, where we can redirect from with a deprecation warning.

The main interface for users should be the callse download.JPMaQSDownload which has a download attribute. This way, if we need to change/add another data distribution source, we can easily add it as a key-word argument in the jpmaqs.py file.

@lsimonsen lsimonsen marked this pull request as ready for review December 13, 2022 16:00
Comment on lines 233 to 240
while (not success) and (i < 3):
js, success, self.last_url, msg = dq_request(
url=self.__token_url,
data=self.token_data,
method="post",
proxies=self.token_proxy,
)
i += 1
Copy link
Member

Choose a reason for hiding this comment

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

Why are we trying to request multiple times?

Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we are better off using off the shelves libraries for the OAuth handling:

from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session

OAUTH_BASE_URL: str = "https://api-developer.jpmorgan.com/research/dataquery-authe/api/v2/"
OAUTH_TOKEN_URL: str = "https://authe.jpmchase.com/as/token.oauth2"
OAUTH_DQ_RESOURCE_ID: str = "JPMC:URI:RS-06785-DataQueryExternalApi-PROD"

class DQOAuth(object):
    def __init__(self, client_id: str, client_secret: str):
         self.client_id: str = client_id
         self.client_secret = client_secret

         self.client = BackendApplicationClient(client_id=client_id, aud=OAUTH_DQ_RESOURCE_ID)
         self.session = OAuth2Session(client=client)

    def _renew_token(self):
         self.session.fetch_token(
             token_url=OAUTH_TOKEN_URL,
             client_id="<client_id>",
            client_secret="<client_secret>"
          )

   def authorized(self):
        return self.session.authorized

    def request(url: str, method: = "GET"):
        if not self.authorized():
             self._renew_token()


        with self.session.request(url=url, method=method) as r:
             assert r.ok
             js = r.json()
        return js

Copy link
Member Author

Choose a reason for hiding this comment

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

The logic behind the multiple requests was to retry getting the token 3 times if and when it a token renew request fails. I've reverted to the original method (currently in use).

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll look into using the library methods directly. Seems cleaner to use as well.

class AuthenticationError(Exception):
"""Raised when authentication fails."""

pass
Copy link
Member

Choose a reason for hiding this comment

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

No need for the pass here.

tests/unit/download/test_api.py Outdated Show resolved Hide resolved
Comment on lines 85 to 90
logger.error(
f"Invalid credentials."
f"Request failed for URL: {last_url}"
f" with message: {msg}"
f" and response: {js}"
)
Copy link
Member

Choose a reason for hiding this comment

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

We could pass parameters to the logger:

logger.error(
    "Invalid credentials. Request failed for URL: %s with message: %s and response: %s",
    last_url, msg, js
)

Copy link
Member

Choose a reason for hiding this comment

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

logger.error(
    "Invalid credentials. Request failed for URL: %s"
    " with message: %s and response: %s",
    last_url, msg, js
)

macrosynergy/download/dataquery.py Outdated Show resolved Hide resolved
macrosynergy/download/dataquery.py Outdated Show resolved Hide resolved
macrosynergy/download/dataquery.py Outdated Show resolved Hide resolved
macrosynergy/download/dataquery.py Outdated Show resolved Hide resolved
Comment on lines 400 to 402
(not (select in response.keys()))
and (counter <= server_count)
and (invalid_responses <= server_count)
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't we arbitrarily break after 5 retries now as default? Even if the next link has more left?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes; that's true.
I think setting server_count=10 and testing (counter+invalid_responses)<=server_count before breaking might be a better idea.
PS: the variables after refactor are
server_countmax_retries
counterconxn_errors

macrosynergy/download/dataquery.py Outdated Show resolved Hide resolved
Comment on lines +642 to +646
elif count > 5:
recursive_call = False
logger.warning(
f"Error requesting tickers: {', '.join(error_tickers)}. No longer retrying."
)
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be further up? in first error_tickers?

macrosynergy/download/jpmaqs.py Outdated Show resolved Hide resolved

log_url = f"{url}?{requests.compat.urlencode(params)}" if params else url
log_url = requests.compat.quote(log_url, safe="%/:=&?~#+!$,;'@()*[]")
logger.info(f"Requesting URL: {log_url} " + track_id)
Copy link
Member

Choose a reason for hiding this comment

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

I think this will fail if track_id is None:

msg = "Hello, World" + None

Throws a TypeError

Copy link
Member

Choose a reason for hiding this comment

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

Ah got it we convert track id to "" if None.

@Magnus167 Magnus167 merged commit 4f9f3bb into develop Dec 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants