Skip to content

Commit

Permalink
Implement a feature to check for overlap in alarms
Browse files Browse the repository at this point in the history
Add a feature to get existing alarms
Handle deletion of more than one alarms
Remove env var for pre-commit
Remove filtering process name for pre-commit
Fix broken docs
  • Loading branch information
dormant-user committed Aug 22, 2023
1 parent 7694b9f commit 331d1fe
Show file tree
Hide file tree
Showing 14 changed files with 12,881 additions and 2,768 deletions.
2,271 changes: 2,177 additions & 94 deletions docs/genindex.html

Large diffs are not rendered by default.

12,146 changes: 9,811 additions & 2,335 deletions docs/index.html

Large diffs are not rendered by default.

Binary file modified docs/objects.inv
Binary file not shown.
470 changes: 470 additions & 0 deletions docs/py-modindex.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/searchindex.js

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion gen_docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# This is the opposite of the default shell behaviour, which is to ignore errors in scripts.
set -e

export pre_commit=1;
rm -rf docs # Remove existing docs directory
mkdir docs # Create new docs directory
mkdir -p docs_gen/_static # Create a _static directory if unavailable
Expand Down
5 changes: 2 additions & 3 deletions jarvis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
from multiprocessing import current_process

version = "3.5.1"
version = "3.6"

install_script = os.path.join(os.path.dirname(__file__), 'lib', 'install.sh')

Expand All @@ -21,8 +21,7 @@
"Note: Shell script will quit for any non-zero exit status, "
"so it might have to be triggered twice.")
else:
if current_process().name == 'MainProcess' and \
not os.environ.get('pre_commit'): # Ignore unwanted triggers when docs are generated
if current_process().name == 'MainProcess':
current_process().name = 'JARVIS'
from ._preexec import keywords_handler # noqa: F401
from .main import start # noqa: F401
4 changes: 0 additions & 4 deletions jarvis/_preexec/keywords_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@
from jarvis.modules.models import models
from jarvis.modules.utils import support

# Used by docs, actual fileio is created in models.py as it gets invoked during the imports above
if not os.path.isdir(models.fileio.root):
os.mkdir(models.fileio.root)


def rewrite_keywords() -> NoReturn:
"""Loads keywords.yaml file if available, else loads the base keywords module as an object."""
Expand Down
196 changes: 100 additions & 96 deletions jarvis/api/routers/investment.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,105 +13,109 @@
from jarvis.api.logger import logger
from jarvis.api.models import authenticator, settings
from jarvis.api.squire import timeout_otp
from jarvis.modules.exceptions import APIResponse
from jarvis.modules.exceptions import (CONDITIONAL_ENDPOINT_RESTRICTION,
APIResponse)
from jarvis.modules.models import models
from jarvis.modules.templates import templates
from jarvis.modules.utils import util

router = APIRouter()

# Conditional endpoint: Condition matches without env vars during docs generation
if os.environ.get('pre_commit') or all([models.env.robinhood_user, models.env.robinhood_pass,
models.env.robinhood_pass, models.env.robinhood_endpoint_auth]):
@router.post(path="/robinhood-authenticate", dependencies=authenticator.ROBINHOOD_PROTECTOR)
async def authenticate_robinhood():
"""Authenticates the request and generates single use token.
Raises:
APIResponse:
- 200: If initial auth is successful and single use token is successfully sent via email.
- 503: If failed to send the single use token via email.
See Also:
If basic auth (stored as an env var robinhood_endpoint_auth) succeeds:
- Sends a token for MFA via email.
- Also stores the token in the Robinhood object which is verified in the /investment endpoint.
- The token is nullified in the object as soon as it is verified, making it single use.
"""
mail_obj = gmailconnector.SendEmail(gmail_user=models.env.open_gmail_user,
gmail_pass=models.env.open_gmail_pass)
auth_stat = mail_obj.authenticate
if not auth_stat.ok:
raise APIResponse(status_code=HTTPStatus.SERVICE_UNAVAILABLE.real, detail=auth_stat.body)
settings.robinhood.token = util.keygen_uuid(length=16)
rendered = jinja2.Template(templates.email.one_time_passcode).render(ENDPOINT="'robinhood' endpoint",
TOKEN=settings.robinhood.token,
EMAIL=models.env.recipient)
mail_stat = mail_obj.send_email(recipient=models.env.recipient, sender='Jarvis API',
subject=f"Robinhood Token - {datetime.now().strftime('%c')}",
html_body=rendered)
if mail_stat.ok:
logger.debug(mail_stat.body)
logger.info("Token will be reset in 5 minutes.")
Timer(function=timeout_otp.reset_robinhood, interval=300).start()
raise APIResponse(status_code=HTTPStatus.OK.real,
detail="Authentication success. Please enter the OTP sent via email:")
else:
logger.error(mail_stat.json())
raise APIResponse(status_code=HTTPStatus.SERVICE_UNAVAILABLE.real, detail=mail_stat.body)

