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

Cron / Updated cli #234

Merged
merged 5 commits into from
Jan 22, 2024
Merged
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
6 changes: 6 additions & 0 deletions backend/deploy.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ COPY requirements.txt .
COPY pyproject.toml .
COPY alembic.ini.example alembic.ini
COPY scripts/entry.sh scripts/entry.sh
COPY scripts/cron /etc/cron.d/appointment-cron
Copy link
Collaborator

Choose a reason for hiding this comment

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

This cron file is currently empty, right? Should we add an example entry here for convenience? Or add some documentation how to use it? Or is this handled by Typer automatically?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh yea sorry, the cron is empty. Once we want to add stuff we'll just stuff it in that file and it should work.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice, fine by me 👍🏻


# Setup cron permissions
RUN chmod 0644 /etc/cron.d/appointment-cron
RUN crontab /etc/cron.d/appointment-cron

# Needed for deploy, we don't have a volume attached
COPY src .
Expand All @@ -25,5 +30,6 @@ RUN pip install .'[deploy]'
RUN mkdir src
RUN ln -s /app/appointment src/appointment


EXPOSE 5000
CMD ["/bin/sh", "./scripts/entry.sh"]
1 change: 1 addition & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ sentry-sdk==1.26.0
starlette-context==0.3.6
sqlalchemy-utils==0.39.0
sqlalchemy==1.4.40
typer[all]==0.9.0
tzdata==2022.7
uvicorn==0.20.0
validators==0.20.0
Expand Down
5 changes: 4 additions & 1 deletion backend/scripts/dev-entry.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#!/bin/sh

run-command update-db
run-command main update-db

# Start up fake mail server
python -u -m smtpd -n -c DebuggingServer localhost:8050 &

# Start cron
service cron start

# Start up real webserver
uvicorn --factory appointment.main:server --reload --host 0.0.0.0 --port 5173

2 changes: 1 addition & 1 deletion backend/scripts/entry.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/sh

run-command update-db
run-command main update-db

uvicorn --factory appointment.main:server --host 0.0.0.0 --port 5000
19 changes: 7 additions & 12 deletions backend/src/appointment/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
import logging
import sys

import typer
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.exception_handlers import (
http_exception_handler,
)
from starlette_context import context

import sentry_sdk

Expand Down Expand Up @@ -136,20 +136,15 @@ async def catch_google_refresh_errors(request, exc):

def cli():
"""
A very simple cli handler
Entrypoint for our typer cli
"""

if len(sys.argv) < 2:
print("No command specified")
return

# Run common setup first
_common_setup()

command = sys.argv[1:]

if command[0] == 'update-db':
from .commands import update_db
update_db.run()

from .routes import commands

app = typer.Typer()
# We don't have too many commands, so just dump them under main for now.
app.add_typer(commands.router, name="main")
app()
31 changes: 31 additions & 0 deletions backend/src/appointment/routes/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""This file handles routing for console commands"""
from contextlib import contextmanager
import os

import typer
from ..commands import update_db

router = typer.Typer()


@contextmanager
def cron_lock(lock_name):
"""Context manager helper to create a cron lockfile or error out with FileExistsError."""
lock_file_name = f'/tmp/{lock_name}.lock'

# Lock file exists? Don't run
if os.path.isfile(lock_file_name):
raise FileExistsError

fh = open(lock_file_name, 'w+')
try:
yield
finally:
fh.close()
os.remove(lock_file_name)


@router.command('update-db')
def update_database():
update_db.run()

31 changes: 31 additions & 0 deletions backend/test/unit/test_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import os

import pytest

from appointment.routes.commands import cron_lock


def test_cron_lock():
"""Test our cron lock function, this does use disk io but should clean itself up after."""
test_lock_name = 'test_cron_lock_run'
test_lock_file_name = f'/tmp/{test_lock_name}.lock'

# Clean up in case the lock file previously exists
if os.path.isfile(test_lock_file_name):
os.remove(test_lock_file_name)

# Test that the lock works
with cron_lock(test_lock_name):
assert os.path.isfile(test_lock_file_name)

# And cleans itself up
assert not os.path.isfile(test_lock_file_name)

# Test a lock already exists case with way too many withs.
with open(test_lock_file_name, 'w'):
with pytest.raises(FileExistsError):
with cron_lock(test_lock_name):
pass

# Remove the lock file we manually created
os.remove(test_lock_file_name)