In the middle of the desert you can say anything you want
Get screenshotting working through a hotkey. I need to screenshot an area of the screen, put the screenshot in a folder, and immediately open it.
In i3 had
bindsym Mod3+s --release exec scrot -s -e 'mv $f ~/s/screenshots && eog ~/s/screenshots/$f'
Nothing I tried worked (didn’t do anything weird):
Key([mod], "s", lazy.spawn(CONFIG_LOCATION + "screenshot.sh"))
Tracked it down to two main issues:
scrot
works, scrot -s
doesn’t. (Running the shell script directly from shell was fine!)# this works
scrot -u -e 'thunar $f' "/tmp/shot.png"
# this doesn't
scrot -u -e 'thunar $f' "$SCREENSHOT_PATH/shot.png"
Decided to leave the first one alone, scrot -u
gets the currently selected window, which generally is good enough for me.
The second one - first rewrote the script to get passed the target path as positional variable (surprisingly it worked!), then decided to do it python-only. As a bonus, copies the screenshot url to the clipboard.
# definition
copy_command = 'bash -c "echo {0} | xclip -selection c"'
# ...
def take_screenshot():
SCREENSHOT_FILENAME = datetime.now().strftime("qtile_%y%m%d-%H%M%S%z")+"-$w$h.png"
screenshot_path = D.SCREENSHOT_DIR +"/"+ SCREENSHOT_FILENAME
command = f"scrot -u -e 'thunar $f && {Commands.copy_command.format('$f')}' {screenshot_path}"
return command
#usage
Key([mod], "s", lazy.spawn(Commands.take_screenshot()))
(qtile-dotfiles/config.py at master · justinesmithies/qtile-dotfiles has escrotum
as python module, errored out during install in the qtile venv and segfaulted on first run when installed outside of it.)
To add an item for the WM to the options shown on gdm startup:
.desktop
file to /usr/share/xsessions
:[Desktop Entry]
Name=qtile
Comment=Qtile
Exec=/home/me/.dotfiles/qtile/.config/qtile/startup.sh
Type=Application
X-LightDM-DesktopName=qtile
DesktopNames=qtile
Keywords=tiling;wm;windowmanager;window;manager;
sudo systemctl restart gdm.service
1Before that I tried killing gdm3
and X but it didn’t work. ↩︎
(From a python-worshop I attended)
asserts
(vs. unittests’ self.assertEqual()...
)
def test_whatever():
# ....
assert foo == bar
Fixtures are useful bits you don’t want to repeat every time, like connecting to a database etc.
It’s a function, that may or may not take arguments, that might or might not return something.
Tests can request a fixture, and it’s basically done like this:
@pytest.fixture
def my_fixture():
return "fix"
def test_with_fixture(my_fixture):
assert my_fixture == "fix"
# fixtures inside other fixtures
@pytest.fixture
def next_fixture(my_fixture):
return my_fixture + "fix"
They are run independently for each test, to ensure that tests are as separated as possible. There are ways to define their scope, but it’s rarely used.
You can also use them to change settings like logging, by adding a fixture that changes etc.
“By using the pytest.mark helper you can easily set metadata on your test functions” 1
#@pytest.mark.skip(reason="there's a good reason")
@pytest.mark.skipif(pytorch.cuda.is_available(), reason="there's a good reason")
def test_always_ski():
assert False
That way you don’t have to do anything inside the test and based on python environment.
# simple marks
@pytest.mark.whatever
def test_whatever():
pass
# complex marks (and defined beforehand)
cuda = pytest.mark.skipif(True, reason="...")
@cuda
def test_require_cuda():
assert False
@pytest.mark.one
@cuda
def test_whatever():
Assuming @pytest.mark.gpu
:
python3 -m "not gpu"
python3 -m "gpu"
Recommended, to keep track of them and get stuff like pytest --markers
etc.
In pyproject.toml
:
[tool.pytest.ini_options]
markers = [
"gpu: marks test which require a gpu"
]
Replace some functions, including ones deep inside code. Lives inside the pypy package pytest-mock · PyPI.
You can patch calls, objects, etc.
from pytest_mock import MockerFixture
def test_mock(mocker: MockerFixture) -> None:
env_mock = mocker.patch("os.environ.get")
os.environ.get("something")
assert env_mock.call_count == 1
# Do stuff to dictionaries:
mocker.patch.dict("os.environ", {"sth": "test"})
assert os.environ.get("sth") == "test"
assert os.environ.get("not_there") == None
# classes, function calls, etc
TODO - does this work for class instances created after the mock?
mocker.spy
Sample from documentation:
def test_spy_method(mocker):
class Foo(object):
def bar(self, v):
return v * 2
foo = Foo()
spy = mocker.spy(foo, 'bar')
assert foo.bar(21) == 42
spy.assert_called_once_with(21)
assert spy.spy_return == 42
pytest test_mod.py
and pytest testing/
pytest -m mark
, pytest -m "not mark"
pytest -k "MyClass and not method
would run TestMyClass.test_something
but not TestMyClass.test_method_something
pytest test_mod.py::test_func
or pytest test_mod.py::TestClass::test_method
pytest-xdist
package allows to do pytest --loop-on-fail
, which keeps looping tests and you can see the test results in real time
logger.warning("test")
inside tests doesn’t get shown by default, but you can enable this in pytest results:
[tool.pytest.ini_options]
log_cli = true
log_cli_level = "DEBUG"
You can change it in single tests: caplog.set_level(logging.DEBUG)
This is useful if you’re fixing a specific bug and want more logging on a specific test.
Python tiling window manager, playing with it for a couple of days now.
It’s everything I expected from a tiling WM, except it’s completely configurable with Python, so basically unlimited options to do anything. Compared to my usual i3: speed is the same, documentation is a bit worse, but configuration is much more intuitive.
And it has a lot of stuff, I never heard of it but was surprised to learn it has a lot of widgets / layouts / etc., and it has even a CLI-like shell qtile shell where you can use the standard bash commands to do stuff to anything (cd/ls/etc to layouts/groups/windows, run things like cd groups/F1/windows/213; down_opacity()
).
Everything I customized in i3 via hacks can be done natively nicely and in python and I love it.
No easy way to check config for correctness I’ve found, but python3 config.py
finds most errors.
Docu suggests python3 -m py_compile config.py
but it returns no output regardless of errors. qtile shell
’s test config also is quiet.
A lot of them. Tried all. Favourites so far. Listed here: Built-in Layouts — Qtile 0.1.dev50+g9c583ed.d20211208 documentation
Main realization so far is that I’ve been using tiling WMs wrong, in i3 I kept manually splitting the window when I needed to have it split into smaller ones. Except that this should happen automatically, because I never want three windows side-by-side at the same time.
Probably my favourite one. Splits stuff nicely in one big and multiple smaller ones in a separate columns.
Added these bits to config:
Key([modkey], "i", lazy.layout.grow()),
Key([modkey], "m", lazy.layout.shrink()),
Key([modkey], "n", lazy.layout.normalize()),
Key([modkey], "o", lazy.layout.maximize()),
<mod+o>
toggles between how big/main is the highlighted window. If it’s the big window, it gets narrower or wider, if it’s one of the smaller ones in a column, each becomes the biggest/smallest in that column.<mod+i>
/<mod+m>
grows/shrinks the current window.<mod+n>
’normalizes’ everything by resetting the layout.Nice intuitive etc, has N columns, moving windows to left-right does what I expect, including creating newer columns, or splitting existing ones as the window “travels” through it.
The tree-thingy that splits each thing into two, ad infinitum.
These bindings use mod3
which is the physical ctrl key, that move the splits with all windows inside them (not individual windows). They seem to be used only for that layout.
Key([mod3], "j", lazy.layout.flip_down()),
Key([mod3], "k", lazy.layout.flip_up()),
Key([mod3], "h", lazy.layout.flip_left()),
Key([mod3], "l", lazy.layout.flip_right()),
Two stacks, one with N “main” windows (1, but configurable), and a second stack for all the other ones. See no added value compared to the Monad ones. But add_after_last=True
makes the behaviour more intuitive to me.
One single window, the rest are hidden behind it (as a stack), no configs, no way to signal if it’s the only window or there are more behind it.
Only layout that I can get to show the titles of the windows inside the stack. You get one stack and window titles on the right.
Meant for browsers like uzbl, and it emulates almost exactly the setup I have for qutebrowser.
layout = qtile.current_layout
group = qtile.current_group
if layout.name == 'monadtall':
layout.cmd_maximize()
if len(group.windows) != 2:
return
One of those two worked:
- calling Obsidian directly as binary (instead of my runner shell script)
- Using config.Match()
to identify it .
from libqtile.config import Screen
from platforms import num_screens, hostname
if num_screens[hostname] == 4:
from bars import chat_bar, main_bar, media_bar, code_bar
# ...
chat_screen = Screen(top=chat_bar)
# ...
screens = [main_screen, media_screen, code_screen, chat_screen]
You can star/unstar a search!
Really handy for summary/analysis-type searches, like for hashtags of things that may be reoccurring.
Additionally a “search” doesn’t stop once you click through files or through the folders, it’s still available in its own tab.
You can embed not just an entire document, but also part of it, like headers! The same mechanism as with linking, but I can’t figure out how the autocompletion is supposed to be used.
In any case, works the same way, page title and then #
for headers and ^
for blocks, for which it will autogenerate a reference in the target file.
To trigger this you have to have the page name already filled in, it suggests stuff, but once you click on something or use tab it generates a link with it immediately. Best way I can figure out is to let it do this, and then replace the syntax around, the autocompletion gets triggered once you get it in a syntax like below: ^66eab0
![[Your page title#
I can always replace return None
with just return
in #python. (Third way is omit a return
completely.)
More about this: The Python return Statement: Usage and Best Practices – Real Python
In python, when doing regex on a multiline string:
re.MULTILINE
makes ^
and $
match on each line, not just begin/end of entire string.re.DOTALL
makes .
match newline (by default it doesn’t).Obsidian can do advanced search: Obsidian search
tag: #Tag
is better than searching the tag by itself, as the latter might find tags inside code listings etc etc etc
I changed the templates I use to be more repetitive but hopefully with less chances for a note meant to be private to get published on my website.
Three types of notes I want to be able to create easily:
I don’t want the Personal ones to end up left in any of the folders parsed by obyde even by chance, and if they do I don’t want them converted, and if they do - shown.
Now I just create a note, it gets put into /
, I give it a name, and then run one of the three templates. The templates take care of moving it to the correct folder and prefic
Now I have three identical templates, they move the note to the correct place, prefix the file with the datetime if needed, and add boilerplate frontmatter.
Public diensttagebuch note (<C-t>
), puts it into /garden/it/
and prefixes with datetime:
<% tp.file.move("garden/it/"+tp.date.now("YYMMDD-HHmm")+" "+tp.file.title) %>---
title: "<% tp.file.title %>"
tags:
- "zc"
- "zc/it"
- "<% tp.file.cursor() %>"
fulldate: <% tp.date.now("YYYY-MM-DDTHH:MM:SSZZ") %>
date: <% tp.date.now("YYYY-MM-DD") %>
layout: post
hidden: false
draft: false
---
Public journal note (<C-S-t>
) is pretty much identical:
<% tp.file.move("garden/rl/"+tp.date.now("YYMMDD-HHmm")+" "+tp.file.title) %>---
title: "<% tp.file.title %>"
tags:
- "zc"
- "zc/rl"
- "<% tp.file.cursor() %>"
fulldate: <% tp.date.now("YYYY-MM-DDTHH:MM:SSZZ") %>
date: <% tp.date.now("YYYY-MM-DD") %>
layout: post
hidden: false
draft: false
---
Notes not meant to be published (<C-t>
) get put into /Personal
, but also:
date
in frontmatter, obyde should loudly error out if it sees them (which it should never)