initial commit

This commit is contained in:
Ayo Ayco 2025-03-09 11:41:17 +01:00
commit f50ba452df
13 changed files with 757 additions and 0 deletions

169
.gitignore vendored Executable file
View file

@ -0,0 +1,169 @@
config.json
# Deployment Unix socket
*.sock
# Temp files
*~
*swp
*swo
# web
node_modules/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# Mastodon secrets
**/*.secret

61
README.md Normal file
View file

@ -0,0 +1,61 @@
# Python Template
An opinionated starter project for flask python projects. It contains code for a new [Flask Blueprint]https://flask.palletsprojects.com/en/stable/blueprints/).
**DO NOT PUSH PROJECT-SPECIFIC CHANGES**
## Project setup
1. Set up your **Debian** (for other environments, search for counterpart instructions)
```bash
# update repositories
$ sudo apt update
# install python stuff
$ sudo apt install python3-pip python3-dev build-essential libssl-dev libffi-dev python3-setuptools python3-venv
```
> For MacOS: https://docs.python.org/3/using/mac.html
2. Install dependencies and set up the project
```bash
# clone the project
$ git clone git@git.sr.ht:~ayoayco/python-template [project-name]
# go into the directory
$ cd [project-name]
# remove template .git stuff
$ rm -rf .git
# initialize git
$ git init .
# create python environment:
$ python3 -m venv .venv
# activate python env:
$ . .venv/bin/activate
# install dependencies
(.venv)$ python -m pip install -r requirements.txt
# create configuration from example config file
(.venv)$ cp ./example_config.json ./config.json
# rejoice!
```
3. To start development, run the following:
```bash
(.venv)$ flask --debug run
```
> Note: On a Mac, the default port 5000 is used by AirDrop & Handoff; you may have to turn those off
4. After development session, deactivate the python env
``bash
` (.venv)$ deactivate
```

0
__init__.py Normal file
View file

14
app.py Executable file
View file

@ -0,0 +1,14 @@
from flask import Flask
import json
from .blueprintname import blueprintname
from .cache import cache
# TODO: Update blueprintname
app = Flask(__name__)
cache.init_app(app, config={'CACHE_TYPE': 'SimpleCache'})
app.register_blueprint(blueprintname, url_prefix='/')
app.config.from_file("config.json", load=json.load)
if __name__ == '__main__':
app.run(host='0.0.0.0')

20
blueprintname.py Executable file
View file

@ -0,0 +1,20 @@
from flask import Blueprint, render_template, current_app
from .cache import cache
blueprintname = Blueprint("blueprintname", __name__, template_folder="templates", static_folder="static")
def get_attribution():
return current_app.config['ATTRIBUTION']
def get_app_config():
return current_app.config['APPS']['blueprintname']
@blueprintname.route("/")
@cache.cached(timeout=300)
async def home():
# DO STUFF...
return render_template("_home.html", app=get_app_config(), attribution=get_attribution())

2
cache.py Normal file
View file

@ -0,0 +1,2 @@
from flask_caching import Cache
cache = Cache()

2
requirements.txt Normal file
View file

@ -0,0 +1,2 @@
flask[async]
Flask-Caching

29
static/button.css Normal file
View file

@ -0,0 +1,29 @@
.btn {
text-decoration: none;
border-radius: 5px;
border: 1px solid;
color: var(--text-color-dark);
border-color: var(--border-color-light);
background-color: var(--bg-light);
padding: 0.25em 0.5em;
font-weight: bold;
font-size: var(--font-size-base);
&:hover {
color: var(--color-brand-blue-3);
border-color: var(--color-brand-blue-3);
}
}
@media (prefers-color-scheme: dark) {
.btn {
color: var(--text-color-light);
border-color: var(--border-color-dark);
background-color: var(--bg-dark);
&:hover {
color: var(--color-brand-complement);
border-color: var(--color-brand-complement);
}
}
}

175
static/card.css Normal file
View file

@ -0,0 +1,175 @@
.card_content {
& .invisible {
display: none;
}
& .emoji {
display: inline;
height: calc(1rem + 6px);
margin-bottom: -4px;
}
& .ellipsis::after {
content: "...";
}
& .body {
.hashtag:not(.pill),
.mention:not(.pill) {
color: var(--text-color-dark-faded);
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
& code {
font-size: var(--font-size-sm);
background: rgb(245, 242, 240);
padding: 0.25em 0.3em;
border-radius: 5px;
display: inline-block;
vertical-align: text-bottom;
overflow-x: auto;
overflow-wrap: initial;
max-width: 100%;
}
& a:has(.link-card) {
text-decoration: none;
}
& .media,
& .link-card {
border: 1px solid;
border-color: var(--border-color-light);
color: var(--text-color-dark-faded);
border-radius: 5px;
box-shadow: 5px 25px 10px -25px rgba(34, 34, 34, 0.15);
max-width: 100%;
margin: 15px 0 1em;
object-fit: contain;
height: auto;
text-decoration: none;
text-wrap: balance;
}
& .media:hover,
& .link-card:hover {
color: var(--color-brand-blue-3);
border: 1px solid var(--color-brand-blue-3);
text-decoration-color: var(--color-link);
}
& p {
margin-bottom: 1em;
}
}
& .heading {
display: grid;
grid-template-columns: auto auto;
gap: 5px;
height: 20px;
& .author {
font-size: var(--font-size-lg);
}
& .right_menu {
font-size: var(--font-size-sm);
text-align: right;
& a,
& span {
line-height: 36px;
}
& a {
color: var(--text-color-dark-faded);
text-decoration: none;
&:hover {
color: var(--color-link);
text-decoration: underline;
}
}
}
}
& .link-card {
color: var(--text-color-dark-faded);
text-decoration: underline;
text-decoration-color: var(--text-color-light-faded);
& strong,
& small {
text-decoration-thickness: 1px;
display: block;
}
padding: 1rem;
}
}
article.card {
/* border-bottom: 1px solid;
border-color: var(--border-color-light);
*/
& .bottom-menu {
padding: 0.25em 0 1em;
}
}
@media (prefers-color-scheme: dark) {
.card_content {
& .action {
color: var(--color-brand-complement);
}
& .heading .right_menu a {
color: var(--text-color-light-faded);
&:hover {
color: var(--color-brand-complement);
}
}
& .body {
.hashtag:not(.pill),
.mention:not(.pill) {
color: var(--text-color-light-faded);
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
code {
background: rgb(45, 51, 59);
color: rgb(197, 209, 222);
}
& .media,
& .link-card {
border: 1px solid;
border-color: var(--border-color-dark);
color: var(--text-color-light-faded);
background-color: var(--bg-dark);
}
& .media:hover,
& .link-card:hover {
color: var(--color-brand-complement);
border: 1px solid var(--color-brand-complement);
}
}
}
/* article.card {
border-color: var(--border-color-dark);
} */
}

78
static/reset.css Normal file
View file

@ -0,0 +1,78 @@
/**
THANKS TO JOSH COMEAU FOR HIS CUSTOM CSS RESET
👉 https://www.joshwcomeau.com/css/custom-css-reset/
**/
/*
1. Use a more-intuitive box-sizing model.
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
/*
2. Remove default margin
*/
* {
margin: 0;
}
/*
3. Allow percentage-based heights in the application
*/
html,
body {
height: 100%;
}
/*
Typographic tweaks!
4. Add accessible line-height
5. Improve text rendering
*/
body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
/*
6. Improve media defaults
*/
img,
picture,
video,
canvas,
svg,
iframe {
display: block;
max-width: 100%;
margin: 0 auto;
}
/*
7. Remove built-in form typography styles
*/
input,
button,
textarea,
select {
font: inherit;
}
/*
8. Avoid text overflows
*/
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}
/*
9. Create a root stacking context
*/
#root,
#__next {
isolation: isolate;
}

