Skip to content

Commit

Permalink
Format with ruff (#108)
Browse files Browse the repository at this point in the history
* enable formatting check

* apply formatting

* remove noqa in imports
  • Loading branch information
facundoolano authored Jul 18, 2024
1 parent 4a38de5 commit 75c1992
Show file tree
Hide file tree
Showing 20 changed files with 1,051 additions and 998 deletions.
4 changes: 1 addition & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ format:

lint:
$(venv) ruff check
# FIXME uncomment in another PR, and apply formatting
# $(venv) ruff format --check
# $(venv) flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude venv,migrations
$(venv) ruff format --check

# Serve the app in development mode
run:
Expand Down
18 changes: 9 additions & 9 deletions feedi/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
def create_app():
app = flask.Flask(__package__)
load_config(app)
app.logger.info('Starting app with FLASK_ENV=%s', os.getenv('FLASK_ENV'))
app.logger.info("Starting app with FLASK_ENV=%s", os.getenv("FLASK_ENV"))

with app.app_context():
from . import auth, filters, routes, tasks # noqa
Expand All @@ -24,7 +24,7 @@ def create_app():

auth.init()

if not is_running_from_reloader() and not os.environ.get('DISABLE_CRON_TASKS'):
if not is_running_from_reloader() and not os.environ.get("DISABLE_CRON_TASKS"):
# we want only one huey scheduler running, so we make sure
# this isn't the dev server reloader process

Expand All @@ -49,14 +49,14 @@ def create_huey_app():
Construct a minimal flask app for exposing to the huey tasks.
This is necessary to make config and db session available to the periodic tasks.
"""
app = flask.Flask('huey_app')
app = flask.Flask("huey_app")
load_config(app)

with app.app_context():
# since huey tasks share the db engine pool, we want to have a big enough pool
# so all concurrent tasks get their own connection
pool_size = round(app.config['HUEY_POOL_SIZE'] * 1.1)
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {'pool_size': pool_size}
pool_size = round(app.config["HUEY_POOL_SIZE"] * 1.1)
app.config["SQLALCHEMY_ENGINE_OPTIONS"] = {"pool_size": pool_size}

models.init_db(app)

Expand All @@ -65,10 +65,10 @@ def create_huey_app():

def load_config(app):
app.logger.setLevel(logging.INFO)
env = os.getenv('FLASK_ENV')
env = os.getenv("FLASK_ENV")
if not env:
app.logger.error('FLASK_ENV not set')
app.logger.error("FLASK_ENV not set")
exit()

app.config.from_object('feedi.config.default')
app.config.from_object(f'feedi.config.{env}')
app.config.from_object("feedi.config.default")
app.config.from_object(f"feedi.config.{env}")
44 changes: 22 additions & 22 deletions feedi/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

def init():
login_manager = flask_login.LoginManager()
login_manager.login_view = 'login'
login_manager.login_view = "login"
login_manager.init_app(app)

@login_manager.user_loader
Expand All @@ -23,56 +23,56 @@ def load_user(user_id):
def login():
# if config has a default user it means auth is disabled
# just load the user so we know what to point feeds to in the DB
default_email = app.config.get('DEFAULT_AUTH_USER')
default_email = app.config.get("DEFAULT_AUTH_USER")
if default_email:
app.logger.debug("Logging default user %s", default_email)
user = db.session.scalar(db.select(models.User).filter_by(email=default_email))
flask_login.login_user(user, remember=True)
return flask.redirect(flask.url_for('entry_list'))
return flask.redirect(flask.url_for("entry_list"))

return flask.render_template('login.html')
return flask.render_template("login.html")


@app.post('/auth/login')
@app.post("/auth/login")
def login_post():
email = flask.request.form.get('email')
password = flask.request.form.get('password')
email = flask.request.form.get("email")
password = flask.request.form.get("password")
if not email or not password:
return flask.render_template('login.html', error_msg="missing required field")
return flask.render_template("login.html", error_msg="missing required field")

user = db.session.scalar(db.select(models.User).filter_by(email=email))

if not user or not user.check_password(password):
return flask.render_template('login.html', error_msg="authentication failed")
return flask.render_template("login.html", error_msg="authentication failed")

flask_login.login_user(user, remember=True)

return flask.redirect(flask.url_for('entry_list'))
return flask.redirect(flask.url_for("entry_list"))


@app.get("/auth/kindle")
@login_required
def kindle_add():
feedi_email = app.config.get('FEEDI_EMAIL')
feedi_email = app.config.get("FEEDI_EMAIL")
if not feedi_email:
return flask.abort(400, "no feedi email configured")
return flask.render_template('kindle.html', feedi_email=feedi_email)
return flask.render_template("kindle.html", feedi_email=feedi_email)


@app.post("/auth/kindle")
@login_required
def kindle_add_submit():
kindle_email = flask.request.form.get('kindle_email')
kindle_email = flask.request.form.get("kindle_email")
current_user.kindle_email = kindle_email
db.session.commit()
return flask.redirect(flask.url_for('entry_list'))
return flask.redirect(flask.url_for("entry_list"))


@app.get("/auth/mastodon")
@login_required
def mastodon_oauth():
"Displays the form to initiate a mastodon oauth login flow."
return flask.render_template('mastodon.html')
return flask.render_template("mastodon.html")


@app.post("/auth/mastodon")
Expand All @@ -84,15 +84,15 @@ def mastodon_oauth_submit():
Returns a redirect to the mastodon authorization url on that instance, which
will then redirect to the callback route.
"""
base_url = flask.request.form.get('url')
base_url = flask.request.form.get("url")
if not base_url:
return flask.render_template('mastodon.html', error_msg="The instance url is required")
return flask.render_template("mastodon.html", error_msg="The instance url is required")

# normalize base url
url_parts = urllib.parse.urlparse(base_url)
base_url = f'https://{url_parts.netloc}'
base_url = f"https://{url_parts.netloc}"

app.logger.info('Registering mastodon application for %s', base_url)
app.logger.info("Registering mastodon application for %s", base_url)
masto_app = models.MastodonApp.get_or_create(base_url)
return flask.redirect(masto_app.auth_redirect_url())

Expand All @@ -106,8 +106,8 @@ def mastodon_oauth_callback():
and an access token will be stored in the DB for subsequent access to the mastodon api.
Redirects to the feed add form to proceed creating a mastodon feed associated with the new account.
"""
code = flask.request.args.get('code')
base_url = flask.request.args.get('server')
code = flask.request.args.get("code")
base_url = flask.request.args.get("server")
if not code or not base_url:
app.logger.error("Missing required parameter in mastodon oauth callback")
flask.abort(400)
Expand All @@ -122,4 +122,4 @@ def mastodon_oauth_callback():
app.logger.info("Successfully logged in mastodon")

# redirect to feed creation with masto pre-selected
return flask.redirect(flask.url_for('feed_add', masto_acct=account.id))
return flask.redirect(flask.url_for("feed_add", masto_acct=account.id))
8 changes: 4 additions & 4 deletions feedi/config/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

ENTRY_PAGE_SIZE = 10

SYNC_FEEDS_CRON_MINUTES = '*/30'
DELETE_OLD_CRON_HOURS = '*/12'
SYNC_FEEDS_CRON_MINUTES = "*/30"
DELETE_OLD_CRON_HOURS = "*/12"

SKIP_RECENTLY_UPDATED_MINUTES = 10
CONTENT_PREFETCH_MINUTES = '*/15'
CONTENT_PREFETCH_MINUTES = "*/15"
RSS_SKIP_OLDER_THAN_DAYS = 7
DELETE_AFTER_DAYS = 7
RSS_MINIMUM_ENTRY_AMOUNT = 5
Expand All @@ -19,4 +19,4 @@
# username to use internally when authentication is "disabled"
# this user will be inserted automatically when first creating the DB
# and auto-logged-in when a browser first sends a request to the app.
DEFAULT_AUTH_USER = '[email protected]'
DEFAULT_AUTH_USER = "[email protected]"
4 changes: 2 additions & 2 deletions feedi/config/development.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ENV = 'development'
SECRET_KEY = b'\xffN\xcfX\xbc\xa9V\x8b*_zFB\xb9\xfa\x1d'
ENV = "development"
SECRET_KEY = b"\xffN\xcfX\xbc\xa9V\x8b*_zFB\xb9\xfa\x1d"
TEMPLATES_AUTO_RELOAD = True

FEEDI_EMAIL = ""
Expand Down
4 changes: 2 additions & 2 deletions feedi/config/testing.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ENV = 'testing'
SECRET_KEY = b'\xffN\xcfX\xbc\xa9V\x8b*_zFB\xb9\xfa\x1d'
ENV = "testing"
SECRET_KEY = b"\xffN\xcfX\xbc\xa9V\x8b*_zFB\xb9\xfa\x1d"
TESTING = True
SQLALCHEMY_DATABASE_URI = "sqlite:///feedi.test.db"
18 changes: 9 additions & 9 deletions feedi/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@


def send(recipient, attach_data, filename):
server = flask.current_app.config['FEEDI_EMAIL_SERVER']
port = flask.current_app.config['FEEDI_EMAIL_PORT']
sender = flask.current_app.config['FEEDI_EMAIL']
password = flask.current_app.config['FEEDI_EMAIL_PASSWORD']
server = flask.current_app.config["FEEDI_EMAIL_SERVER"]
port = flask.current_app.config["FEEDI_EMAIL_PORT"]
sender = flask.current_app.config["FEEDI_EMAIL"]
password = flask.current_app.config["FEEDI_EMAIL_PASSWORD"]

msg = MIMEMultipart()
msg['From'] = sender
msg['To'] = recipient
msg['Subject'] = f'feedi - {filename}'
msg["From"] = sender
msg["To"] = recipient
msg["Subject"] = f"feedi - {filename}"

part = MIMEBase('application', 'epub')
part = MIMEBase("application", "epub")
part.set_payload(attach_data)
encoders.encode_base64(part)

# https://stackoverflow.com/a/216777/993769
filename = urllib.parse.quote(filename)
part.add_header('Content-Disposition', f"attachment; filename*=UTF-8''{filename}.epub")
part.add_header("Content-Disposition", f"attachment; filename*=UTF-8''{filename}.epub")
msg.attach(part)

smtp = smtplib.SMTP(server, port)
Expand Down
38 changes: 19 additions & 19 deletions feedi/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


# TODO unit test this
@app.template_filter('humanize')
@app.template_filter("humanize")
def humanize_date(dt):
delta = datetime.datetime.utcnow() - dt

Expand All @@ -25,53 +25,53 @@ def humanize_date(dt):
return dt.strftime("%b %d, %Y")


@app.template_filter('url_domain')
@app.template_filter("url_domain")
def feed_domain(url):
parts = urllib.parse.urlparse(url)
return parts.netloc.replace('www.', '')
return parts.netloc.replace("www.", "")


@app.template_filter('should_unfold_folder')
@app.template_filter("should_unfold_folder")
def should_unfold_folder(filters, folder_name, folder_feeds):
if filters.get('folder') == folder_name:
if filters.get("folder") == folder_name:
return True

if filters.get('feed_name'):
if filters['feed_name'] in [f.name for f in folder_feeds]:
if filters.get("feed_name"):
if filters["feed_name"] in [f.name for f in folder_feeds]:
return True

return False


@app.template_filter('contains_feed_name')
@app.template_filter("contains_feed_name")
def contains_feed_name(feed_list, selected_name):
for feed in feed_list:
if feed.name == selected_name:
return True
return False


@app.template_filter('sanitize')
@app.template_filter("sanitize")
def sanitize_content(html, truncate=True):
if not html:
return ''
return ""

# poor man's line truncating: reduce the amount of characters and let bs4 fix the html
soup = BeautifulSoup(html, 'lxml')
soup = BeautifulSoup(html, "lxml")
if len(html) > 500 and truncate:
html = html[:500] + '…'
soup = BeautifulSoup(html, 'lxml')
html = html[:500] + "…"
soup = BeautifulSoup(html, "lxml")

if soup.html:
if soup.html.body:
soup.html.body.unwrap()
soup.html.unwrap()

for a in soup.find_all('a', href=True):
for a in soup.find_all("a", href=True):
# prevent link clicks triggering the container's click event
# add kb modifiers to open in reader
read_url = flask.url_for("entry_add", url=a["href"], redirect=1)
a['_'] = f"""
a["_"] = f"""
on click[shiftKey and not metaKey] go to url {read_url} then halt
then on click[shiftKey and metaKey] go to url {read_url} in new window then halt
then on click halt the event's bubbling
Expand All @@ -82,10 +82,10 @@ def sanitize_content(html, truncate=True):

# FIXME this wouldn't be necessary if I could figure out the proper CSS
# to make the text hide on overflow
@app.template_filter('entry_excerpt')
@app.template_filter("entry_excerpt")
def entry_excerpt(entry):
if not entry.content_short:
return '[click to read]'
return "[click to read]"

if entry.content_url and entry.title:
title = entry.title
Expand All @@ -94,13 +94,13 @@ def entry_excerpt(entry):
else:
title = entry.feed.name

body_text = BeautifulSoup(entry.content_short, 'lxml').text
body_text = BeautifulSoup(entry.content_short, "lxml").text

# truncate according to display title length so all entries
# have aproximately the same length
max_length = 100
max_body_length = max(0, max_length - len(title))
if len(body_text) > max_body_length:
return body_text[:max_body_length] + '…'
return body_text[:max_body_length] + "…"

return body_text
Loading

0 comments on commit 75c1992

Please sign in to comment.