# Conditional endpoint: Condition matches without env vars during docs generation
if os.environ.get('pre_commit') or all([models.env.robinhood_user, models.env.robinhood_pass,
models.env.robinhood_pass, models.env.robinhood_endpoint_auth]):
@router.get(path="/investment", response_class=HTMLResponse)
async def robinhood_path(request: Request, token: str = None):
"""Serves static file.
Args:
- request: Takes the Request class as an argument.
- token: Takes custom auth token as an argument.
Raises:
APIResponse:
- 403: If token is null.
- 404: If the HTML file is not found.
- 417: If token doesn't match the auto-generated value.
Returns:
HTMLResponse:
Renders the html page.
See Also:
- This endpoint is secured behind single use token sent via email as MFA (Multi-Factor Authentication)
- Initial check is done by the function authenticate_robinhood behind the path "/robinhood-authenticate"
- Once the auth succeeds, a one-time usable hex-uuid is generated and stored in the Robinhood object.
- This UUID is sent via email to the env var RECIPIENT, which should be entered as query string.
- The UUID is deleted from the object as soon as the argument is checked for the first time.
- Page refresh is useless because the value in memory is cleared as soon as it is authed once.
"""
logger.debug("Connection received from %s via %s using %s" %
(request.client.host, request.headers.get('host'), request.headers.get('user-agent')))

if not token:
raise APIResponse(status_code=HTTPStatus.UNAUTHORIZED.real,
detail=HTTPStatus.UNAUTHORIZED.phrase)
# token might be present because its added as headers but robinhood.token will be cleared after one time auth
if settings.robinhood.token and secrets.compare_digest(token, settings.robinhood.token):
settings.robinhood.token = None
if not os.path.isfile(models.fileio.robinhood):
raise APIResponse(status_code=HTTPStatus.NOT_FOUND.real, detail='Static file was not found on server.')
with open(models.fileio.robinhood) as static_file:
html_content = static_file.read()
content_type, _ = mimetypes.guess_type(html_content)
return HTMLResponse(status_code=HTTPStatus.TEMPORARY_REDIRECT.real,
content=html_content, media_type=content_type) # serves as a static webpage
else:
raise APIResponse(status_code=HTTPStatus.EXPECTATION_FAILED.real,
detail='Requires authentication since endpoint uses single-use token.')