31
static/variables.css Normal file
View file

@ -0,0 +1,31 @@
:root {
--content-width: 700px;
--font-size-sm: clamp(0.75rem, 0.2vw + 0.66rem, 0.8rem);
--font-size-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
--font-size-lg: clamp(1.2rem, 0.7vw + 1.2rem, 1.5rem);
--font-size-xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
--color-border: hsl(17, 24%, 90%);
--color-link: var(--color-brand-blue-5);
--color-brand-blue-1: #3054bf;
--color-brand-blue-2: #203880;
--color-brand-blue-3: #416fff;
--color-brand-blue-4: #101c40;
--color-brand-blue-5: #3964e6;
--color-brand-complement: orange;
--ayo-gradient: linear-gradient(45deg, #3054bf, #416fff);
--text-color-dark: #232323;
--text-color-dark-faded: #555;
--text-color-light: #f8f9fa;
--text-color-light-faded: #999;
--border-color-light: rgba(197, 209, 222, 0.7);
--bg-light: rgba(197, 209, 222, 0.15);
--border-color-dark: rgba(0, 0, 0, 0.25);
--bg-dark: #343a40;
--bg-darker: #212529;
--bg-darkest: #000;
}

175
templates/_home.html Normal file
View file

@ -0,0 +1,175 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ app.title }}</title>
<meta name="theme-color" content="#3054bf">
<meta name="description" content="{{ app.description }}" />
<meta property="og:description" content="{{ app.description }}" />
<meta name="author" content="{{ attribution.owner }}" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="{{ app.site_name }}" />
<meta property="og:title" content="{{ app.title }}" />
<style>
html {
scroll-behavior: smooth;
}
body {
font-family: system-ui, sans-serif;
max-width: 600px;
margin: 0 auto;
color: var(--text-color-dark);
font-size: var(--font-size-base);
display: grid;
gap: 1em;
padding: 0 1em;
a {
color: var(--color-link);
}
small {
font-size: var(--font-size-sm);
}
}
header {
& a.app-title {
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
header,
footer {
background: var(--ayo-gradient);
color: var(--text-color-light);
border-radius: 5px;
padding: 1em;
text-wrap: balance;
& a {
color: var(--text-color-light);
}
}
footer {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
main {
& ul.tags {
list-style: none;
padding-left: 0;
& li {
display: inline
}
}
}
main {
display: grid;
gap: 1em;
}
main.home {
& .back {
display: none
}
}
main.thread {
& .card:not(:last-of-type) .card_avatar::after {
content: " ";
display: block;
height: 100%;
border-right: 2px solid rgba(34, 34, 34, 0.15);
width: 26px;
margin-top: -8px;
}
}
.card_avatar img {
border: 2px solid rgba(197, 209, 222, 0.15);
border-radius: 50%;
display: inline;
width: 50px;
}
.card {
grid-template-columns: 55px auto;
display: grid;
gap: 5px;
}
@media (prefers-color-scheme: dark) {
html,
body {
background: var(--bg-darker);
color: var(--text-color-light);
}
main a {
color: var(--color-brand-complement);
}
main.thread {
& .card:not(:last-of-type) .card_avatar::after {
border-right: 2px solid rgba(197, 209, 222, 0.15);
}
}
}
</style>
<!-- Use parent app's variables & reset stylesheets -->
<link rel="stylesheet" href="{{ url_for('static', filename='variables.css') }}" />
<link rel="stylesheet" href="{{ url_for('static', filename='reset.css') }}" />
</head>
<body>
<a id="top"></a>
<header>
{% include "nav.html" %}
<a class="app-title" href="{{url_for('blueprintname.home')}}">
<h1>{{ app.title }}</h1>
</a>
<p>{{ app.description }}</p>
</header>
<main>
<p>CONTENT GOES HERE</p>
</main>
<footer>
<p>
Copyright &copy;
{% if attribution.current_year %}
{{ attribution.year}}-{{ attribution.current_year }}
{% else %}
{{ attribution.year}}
{% endif %}
{{ attribution.owner }}
</p>
<p>Rendered on {{ render_date }} in Europe/Amsterdam</p>
</footer>
<script type="module">
import TimeAgo from "https://esm.sh/v135/@github/relative-time-element@4.4.0/es2022/relative-time.js"
customElements.define('relative-time', TimeAgo)
</script>
<script type="module" src="{{ url_for('blueprintname.static', filename='enhance-content.js') }}">
</script>
</body>
</html>

1
templates/nav.html Normal file
View file

@ -0,0 +1 @@
<nav><a href="/">Go home</a></nav>