Thoughts on tech

186 posts latest post 2026-10-06
Publishing rhythm
May 2026 | 3 posts

I have list [True, False, False, True] and another list [1, 2, 3, 4] and a use case where I want to filter list 2 based on list 1 to remove values that line up with the element False in list 1…. so the outcome will be [1, 4]. list(compress(list2, list1)) will do it. As long as you can create a mask for the filter than itertool.compress will be your friend!

TL;DR

As the nextcloud docs say… if you want to write to an external volume that location has to be writeable by the user/group www-data on the host system… so if that makes sense to you then this TIL probably isn’t a ton of value.. if not however, read on :)

Case Study

You want to self-host your own cloud and use a smart file system for convenience… Nextcloud and ZFS are pretty common goto answers for each of those problems.

My home NAS is built on ZFS and among other things I have a zpool named tank and nested in there is a tank/nas dataset with several child zfs datasets under that.

I want to use nextcloud mainly for auto-uploading photos from my wife’s and my phones for automatic backups. The issue is that the nextcloud application (I run in Docker) is fixed as the www-data user and so any volume/folder that you want nextcloud to write to needs to be permissioned such that www-data owns it… but I don’t want www-data to own everything in my NAS… so what’s a girl to do?

Solution

Well, one way to go is to just utilize docker volumes, write the data in the container to /var/www/html and let that be the place your data backsup to.

I still wanted nextcloud to automatically write right to my NAS so I created a nextcloud-upload directory inside of tank/nas/media/photos (photos cause that’s all that gets automatically uploaded)

Then I chown -R www-data:www-data /tank/nas/media/photos/nextcloud-upload so that just that sub-folder is owned by www-data. Now everyone’s happy!

As I was cleaning up my NAS recently I noticed that I ran out of storage even though my disk usage looked pretty low… turns out I was keeping a mega-ton of ZFS snapshots and due to my own ignorance at the time didn’t realize the storage cost of this!

zfs list-o space tank/home will show the disk usage of the dataset and snapshots in tank/home

From my daily driver Ubuntu machine I often open nautilus, dolphin, etc. and delete a file here or there on my NAS… turns out Ubuntu sends thse file to .Trash-100 ON THE NAS so I’m effectively just moving that file and not freeing up any space…

:PlugSnapshot, :w ~/dotfiles/nvim/snapshot.vim… keep your config safe with git!

I’ve had Plug 'hrsh7th/cmp-path' in my plugins for ever but didn’t notice until recently that I wasn’t getting any filepath completion in vim!

Fuller setup instructions below the TLDR

TL;DR

Turns out I need to not be a dope and configure nvim-cmp to actually use it…

local cmp = require'cmp'

cmp.setup({
    -- removed rest of setup - see the rest in my dotfiles
  sources = cmp.config.sources({
    { name = 'path' },  -- This needs to be here!
    })
})

My Setup

For the sake of completeness here is how I currently (May 2022) configure completion in Neovim usin nvim-cmp

Plugins #

I keep all my plugins in plugins.vim

call plug#begin(s:plug_dir)
Plug 'neovim/nvim-lspconfig'
Plug 'hrsh7th/cmp-nvim-lsp'
Plug 'hrsh7th/cmp-buffer'
Plug 'hrsh7th/cmp-path'
Plug 'hrsh7th/cmp-cmdline'
Plug 'hrsh7th/nvim-cmp'

" For ultisnips users.
<!-- " Plug 'SirVer/ultisnips' -->
<!-- " Plug 'quangnguyen30192/cmp-nvim-ultisnips' -->

call plug#end()

Vim Settings #

My vim settings are also kept in their own file, settings.vim


set completeopt=menu,menuone,noselect

nvim-cmp configuration #

I have a cmp.lua file that gets sourced in init.lua (file structure explained below) for configuring cmp.


  -- Setup nvim-cmp.
local cmp = require'cmp'

cmp.setup({
  snippet = {
    -- REQUIRED - you must specify a snippet engine
    expand = function(args)
      -- For `ultisnips` user.
      vim.fn["UltiSnips#Anon"](args.body)
    end,
  },
  window = {
      completion = cmp.config.window.bordered(),
  },
  mapping = {
    ['<Down>'] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Select }),
    ['<Up>'] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Select }),
    ['<C-d>'] = cmp.mapping.scroll_docs(-4),
    ['<C-f>'] = cmp.mapping.scroll_docs(4),
    ['<C-Space>'] = cmp.mapping.complete(),
    ['<C-e>'] = cmp.mapping.close(),
    ['<Tab>'] = cmp.mapping(cmp.mapping.select_next_item(), { 'i', 's' }),
    ['<CR>'] = cmp.mapping.confirm({
      behavior = cmp.ConfirmBehavior.Replace,
      select = true,
    })
  },
  sources = cmp.config.sources({
    { name = 'nvim_lsp' },
    { name = 'ultisnips' },
    { name = 'buffer' },
    { name = 'path' },
    { name = 'tmux' },
    })
})