@router.post(path="/robinhood-authenticate", dependencies=authenticator.ROBINHOOD_PROTECTOR)
async def authenticate_robinhood():
"""Authenticates the request and generates single use token.
Raises:
APIResponse:
- 200: If initial auth is successful and single use token is successfully sent via email.
- 503: If failed to send the single use token via email.
See Also:
If basic auth (stored as an env var robinhood_endpoint_auth) succeeds:
- Sends a token for MFA via email.
- Also stores the token in the Robinhood object which is verified in the /investment endpoint.
- The token is nullified in the object as soon as it is verified, making it single use.
"""
if not all([models.env.robinhood_user, models.env.robinhood_pass,
models.env.robinhood_pass, models.env.robinhood_endpoint_auth]):
raise CONDITIONAL_ENDPOINT_RESTRICTION
mail_obj = gmailconnector.SendEmail(gmail_user=models.env.open_gmail_user,
gmail_pass=models.env.open_gmail_pass)
auth_stat = mail_obj.authenticate
if not auth_stat.ok:
raise APIResponse(status_code=HTTPStatus.SERVICE_UNAVAILABLE.real, detail=auth_stat.body)
settings.robinhood.token = util.keygen_uuid(length=16)
rendered = jinja2.Template(templates.email.one_time_passcode).render(ENDPOINT="'robinhood' endpoint",
TOKEN=settings.robinhood.token,
EMAIL=models.env.recipient)
mail_stat = mail_obj.send_email(recipient=models.env.recipient, sender='Jarvis API',
subject=f"Robinhood Token - {datetime.now().strftime('%c')}",
html_body=rendered)
if mail_stat.ok:
logger.debug(mail_stat.body)
logger.info("Token will be reset in 5 minutes.")
Timer(function=timeout_otp.reset_robinhood, interval=300).start()
raise APIResponse(status_code=HTTPStatus.OK.real,
detail="Authentication success. Please enter the OTP sent via email:")
else:
logger.error(mail_stat.json())
raise APIResponse(status_code=HTTPStatus.SERVICE_UNAVAILABLE.real, detail=mail_stat.body)


@router.get(path="/investment", response_class=HTMLResponse)
async def robinhood_path(request: Request, token: str = None):
"""Serves static file.
Args:
- request: Takes the Request class as an argument.
- token: Takes custom auth token as an argument.
Raises:
APIResponse:
- 403: If token is null.
- 404: If the HTML file is not found.
- 417: If token doesn't match the auto-generated value.
Returns:
HTMLResponse:
Renders the html page.
See Also:
- This endpoint is secured behind single use token sent via email as MFA (Multi-Factor Authentication)
- Initial check is done by the function authenticate_robinhood behind the path "/robinhood-authenticate"
- Once the auth succeeds, a one-time usable hex-uuid is generated and stored in the Robinhood object.
- This UUID is sent via email to the env var RECIPIENT, which should be entered as query string.
- The UUID is deleted from the object as soon as the argument is checked for the first time.
- Page refresh is useless because the value in memory is cleared as soon as it is authed once.
"""
logger.debug("Connection received from %s via %s using %s" %
(request.client.host, request.headers.get('host'), request.headers.get('user-agent')))

if not all([models.env.robinhood_user, models.env.robinhood_pass,
models.env.robinhood_pass, models.env.robinhood_endpoint_auth]):
raise CONDITIONAL_ENDPOINT_RESTRICTION

if not token:
raise APIResponse(status_code=HTTPStatus.UNAUTHORIZED.real,
detail=HTTPStatus.UNAUTHORIZED.phrase)
# token might be present because its added as headers but robinhood.token will be cleared after one time auth
if settings.robinhood.token and secrets.compare_digest(token, settings.robinhood.token):
settings.robinhood.token = None
if not os.path.isfile(models.fileio.robinhood):
raise APIResponse(status_code=HTTPStatus.NOT_FOUND.real, detail='Static file was not found on server.')
with open(models.fileio.robinhood) as static_file:
html_content = static_file.read()
content_type, _ = mimetypes.guess_type(html_content)
return HTMLResponse(status_code=HTTPStatus.TEMPORARY_REDIRECT.real,
content=html_content, media_type=content_type) # serves as a static webpage
else:
raise APIResponse(status_code=HTTPStatus.EXPECTATION_FAILED.real,
detail='Requires authentication since endpoint uses single-use token.')
3 changes: 1 addition & 2 deletions jarvis/api/routers/stock_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ async def get_signals(symbol: str, bar_count: int = 100, data_dict: bool = False
if data_dict:
raise APIResponse(status_code=HTTPStatus.OK.real, detail=settings.trader.stock_list)
raise APIResponse(status_code=HTTPStatus.OK.real, detail=json.dumps(settings.trader.stock_list))
# todo:
# repeated URL errors, no luck with traditional loop
# TODO: repeated URL errors, no luck with traditional loop
# thread_worker(function_to_call=get_signals_per_ticker)
# result = {
# k: '\n'.join(v) for k, v in settings.trader.result.items()
Expand Down
Loading

0 comments on commit 331d1fe

Please sign in to comment.