Juan Olvera

  • Quick guide to convert a Python script into a CLI tool

    Reading the Click’s documentation will bring you up to speed on what it can do, but I’m writing a quick list of steps I follow every time I need to make a quick CLI out of a Python script.

    So, let’s start by creating a Python project and installing Click as a dependency.

    $ poetry new cool-tool
    $ cd cool-tool
    $ poetry add click

    Poetry will create a scaffold directory that looks like this:

    cool-tool/
      cool_tool/
        __init__.py
      poetry.lock
      pyproject.toml

    For this example, we will create a main.py file with a very cool function.

    $ cd cool-tool 
    $ touch cool_tool/main.py

    In our file we create a function and some logic.

    # main.py
    def cool():
      print("hello cool world")

    OK, now to the part we really care. Let’s convert this script into a CLI

    • create a directory named “console” inside our main module.
    • create a __init__.py file to let Python know this is a module.
    • create a run.py file where we will add our Click logic.
    $ mkdir console
    $ touch console/__init__.py
    $ touch console/run.py

    Inside our run.py we add:

    import os
    import click
    from cool_tool.main import cool
    
    @click.group
    def cli():
       pass
    
    
    @click.command
    @click.argument("cool")
    def show_cool():
       click.echo(cool())

    Then we add our CLI script to our pyproject.toml so we can use it directly inside our virtual environment, e.g., cool --help. Add this section and we should be done:

    # pyproject.toml
    [tool.poetry.scripts]
    cool = 'cool_tool.console.run:cli'

    Run poetry install, make sure you have the virtual environment activated, and verify everything works by running the help command.

    $ poetry install
    $ poetry shell
    $ cool --help
    
    Usage: cool [OPTIONS] COMMAND [ARGS]...
    
    Options:
      --help Show this message and exit.
    
    Commands:
      show_cool

    Resources

  • Python function to send a single Telegram bot message

    This is my console.log equivalent when I need a quick observability implementation.

    Usually, when you build a Telegram bot, you have to have a small server listening for updates.

    The server logic is abstracted away as a simple API using the python-telegram-bot package, but we can write a simpler implementation for a single message:

    import os
    import telegram
    
    TELEGRAM_BOT_ID = os.getenv("TELEGRAM_BOT_ID")
    TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
    
    async def send_telegra_notification(msg: str, origin: str = "AppName:main"):
        notification_msg = f"*{origin}*: {msg}"
        bot = telegram.Bot(token=TELEGRAM_BOT_TOKEN)
        try:
            await bot.initialize()
            await bot.send_message(
                chat_id=TELEGRAM_BOT_ID,
                text=notification_msg,
                parse_mode=telegram.constants.ParseMode.MARKDOWN,
            )
        except Exception as e:
            # handle exception, e.g., logger.error(e)
            print(e)
        finally:
            await bot.shutdown()
    
  • Deploy PostgREST to Dokku

    Here is a list of steps to deploy a PostgREST instance to Dokku.

    Create a new app.

    dokku apps:create pgrest-myproject

    Add a port mapping from 3000 (PostgREST container) to 80 (host).

    dokku ports:add pgrest-myproject http:80:3000

    Create a new PostgreSQL database using the dokku-postgres plugin and link it to our project.

    dokku postgres:create pgrest-myproject
    dokku posgres:link pgrest-myproject pgrest-myproject

    A new environment variable will be created:

    =====> pgrest-myproject
    DATABASE_URL: postgres://postgres:alongpass@dokku-postgres-pgrest-myproject:5432/pgrest_myproject

    But we need to define the same value in a variable named PGRST_DB_URI so PostgREST can access the database.

    dokku config:set pgrest-myproject PGRST_DB_URI="postgres://postgres:alongpass@dokku-postgres-pgrest-myproject:5432/pgrest_myproject"

    Pull and deploy the Docker image from dockerhub.

    docker pull postgrest/postgrest:latest
    docker tag postgrest/postgrest:latest dokku/postgrest:latest
    dokku git:from-image pgrest-myproject dokku/postgrest:latest

    Add a Let’s Encrypt TLS certificate with the Dokku letsencrypt plugin.

    dokku letsencrypt:set pgrest-myproject email <your-email>
    dokku letsencrypt:enable pgrest-myproject
  • Launch a simple HTTP Server in One Line of Python

    I always google this snippet when I need it, so I will put it here for easier access.

    python3 -m http.server 8080 -d ./public
  • Types of UUID

    • v1: mac address + time + random
    • v4: completely random
    • v5: input + seed (consistent, derived from input)
    • v7: time + random (distributed sortable ids)

    Source

  • Ubuntu Desktop personal setup

    This is how I customize a clean Ubuntu install.

    Essentials

    First we install the essentials.

    sudo apt install curl vim fish tmux

    SSH keys

    ssh-keygen -t ed25519 -C "user@domain.com"

    Copy public key to paste as needed:

    xclip -sel clip < ~/.ssh/id_ed25519.pub

    Folder structure

    Update the folder structure. Basically changing the defaults locations of folders like ~/Downloads and ~/Desktop.

    Create new folders.

    cd ~
    mkdir work dl docs media tpl pub \
      media/{music,books,pics,vids,screens,torrents}

    Make Ubuntu use our new folders. Edit ~/.config/user-dirs.dir and update the file so it looks like this:

    XDG_DESKTOP_DIR="$HOME/desk"
    XDG_DOWNLOADS_DIR="$HOME/dl"
    XDG_TEMPLATES_DIR="$HOME/tpl"
    XDG_PUBLICSHARE_DIR="$HOME/pub"
    XDG_DOCUMENTS_DIR="$HOME/docs"
    XDG_MUSIC_DIR="$HOME/media/music"
    XDG_PICTURES_DIR="$HOME/media/pics"
    XDG_VIDEOS_DIR="$HOME/media/vids"

    Fish configuration

    Make fish the default shell

    Install Fisher.

    Install z:

    fisher install jethrokuan/z

    Ubuntu configuration

    Add Spanish:

    • Settings
      • Region & Language
      • Input Sources
      • Add Spanish
    • Appearance
      • Dark
      • Dock
        • Auto-hide the Dock: On
        • Position on screen: Bottom

    Install GNOME tweaks

    sudo apt install gnome-tweaks

    With GNOME Tweaks activate:

    • Top Bar
      • Activities Overview Hot Corner
      • Battery Percentage
      • Weekday
    • Workspaces
      • Static Workspaces
        • Number of Workspaces: 5
        • Workspaces span displays

    GNOME Terminal configuration

    Download and install the Dracula theme for GNOME Terminal.

    Restart your terminal to reflect the changes.

    Tmux configuration

    Update ./config/tmux/tmux.conf:

    set -g @plugin 'tmux-plugins/tpm'
    set -g @plugin 'dracula/tmux'
    set -g @dracula-plugins 'git, network-ping, attached-clients, network-vpn, weather, time'
    set -g @dracula-show-left-icon 'uwu'
    
    run -b '~/.tmux/plugins/tpm/tpm'

    Install Node

    Install n:

    curl -L https://git.io/n-install | bash

    Install Python

    Install pyenv using pyenv-installer.

    Be sure to install the build environment dependencies:

    • https://github.com/pyenv/pyenv/wiki#suggested-build-environment

    Install poetry.

  • Git commit messages

    Each commit message consists of a header, a body, and a footer.

    <type>(<scope>): <subject>
    <BLANK LINE>
    <body>
    <BLANK LINE>
    <footer>

    Header

    The header is mandatory and the scope is optional.

    Type

    Must be one of the following:

    • feat: introduces a new feature to the codebase
    • fix: patches a bug in your codebase
    • refactor: improves the code without changing functionality
    • BREAKING CHANGE: major version

    Additional types:

    • build: a change that affects the build system or external dependencies
    • chore: same as build but chore is preferred, also any change that doesn’t fit in any other type
    • ci: changes to the CI configuration
    • docs: changes to the documentation
    • style: white-space, formatting, semi-colons, etc.
    • perf: performance improvements
    • test: adding or fixing

    Scope

    The scope can be empty. Scope can be any of these:

    • category, part or section of the codebase we are affecting, e.g.: api, client, tooling
    • a specific package, library or dependency,e.g.: moment.js, requests
    • a ticket number in case the current commit doesn’t close it, e.g.: chore(123): update dependencies

    Subject

    Short (50 chars or less) summary of the change:

    • use the imperative, present tense: “change” not “changed”
    • don’t capitalize the first letter
    • no dot (.) at the end

    Footer

    The footer should contain a closing reference to an issue if any and breaking changes.

    Closes #123, #345
    
    BREAKING CHANGE:
    
    IE6 not supported anymore.

    Semantic versioning

    Conventional commits are designed to work with Semantic versioning.

    • feat: minor version
    • fix: patch version
    • BREAKING CHANGE: major version

    Examples

    Add a new feature without breaking existing functionality. This bumps a minor version, e.g.: 1.2.0 to 1.3.0.

    feat: add search for transactions
    
    Add new React component for searching transactions with autocompletion.
    
    Include tests.
    
    Closes #123, #234

    Fix a bug. This bumps a patch version, e.g.: 1.3.0 to 1.3.1.

    bug(api): add correct status code for existing resources
    
    We return `409` when trying to create a user with an existing email.
    
    Closes #453

    Add new feature that breaks existing functionality. This bumps a major version, e.g.: 1.3.1 to 2.0.0.

    feat!: add bank sync 
    
    This replace CSV import and use Plaid to connect to people's banks.
    
    Closes #423, #424, #425
    
    BREAKING CHANGE:
    
    People won't be able to import transactions using CSV. Only Bank 
    Sync is available.

    Resources

  • How to resize a disk on Ubuntu server 20.04

    I had an issue with my Thinkpad x220; when I installed Ubuntu, I had only 100GB available out of 1000 GB. So it ran out of available space quickly.

    To resize to all the space available, I ran these commands:

    lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv
    
    resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv

    Sources:

  • Deploy Gitea to Dokku

    Requirements

    Steps

    Create an application.

    dokku apps:create gitea

    Expose the ports for http and ssh. By default, gitea exposes 3000 for http and 22 for ssh. We want our application to listen externally on port 80, but we need to map our ssh to a different port because 22 is used by the host.

    We will rely on the docker-options plugin to expose ssh properly.

    # expose container `http` port 3000 on host `http` port 80
    dokku proxy:ports-add gitea http:80:3000
    
    # expose the container port 22 on host port 2222
    dokku docker-options:add git deploy -p 2221:22

    Add a rule in your firewall to allow connections on 2221.

    sudo ufw allow 2221/tcp

    Create a storage directory for the Gitea files.

    sudo mkdir /var/lib/dokku/data/storage/gitea_data
    sudo chown user:user /var/lib/dokku/data/storage/gitea_data
    dokku storage:mount gitea /var/lib/dokku/data/storage/gitea_data:/data

    Pull and deploy the Docker image from dockerhub.

    docker pull gitea/gitea:latest
    docker tag gitea/gitea:latest dokku/gitea:latest
    dokku git:from-image gitea dokku/gitea:latest

    Add a Let’s encrypt TLS certificate with the Dokku letsencrypt plugin.

    dokku letsencrypt:set git email <your-email>
    dokku letsencrypt:enable git

    Now, go visit gitea.<yourdomain>, e.g., gitea.yourdomain.com.

    Configuration

    Most of the options are autopopulated, use the following settings as needed.

    • Get the database information from dokku postgres:info gitea.
    • Set the SSH port as 2221. Gitea will use this to format your projects’ SSH connection info in the UI.

    Resources

    No artificial intelligence was used in the making of this post.

  • Build your own ngrok

    This simple setup can help us expose our local development environment to the public internet. I usually paid for ngrok, but the last time I tried them, they were expensive, and their customer support was terrible.

    For a small-size project, we can build our own small ngrok with Linux, Caddy, and SSH port forwarding.

    Requirements:

    • A VPS with Linux. In this example, we will use Ubuntu 20.01 LTS
    • SSH access to your server
    • Caddy
    • A domain name. In this example, we will use mydomain.io

    Add DNS record

    We need to add two A records to our domain DNS zone:

    • An A record pointing to our server’s IP address
    • A wildcard A record pointing to our server’s IP address

    For example, if our server IP address is 5.164.71.442, our records will look like this:

    HostnameTypeAddress
    @A5.164.71.442
    *A5.164.71.442
    DNS records example

    Configure Caddy

    First, Install Caddy.

    Then, create a Caddyfile to route traffic from a specific domain name to a local port; in this case, we will do app.mydomain.io and port 8881.

    touch ~/Caddyfile
    app.mydomain.io {
      reverse_proxy 127.0.0.1:8881
      log {
        output file /var/log/access.log
      }
    }

    Reload caddy.

    caddy reload
    
    > 2023/05/30 17:55:25.272 INFO    using adjacent Caddyfile

    And our server is ready.

    Create an SSH tunnel

    With SSH, you can create a secure connection between your computer and the server, with the services relayed, i.e., every request to the server will be forwarded to your local development environment.

    For example, if we are building our application in localhost:8000, we can do the following:

    # ssh -R <server port>:<server domain>:<local port> <server ssh user>@<server domain>
    ssh -R 8881:app.mydomain.io:8000 user@mydomain.io

    Now, every request you send to app.mydomain.io will be forwarded to localhost:8000 on your computer.

    Resources

    No artificial intelligence was used in the making of this post.