Install pipx
Install fh-fablib
pipx install fh_fablib
if you're happy with the packaged versionpipx install --editable git+ssh://git@github.com/feinheit/fh-fablib.git@main#egg=fh_fablib
otherwise
Add a
fabfile.py
to your project. A minimal example follows:import fh_fablib as fl fl.require("1.0.20210125") fl.config.update(base=fl.Path(__file__).parent, host="www-data@feinheit06.nine.ch") fl.config.update(domain="example.com", branch="main", remote="production") ns = fl.Collection(*fl.GENERAL, *fl.NINE)
Run
fl --list
to get a list of commands.
Loading the fh_fablib
module automatically creates
.git/hooks/pre-commit
which runs fl check
before each commit.
app = "app"
: Name of primary Django app containing settings, assets etc.base
:pathlib.Path
object pointing to the base dir of the project.branch
: Branch containing code to be deployed.domain
: Primary domain of website. The database name and cache key prefix are derived from this value.environments
: A dictionary of environments, see below.host
: SSH connection string (username@server
)remote
: git remote name for the server. Only used for thefetch
task.
For the sake of an example, suppose that additional processes should be
restarted after deployment. A custom deploy
task follows:
# ... continuing the fabfile above
@fl.task
def deploy(ctx):
"""Deploy once 🔥"""
fl.deploy(ctx) # Reuse
with fl.Connection(fl.config.host) as conn:
fl.run(conn, "systemctl --user restart other.service")
ns.add_task(deploy)
Note
Instead of making existing tasks more flexible or configurable it's
preferable to contribute better building blocks resp. to improve
existing buildings blocks to make it easier to build customized tasks
inside projects. E.g. if you want to fmt
additional paths it's
better to build your own fmt
task and not add configuration
variables to the config
dictionary.
If you need multiple environments, add environment tasks as follows:
import fh_fablib as fl
fl.require("1.0.20210125")
fl.config.update(base=fl.Path(__file__).parent, host="www-data@feinheit06.nine.ch")
environments = [
fl.environment(
"production",
{
"domain": "example.com",
"branch": "main",
"remote": "production",
},
aliases=["p"],
),
fl.environment(
"next",
{
"domain": "next.example.com",
"branch": "next",
"remote": "next",
},
aliases=["n"],
),
]
ns = fl.Collection(*fl.GENERAL, *fl.NINE, *environments)
Now, fl production pull-db
, fl next deploy
and friends should
work as expected.
bitbucket
: Create a repository on Bitbucket and push the codecheck
: Check the coding stylecm
: Compile the translation catalogsdeploy
: Deploy once 🔥dev
: Run the development server for the frontend and backendfetch
: Ensure a remote exists for the server and fetchfmt
: Format the codefreeze
: Freeze the virtualenv's stategithub
: Create a repository on GitHub and push the codehook
: Install the pre-commit hooklocal
: Local environment setupmm
: Update the translation catalogspull-db
: Pull a local copy of the remote DB and reset all passwordsreset-pw
: Set all user passwords to"password"
update
: Update virtualenv and node_modules to match the lockfilesupgrade
: Re-create the virtualenv with newest versions of all libraries
nine
: Run all nine🌟 setup tasks in ordernine-alias-add
: Add aliasses to a nine-manage-vhost virtual hostnine-alias-remove
: Remove aliasses from a nine-manage-vhost virtual hostnine-checkout
: Checkout the repository on the servernine-db-dotenv
: Create a database and initialize the .env. Currently assumes that the shell user has superuser rights (either throughPGUSER
andPGPASSWORD
environment variables or through peer authentication)nine-disable
: Disable a virtual host, dump and remove the DB and stop the gunicorn@ unitnine-reinit-from
: Reinitialize an environment from a different environmentnine-restart
: Restart the application servernine-ssl
: Activate SSLnine-unit
: Start and enable a gunicorn@ unitnine-venv
: Create a venv and install packages from requirements.txtnine-vhost
: Create a virtual host using nine-manage-vhosts
The following functions may be used to build your own tasks. They cannot be executed directly from the command line.
run(c, ...)
: Wrapper aroundContext.run
orConnection.run
which always sets a few useful arguments (echo=True
,pty= True
andreplace_env=False
at the time of writing)
_check_flake8(ctx)
: Runvenv/bin/flake8
_check_django(ctx)
: Run Django's checks_check_prettier(ctx)
: Check whether the frontend code conforms to prettier's formatting_check_eslint(ctx)
: Run ESLint_check_large_files(ctx)
: Check whether the commit would add large files._check_branch(ctx)
: Terminates if checked out branch does not match configuration._check_no_uncommitted_changes(ctx)
: Terminates if there are uncommitted changes on the server.
_fmt_black(ctx)
: Runblack
_fmt_isort(ctx)
: Runisort
_fmt_prettier(ctx)
: Runprettier
_fmt_tox_style(ctx)
: Runtox -e style
_local_env(path=".env")
:speckenv.env
for a local env file_srv_env(conn, path)
:speckenv.env
for a remote env file_python3()
: Return the path of a Python 3 executable. Prefers newer Python versions._local_dotenv_if_not_exists()
: Ensure a local.env
with a few default values exists. Does nothing if.env
exists already._local_dbname()
: Ensure a local.env
exists and return the database name._dbname_from_dsn(dsn)
: Extract the database name from a DSN._dbname_from_domain(domain)
: Mangle the domain to produce a string suitable as a database name, database user and cache key prefix._concurrently(ctx, jobs)
: Run a list of shell commands concurrently and wait for all of them to terminate (or Ctrl-C)._random_string(length, chars=None)
: Return a random string of length, suitable for generating secret keys etc.require(version)
: Terminate if fh_fablib is older.terminate(msg)
: Terminate processing with an error message.
# top-most EditorConfig file root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 trim_trailing_whitespace = true indent_style = space indent_size = 4 [*.{html,js,scss}] indent_size = 2
module.exports = { env: { browser: true, es2020: true, node: true, }, extends: [ "eslint:recommended", "prettier", "preact", // "prettier/react", // "plugin:react/recommended", ], parser: "babel-eslint", parserOptions: { ecmaFeatures: { experimentalObjectRestSpread: true, jsx: true, }, sourceType: "module", }, plugins: [ // "react", // "react-hooks", ], rules: { "no-unused-vars": [ "error", { argsIgnorePattern: "^_", varsIgnorePattern: "React|Fragment|h|^_", }, ], // "react/prop-types": "off", // "react/display-name": "off", // "react-hooks/rules-of-hooks": "warn", // Checks rules of Hooks // "react-hooks/exhaustive-deps": "warn", // Checks effect dependencies }, settings: { react: { version: "detect", }, }, }
[flake8] exclude=venv,build,docs,.tox,migrate,migrations,node_modules ignore=E203,W503 max-line-length=88 max-complexity=10
{ "name": "feinheit.ch", "description": "feinheit", "version": "0.0.1", "private": true, "dependencies": { "babel-eslint": "^10.0.3", "eslint": "^7.7.0", "eslint-config-prettier": "^6.11.0", "fh-webpack-config": "^1.0.7", "prettier": "^2.1.1" }, "eslintIgnore": [ "app/static/app/lib/", "app/static/app/plugin_buttons.js" ] }
const merge = require("webpack-merge") const config = require("fh-webpack-config") module.exports = merge.smart( config.commonConfig, // config.preactConfig, // config.reactConfig, config.chunkSplittingConfig )