The sources section is what was key for this post…

Piecing it together!

My init.vim sources plugins and then settings and then finally calls init.lua. init.lua sources my cmp.lua file and BANG! auto-completion.

More sources #

hrsh7th’s wiki for nvim-cmp is here and has example configs as well as a list of sources…

Don’t forget to configure and not just install!

my dotfiles

I just started using FastAPI for a home project and needed to pass back a dynamic number of values from a form rendered with jinja…

Dynamic Values

The jinja templating for rendering HTML based on something like a python iterable is nice and easy

data is the result of a database query, and item is each row, so the dot notation is the value of each column basically in that row

<form method="post">
  {% for item in data %}
    <div class="form-check ">
        <input class="form-check-input"  name="item_{{ item.id }}" id="{{ item.name }}" value="{{ item.id }}" type="checkbox">
        <label class="form-check-label" for="{{ item.id }}" > Label for: {{ item.name }} </label>
    </div>
  {% endfor %}

<button type="submit" class="submit btn btn-xl btn-outline-danger" >Remove</button>
</form>

This form generates a row with a checkbox for every item in data (in my case each item is an existing row in my table). it?

The way to pass back all those values is pretty straight forward (after hours of messing around that is!)

# I hate it when tutorials don't show ALL relevant pieces to the blurb
import starlette.status as status
from fastapi import APIRouter, Depends, Form, Request
from fastapi.encoders import jsonable_encoder
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session

from app.session.session import create_get_session

router = APIRouter()
templates = Jinja2Templates(directory="templates/")

@router.post("/my_route/do_something_with_form", response_class=HTMLResponse)
async def delete_rows(
    request: Request,
    db: Session = Depends(create_get_session),
):
    form_data = await request.get_form()
    data = jsonable_encoder(form_data)
    # data = {"item_1": 1, "item_2": 2, ... "item_N": N}
    return RedirectResponse("/", status_code=status.HTTP_302_FOUND)

We await request.get_form() and after encoding the data we get a dictionary with key/value pairs of the name/value from the form!

This took me quite a long time to figure out in part because most of the Google-able resources are still on Flask…

Dynamic-Form-Values-With-Jinja-And-Fastapi

I’m currently working on a self-hostable wish list app using FastAPI so we can finally drop Amazon forever. (The lists funcionality has been super handy for sharing holiday gift ideas with the famj!) FastAPI FastAPI is an amazing framework for quickly building APIs with Python. I will have a slightly longer post about my brief experience with it coming later… Jinja, Forms, and FastAPI One of the last things I needed to figure out in my app was how to generate a form in a Jinja template with a dynamic number of inputs and then pass all the inputs to the backend to perform a database operation (my exact case was removing rows from a table). Explicit Values # The way to pass back explicit variables is really easy… Our form would look like this (I’m using bootstrap CSS) So what is this? This form will have 2 rows with the lables you see in and checkboxes that when checked would have the value in each line. So our backend might looks something like this… I’m keeping all the imports and stuf…

If you use vim-plug for managing your vim plugins, do yourself a favor and snapshot your plugins before upgrading!

:PlugSnapshot creates a vim.snapshot file that you can use to restore your plugin versions with vim -S snapshot.vim

The snapshot file looks like this:

❯ cat snapshot.vim
" Generated by vim-plug
" Tue 17 May 2022 03:44:29 PM CDT
" :source this file in vim to restore the snapshot
" or execute: vim -S snapshot.vim

