Skip to content

Commit

Permalink
Chores/CS and linter fixes backend (#457)
Browse files Browse the repository at this point in the history
* πŸ‘• Apply black CS

* πŸ‘• Apply black CS and ruff fixes

* πŸ“œ Update comment

* πŸ”¨ Fix tests

* πŸ”¨ Fix deprecated ruff config

* πŸ”¨ Modify python version for ruff

* ❌ Remove black

* βž• Add rule for single quotes

* πŸ‘• Apply quote fixes

* βž• Add rule for single quotes
  • Loading branch information
devmount authored Jun 11, 2024
1 parent 37d591e commit a6b5ae3
Show file tree
Hide file tree
Showing 104 changed files with 2,225 additions and 1,922 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,18 +110,17 @@ Contributions are very welcome. Please lint/format code before creating PRs.
### Backend
Backend is formatted using Ruff and Black.
Backend is formatted using Ruff.
```bash
pip install ruff
pip install black
```
Commands (from git root)
```bash
ruff backend
black backend
ruff check backend
ruff check backend --fix
```
### Frontend
Expand Down
35 changes: 19 additions & 16 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ repository = "https://github.com/thunderbird/appointment.git"
[project.optional-dependencies]
cli = [
"ruff",
"black"
]
db = [
"mysqlclient==2.1.1",
Expand All @@ -35,13 +34,7 @@ dependencies = { file = ["requirements.txt"] }

# Ruff
[tool.ruff]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
select = ["E", "F"]
ignore = []

# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
unfixable = []
line-length = 120

# Exclude a variety of commonly ignored directories.
exclude = [
Expand All @@ -67,22 +60,32 @@ exclude = [
"venv",
]

# Same as Black.
line-length = 120
# Always generate Python 3.11-compatible code.
target-version = "py311"

[tool.ruff.format]
# Prefer single quotes over double quotes.
quote-style = "single"

[tool.ruff.lint]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
select = ["E", "F"]
ignore = []

# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
unfixable = []

# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

# Assume Python 3.10.
target-version = "py310"
[tool.ruff.lint.flake8-quotes]
inline-quotes = "single"

[tool.ruff.mccabe]
[tool.ruff.lint.mccabe]
# Unlike Flake8, default to a complexity level of 10.
max-complexity = 10

[tool.black]
line-length = 120

[tool.pytest.ini_options]
pythonpath = "test"

Expand Down
6 changes: 2 additions & 4 deletions backend/src/appointment/commands/create_invite_codes.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import os

from ..database import repo
from ..dependencies.database import get_engine_and_session


def run(n: int):
print(f"Generating {n} new invite codes...")
print(f'Generating {n} new invite codes...')

_, session = get_engine_and_session()
db = session()
Expand All @@ -14,4 +12,4 @@ def run(n: int):

db.close()

print(f"Successfull added {len(codes)} shiny new invite codes to the database.")
print(f'Successfull added {len(codes)} shiny new invite codes to the database.')
12 changes: 7 additions & 5 deletions backend/src/appointment/commands/download_legal.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@


def run():
"""Helper function to update privacy and terms. Please check to ensure you're not getting a 404 before committing lol."""
print("Downloading the latest legal documents...")
"""Helper function to update privacy and terms.
Please check to ensure you're not getting a 404 before committing lol.
"""
print('Downloading the latest legal documents...')

extensions = ['markdown.extensions.attr_list']
# Only english for now. There's no german TB privacy policy?
Expand All @@ -20,19 +22,19 @@ def run():
os.makedirs(f'{os.path.dirname(__file__)}/../tmp/legal/{locale}', exist_ok=True)

if privacy_policy:
print("Privacy policy url found.")
print('Privacy policy url found.')
contents = requests.get(privacy_policy).text
html = markupsafe.Markup(markdown.markdown(contents, extensions=extensions))

with open(f'{os.path.dirname(__file__)}/../tmp/legal/{locale}/privacy.html', 'w') as fh:
fh.write(html)

if terms_of_use:
print("Terms of use url found.")
print('Terms of use url found.')
contents = requests.get(terms_of_use).text
html = markupsafe.Markup(markdown.markdown(contents, extensions=extensions))

with open(f'{os.path.dirname(__file__)}/../tmp/legal/{locale}/terms.html', 'w') as fh:
fh.write(html)

print("Done! Copy them over to the frontend/src/assets/legal!")
print('Done! Copy them over to the frontend/src/assets/legal!')
15 changes: 7 additions & 8 deletions backend/src/appointment/commands/update_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@


def run():
print("Checking if we have a fresh database...")
print('Checking if we have a fresh database...')

# then, load the Alembic configuration and generate the
# version table, "stamping" it with the most recent rev:
from alembic import command
from alembic.config import Config

# TODO: Does this work on stage?
alembic_cfg = Config("./alembic.ini")
alembic_cfg = Config('./alembic.ini')

# If we have our database url env variable set, use that instead!
if os.getenv("DATABASE_URL"):
alembic_cfg.set_main_option("sqlalchemy.url", os.getenv("DATABASE_URL"))
if os.getenv('DATABASE_URL'):
alembic_cfg.set_main_option('sqlalchemy.url', os.getenv('DATABASE_URL'))

engine, _ = get_engine_and_session()

Expand All @@ -31,10 +31,9 @@ def run():
# If we have no revisions, then fully create the database from the model metadata,
# and set our revision number to the latest revision. Otherwise run any new migrations
if len(revisions) == 0:
print("Initializing database, and setting it to the latest revision")
print('Initializing database, and setting it to the latest revision')
models.Base.metadata.create_all(bind=engine)
command.stamp(alembic_cfg, "head")
command.stamp(alembic_cfg, 'head')
else:
print("Database already initialized, running migrations")
print('Database already initialized, running migrations')
command.upgrade(alembic_cfg, 'head')

64 changes: 40 additions & 24 deletions backend/src/appointment/controller/apis/fxa_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def from_url(url):
# Check our supported scopes
scopes = response.get('scopes_supported')
if 'profile' not in scopes:
logging.warning("Profile scope not found in supported scopes for fxa!")
logging.warning('Profile scope not found in supported scopes for fxa!')

config = FxaConfig()
config.issuer = response.get('issuer')
Expand All @@ -44,7 +44,7 @@ class FxaClient:
ENTRYPOINT = 'tbappointment'

SCOPES = [
"profile",
'profile',
]

config = FxaConfig()
Expand All @@ -61,17 +61,25 @@ def __init__(self, client_id, client_secret, callback_url):

def setup(self, subscriber_id=None, token=None):
"""Retrieve the openid connect urls, and setup our client connection"""
if type(token) is str:
if isinstance(token, str):
token = json.loads(token)

self.config = FxaConfig.from_url(os.getenv('FXA_OPEN_ID_CONFIG'))

self.subscriber_id = subscriber_id
self.client = OAuth2Session(self.client_id, redirect_uri=self.callback_url, scope=self.SCOPES,
auto_refresh_url=self.config.token_url,
auto_refresh_kwargs={"client_id": self.client_id, "client_secret": self.client_secret, 'include_client_id': True},
token=token,
token_updater=self.token_saver)
self.client = OAuth2Session(
self.client_id,
redirect_uri=self.callback_url,
scope=self.SCOPES,
auto_refresh_url=self.config.token_url,
auto_refresh_kwargs={
'client_id': self.client_id,
'client_secret': self.client_secret,
'include_client_id': True,
},
token=token,
token_updater=self.token_saver,
)

def is_in_allow_list(self, db, email: str):
"""Check this email against our allow list"""
Expand All @@ -93,22 +101,27 @@ def get_redirect_url(self, db, state, email):
raise NotInAllowListException()

utm_campaign = f"{self.ENTRYPOINT}_{os.getenv('APP_ENV')}"
utm_source = "login"
utm_source = 'login'

try:
response = self.client.get(url=self.config.metrics_flow_url, params={
'entrypoint': self.ENTRYPOINT,
'form_type': 'email',
'utm_campaign': utm_campaign,
'utm_source': utm_source
})
response = self.client.get(
url=self.config.metrics_flow_url,
params={
'entrypoint': self.ENTRYPOINT,
'form_type': 'email',
'utm_campaign': utm_campaign,
'utm_source': utm_source,
},
)

response.raise_for_status()

flow_values = response.json()
except requests.HTTPError as e:
# Not great, but we can still continue along..
logging.error(f"Could not initialize metrics flow, error occurred: {e.response.status_code} - {e.response.text}")
logging.error(
f'Could not initialize metrics flow, error occurred: {e.response.status_code} - {e.response.text}'
)
flow_values = {}

url, state = self.client.authorization_url(
Expand All @@ -122,13 +135,15 @@ def get_redirect_url(self, db, state, email):
flow_begin_time=flow_values.get('flowBeginTime'),
flow_id=flow_values.get('flowId'),
utm_source=utm_source,
utm_campaign=utm_campaign
utm_campaign=utm_campaign,
)

return url, state

def get_credentials(self, code: str):
return self.client.fetch_token(self.config.token_url, code, client_secret=self.client_secret, include_client_id=True)
return self.client.fetch_token(
self.config.token_url, code, client_secret=self.client_secret, include_client_id=True
)

def token_saver(self, token):
"""requests-oauth automagically calls this function when it has a new refresh token for us.
Expand All @@ -141,7 +156,9 @@ def token_saver(self, token):
if self.subscriber_id is None:
return

repo.external_connection.update_token(next(get_db()), json.dumps(token), self.subscriber_id, models.ExternalConnectionType.fxa)
repo.external_connection.update_token(
next(get_db()), json.dumps(token), self.subscriber_id, models.ExternalConnectionType.fxa
)

def get_profile(self):
"""Retrieve the user's profile information"""
Expand All @@ -156,11 +173,10 @@ def logout(self):
raise MissingRefreshTokenException()

# This route doesn't want auth! (Because we're destroying it)
resp = requests.post(self.config.destroy_url, json={
'refresh_token': refresh_token,
'client_id': self.client_id,
'client_secret': self.client_secret
})
resp = requests.post(
self.config.destroy_url,
json={'refresh_token': refresh_token, 'client_id': self.client_id, 'client_secret': self.client_secret},
)

resp.raise_for_status()
return resp
Expand Down
Loading

0 comments on commit a6b5ae3

Please sign in to comment.