silent! let g:plugs['Telegraph.nvim'].commit = '92e472f4e83acd60eb3766168e66d02718bfefe0'
silent! let g:plugs['TrueZen.nvim'].commit = '508b977d71650da5c9243698614a9a1416f116d4'
silent! let g:plugs['ariake-vim-colors'].commit = '9fb35f1255e475631c9df24ecc5485a40360cc7b'
silent! let g:plugs['auto-pairs'].commit = '39f06b873a8449af8ff6a3eee716d3da14d63a76'
silent! let g:plugs['bufutils.vim'].commit = '4634feb1312fd73fab66cfaa860e7af3abde935b'
silent! let g:plugs['cmp-buffer'].commit = '12463cfcd9b14052f9effccbf1d84caa7a2d57f0'
silent! let g:plugs['cmp-cmdline'].commit = 'c36ca4bc1dedb12b4ba6546b96c43896fd6e7252'
silent! let g:plugs['cmp-nvim-lsp'].commit = 'e6b5feb2e6560b61f31c756fb9231a0d7b10c73d'
silent! let g:plugs['cmp-nvim-ultisnips'].commit = '21f02b62deb409ce69928a23406076bd0043ddbc'
silent! let g:plugs['cmp-path'].commit = '466b6b8270f7ba89abd59f402c73f63c7331ff6e'
silent! let g:plugs['cmp-spell'].commit = '5602f1a0de7831f8dad5b0c6db45328fbd539971'
silent! let g:plugs['compe-tabnine'].commit = '33e4af509c27da9ef2c9c3002c01e3ec031797d4'
silent! let g:plugs['coverage-highlight.vim'].commit = '864e03679ea4168661501246147893cc82020917'
silent! let g:plugs['diffurcate.vim'].commit = 'b804675072220ff7c7ebcd24a028aa4aa35f09cc'
silent! let g:plugs['fzf'].commit = '6dcf5c3d7d6c321b17e6a5673f1533d6e8350462'
silent! let g:plugs['fzf.vim'].commit = 'd5f1f8641b24c0fd5b10a299824362a2a1b20ae0'
silent! let g:plugs['git-blame.vim'].commit = '9d144b7bed5d8f1c9259551768b7f3b3d1294917'
silent! let g:plugs['harpoon'].commit = 'd3d3d22b6207f46f8ca64946f4d781e975aec0fc'
silent! let g:plugs['instant.nvim'].commit = 'c02d72267b12130609b7ad39b76cf7f4a3bc9554'
silent! let g:plugs['lsp-colors.nvim'].commit = '517fe3ab6b63f9907b093bc9443ef06b56f804f3'
silent! let g:plugs['lsp_extensions.nvim'].commit = '4011f4aec61ba59c734f5dbf52e91f258b99d985'
silent! let g:plugs['lspkind-nvim'].commit = '57e5b5dfbe991151b07d272a06e365a77cc3d0e7'
silent! let g:plugs['lspsaga.nvim'].commit = '8dde091a61ab07f639baaa82b456d3508d0aa7e8'
silent! let g:plugs['neoformat'].commit = '409ebbba9f4b568ea87ab4f2de90a645cf5d000a'
silent! let g:plugs['neovim-fuzzy'].commit = '0bef4e1a81c65fc05d31380dd74454bd67733837'
silent! let g:plugs['nerdtree'].commit = 'eed488b1cd1867bd25f19f90e10440c5cc7d6424'
silent! let g:plugs['nerdtree-visual-selection'].commit = '05427635ff053a2c542fcf8d7c3744c72575e76c'
silent! let g:plugs['nvim-cmp'].commit = 'a226b6a4ff72e5e809ed17734318233fb25c87f3'
silent! let g:plugs['nvim-compe'].commit = 'd186d739c54823e0b010feb205c6f97792322c08'
silent! let g:plugs['nvim-lspconfig'].commit = '9ff2a06cebd4c8c3af5259d713959ab310125bec'
silent! let g:plugs['nvim-lspinstall'].commit = '79ec2425d6b39cdcb69d379f3e56847f49be73eb'
silent! let g:plugs['nvim-treesitter'].commit = '10d57b3ec14cac0b6b759e1eb5594617b8a7e883'
silent! let g:plugs['nvim-treesitter-textobjects'].commit = '094e8ad3cc839e825f8dcc91352837653e365a8f'
silent! let g:plugs['nvim-web-devicons'].commit = 'bdd43421437f2ef037e0dafeaaaa62b31d35ef2f'
silent! let g:plugs['playground'].commit = '71b00a3c665298e5155ad64a9020135808d4e3e8'
silent! let g:plugs['plenary.nvim'].commit = '25b3475b97f241e6f76249747bc209e70c5d2ec8'
silent! let g:plugs['popup.nvim'].commit = 'b7404d35d5d3548a82149238289fa71f7f6de4ac'
silent! let g:plugs['syntastic'].commit = 'b7f4f71539038d33f173bfa72631737da049575a'
silent! let g:plugs['tabular'].commit = '339091ac4dd1f17e225fe7d57b48aff55f99b23a'
silent! let g:plugs['targets.vim'].commit = '8d6ff2984cdfaebe5b7a6eee8f226a6dd1226f2d'
silent! let g:plugs['telescope-fzy-native.nvim'].commit = '7b3d2528102f858036627a68821ccf5fc1d78ce4'
silent! let g:plugs['telescope.nvim'].commit = '39b12d84e86f5054e2ed98829b367598ae53ab41'
silent! let g:plugs['termopen.vim'].commit = '3194a991a18a9be2fd9fcf8c4c55fe990c04b2bd'
silent! let g:plugs['tokyonight.nvim'].commit = '8223c970677e4d88c9b6b6d81bda23daf11062bb'
silent! let g:plugs['trouble.nvim'].commit = 'da61737d860ddc12f78e638152834487eabf0ee5'
silent! let g:plugs['ultisnips'].commit = 'f5ccf0977c611ffd774ca180774959301baaffad'
silent! let g:plugs['undotree'].commit = '08e259be24d4476c1ee745dc735eefd44f90efdc'
silent! let g:plugs['vim-airline'].commit = 'c4655701431a9c79704c827fd88a4783ec946879'
silent! let g:plugs['vim-airline-themes'].commit = '97cf3e6e638f936187d5f6e9b5eb1bdf0a4df256'
silent! let g:plugs['vim-be-good'].commit = 'bc499a06c14c729b22a6cc7e730a9fbc44d4e737'
silent! let g:plugs['vim-commentary'].commit = '3654775824337f466109f00eaf6759760f65be34'
silent! let g:plugs['vim-devicons'].commit = 'a2258658661e42dd4cdba4958805dbad1fe29ef4'
silent! let g:plugs['vim-dispatch'].commit = '00e77d90452e3c710014b26dc61ea919bc895e92'
silent! let g:plugs['vim-flake8'].commit = 'a99054ef98e8fdaefa1315af4649138bcadbfdf7'
silent! let g:plugs['vim-floaterm'].commit = 'ab7876f86c05c1935eb23a193f4f276132902ac1'
silent! let g:plugs['vim-fugitive'].commit = 'f529acef74b4266d94f22414c60b4a8930c1e0f3'
silent! let g:plugs['vim-gitbranch'].commit = '1a8ba866f3eaf0194783b9f8573339d6ede8f1ed'
silent! let g:plugs['vim-indent-object'].commit = '5c5b24c959478929b54a9e831a8e2e651a465965'
silent! let g:plugs['vim-nerdtree-syntax-highlight'].commit = '5178ee4d7f4e7761187df30bb709f703d91df18a'
silent! let g:plugs['vim-polyglot'].commit = '38282d58387cff48ac203f6912c05e4c8686141b'
silent! let g:plugs['vim-pydocstring'].commit = 'f6e3c52bfaf4b4f76dab4d84e75b94199d5f3b9c'
silent! let g:plugs['vim-quicklink'].commit = '021167741588555501594e1fc31f130b16acefa0'
silent! let g:plugs['vim-repeat'].commit = '24afe922e6a05891756ecf331f39a1f6743d3d5a'
silent! let g:plugs['vim-signify'].commit = '69498f6d49f3eeac06870012416dd9bf867b84f3'
silent! let g:plugs['vim-sneak'].commit = '94c2de47ab301d476a2baec9ffda07367046bec9'
silent! let g:plugs['vim-snippets'].commit = '6f270bb2d26c38765ff2243e9337c65f8a96a28b'
silent! let g:plugs['vim-startify'].commit = '81e36c352a8deea54df5ec1e2f4348685569bed2'
silent! let g:plugs['vim-surround'].commit = 'bf3480dc9ae7bea34c78fbba4c65b4548b5b1fea'
silent! let g:plugs['vim-test'].commit = '2240d7a4b868cb594b7d83544e1b6db4df806e5e'
silent! let g:plugs['vim-tmux-runner'].commit = '54767911fd5e6e2d8e493847149e315ac2e6531a'
silent! let g:plugs['vim-ultest'].commit = '6978fd32e3ca2c1c5591884eea0d57a7ee43d212'
silent! let g:plugs['vim-visualstar'].commit = 'a18cd0e7a03311ac709595c1d261ed44b45c9098'
silent! let g:plugs['vimtex'].commit = 'dfaca59bbbf0079ab1b4f159337ae7f17d1b5289'
silent! let g:plugs['which-key.nvim'].commit = 'bd4411a2ed4dd8bb69c125e339d837028a6eea71'

PlugUpdate!