<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://entorb.net//wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Torben</id>
	<title>Torben&#039;s Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://entorb.net//wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Torben"/>
	<link rel="alternate" type="text/html" href="https://entorb.net//wickie/Special:Contributions/Torben"/>
	<updated>2026-05-06T09:38:37Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.1</generator>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Pnpm_Node_Package_Manager&amp;diff=5418</id>
		<title>Pnpm Node Package Manager</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Pnpm_Node_Package_Manager&amp;diff=5418"/>
		<updated>2026-04-18T18:40:45Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Commands==&lt;br /&gt;
 # install packages locally&lt;br /&gt;
 pnpm install&lt;br /&gt;
 # add package&lt;br /&gt;
 pnpm add vue&lt;br /&gt;
 # add dev package&lt;br /&gt;
 pnpm add -D vitest&lt;br /&gt;
 &lt;br /&gt;
 # update all packages to latest minor version&lt;br /&gt;
 pnpm up&lt;br /&gt;
 # update all packages to latest version&lt;br /&gt;
 pnpm up --latest&lt;br /&gt;
 &lt;br /&gt;
 # run package&lt;br /&gt;
 pnpm exec vitest --watch=false&lt;br /&gt;
 &lt;br /&gt;
 # run script (defined in package.json)&lt;br /&gt;
 pnpm run myscript&lt;br /&gt;
 &lt;br /&gt;
 # run without installing to repo&lt;br /&gt;
 pnpx cowsay Moin&lt;br /&gt;
 &lt;br /&gt;
 # check used packages for vulnerabilities&lt;br /&gt;
 pnpm audit&lt;br /&gt;
 pnpm audit --fix&lt;br /&gt;
 pnpm install&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5417</id>
		<title>AI LLM for Coding</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5417"/>
		<updated>2026-04-18T18:24:38Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Mistral Devstral==&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
==Google Gemini==&lt;br /&gt;
pros: free to use with google account&lt;br /&gt;
create your API key at https://aistudio.google.com/app/api-keys&lt;br /&gt;
&lt;br /&gt;
Install&lt;br /&gt;
 npm install -g @google/gemini-cli@latest&lt;br /&gt;
 gemini&lt;br /&gt;
 # or just run&lt;br /&gt;
 npx https://github.com/google-gemini/gemini-cli&lt;br /&gt;
usage&lt;br /&gt;
 cd /tmp&lt;br /&gt;
 export GEMINI_API_KEY=&amp;quot;xxx&amp;quot;&lt;br /&gt;
 git clone https://github.com/entorb/meeting-meter&lt;br /&gt;
 cd myProject&lt;br /&gt;
 gemini&lt;br /&gt;
&lt;br /&gt;
to switch model from pro to flash:&lt;br /&gt;
 export GEMINI_MODEL=&amp;quot;gemini-2.5-flash&amp;quot;&lt;br /&gt;
&lt;br /&gt;
secret alternative: .env file&lt;br /&gt;
 GEMINI_API_KEY=xxx&lt;br /&gt;
&lt;br /&gt;
==Google Antigravity==&lt;br /&gt;
https://antigravity.google/download&lt;br /&gt;
&lt;br /&gt;
==Claude==&lt;br /&gt;
===Web===&lt;br /&gt;
https://claude.ai &amp;lt;br&amp;gt;&lt;br /&gt;
pro: Daily reset free quota&amp;lt;br&amp;gt;&lt;br /&gt;
cons: need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
===Console===&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: https://console.anthropic.com &amp;lt;br&amp;gt;&lt;br /&gt;
pro: fully integrated in code base&amp;lt;br&amp;gt;&lt;br /&gt;
cons: costs money, 5€ are easily spend&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
&lt;br /&gt;
==Tips==&lt;br /&gt;
* E2E tests (e.g. Cypress) are very important&lt;br /&gt;
* Use tools for code formatting, linting, type-hints&lt;br /&gt;
* Configure these tools to produce minimum output to reduce token consumption. e.g. for&lt;br /&gt;
** Prettier:  --log-level silent&lt;br /&gt;
** Cypress: cypress run --e2e --quiet&lt;br /&gt;
* For Refactoring/renaming: better do it manually, via vscode, that is a lot faster&lt;br /&gt;
&lt;br /&gt;
===Cavemen style===&lt;br /&gt;
https://github.com/JuliusBrussee/caveman&lt;br /&gt;
 npx skills add JuliusBrussee/caveman -a github-copilot&lt;br /&gt;
It has an option to install to local home dir instead of to project repo.&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5416</id>
		<title>AI LLM for Coding</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5416"/>
		<updated>2026-04-18T16:37:49Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Mistral Devstral */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Mistral Devstral==&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
==Google Gemini==&lt;br /&gt;
pros: free to use with google account&lt;br /&gt;
create your API key at https://aistudio.google.com/app/api-keys&lt;br /&gt;
&lt;br /&gt;
Install&lt;br /&gt;
 npm install -g @google/gemini-cli@latest&lt;br /&gt;
 gemini&lt;br /&gt;
 # or just run&lt;br /&gt;
 npx https://github.com/google-gemini/gemini-cli&lt;br /&gt;
usage&lt;br /&gt;
 cd /tmp&lt;br /&gt;
 export GEMINI_API_KEY=&amp;quot;xxx&amp;quot;&lt;br /&gt;
 git clone https://github.com/entorb/meeting-meter&lt;br /&gt;
 cd myProject&lt;br /&gt;
 gemini&lt;br /&gt;
&lt;br /&gt;
to switch model from pro to flash:&lt;br /&gt;
 export GEMINI_MODEL=&amp;quot;gemini-2.5-flash&amp;quot;&lt;br /&gt;
&lt;br /&gt;
secret alternative: .env file&lt;br /&gt;
 GEMINI_API_KEY=xxx&lt;br /&gt;
&lt;br /&gt;
==Google Antigravity==&lt;br /&gt;
https://antigravity.google/download&lt;br /&gt;
&lt;br /&gt;
==Claude==&lt;br /&gt;
===Web===&lt;br /&gt;
https://claude.ai &amp;lt;br&amp;gt;&lt;br /&gt;
pro: Daily reset free quota&amp;lt;br&amp;gt;&lt;br /&gt;
cons: need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
===Console===&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: https://console.anthropic.com &amp;lt;br&amp;gt;&lt;br /&gt;
pro: fully integrated in code base&amp;lt;br&amp;gt;&lt;br /&gt;
cons: costs money, 5€ are easily spend&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
&lt;br /&gt;
==Tips==&lt;br /&gt;
* E2E tests (e.g. Cypress) are very important&lt;br /&gt;
* Use tools for code formatting, linting, type-hints&lt;br /&gt;
* Configure these tools to produce minimum output to reduce token consumption. e.g. for&lt;br /&gt;
** Prettier:  --log-level silent&lt;br /&gt;
** Cypress: cypress run --e2e --quiet&lt;br /&gt;
* For Refactoring/renaming: better do it manually, via vscode, that is a lot faster&lt;br /&gt;
&lt;br /&gt;
===Cavemen style===&lt;br /&gt;
https://github.com/JuliusBrussee/caveman&lt;br /&gt;
 npx skills add JuliusBrussee/caveman -a github-copilot&lt;br /&gt;
It has an option to install to local home dir instead of to project repo.&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5415</id>
		<title>AI LLM for Coding</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5415"/>
		<updated>2026-04-18T16:18:50Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Mistral Devstral==&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
&lt;br /&gt;
==Google Gemini==&lt;br /&gt;
pros: free to use with google account&lt;br /&gt;
create your API key at https://aistudio.google.com/app/api-keys&lt;br /&gt;
&lt;br /&gt;
Install&lt;br /&gt;
 npm install -g @google/gemini-cli@latest&lt;br /&gt;
 gemini&lt;br /&gt;
 # or just run&lt;br /&gt;
 npx https://github.com/google-gemini/gemini-cli&lt;br /&gt;
usage&lt;br /&gt;
 cd /tmp&lt;br /&gt;
 export GEMINI_API_KEY=&amp;quot;xxx&amp;quot;&lt;br /&gt;
 git clone https://github.com/entorb/meeting-meter&lt;br /&gt;
 cd myProject&lt;br /&gt;
 gemini&lt;br /&gt;
&lt;br /&gt;
to switch model from pro to flash:&lt;br /&gt;
 export GEMINI_MODEL=&amp;quot;gemini-2.5-flash&amp;quot;&lt;br /&gt;
&lt;br /&gt;
secret alternative: .env file&lt;br /&gt;
 GEMINI_API_KEY=xxx&lt;br /&gt;
&lt;br /&gt;
==Google Antigravity==&lt;br /&gt;
https://antigravity.google/download&lt;br /&gt;
&lt;br /&gt;
==Claude==&lt;br /&gt;
===Web===&lt;br /&gt;
https://claude.ai &amp;lt;br&amp;gt;&lt;br /&gt;
pro: Daily reset free quota&amp;lt;br&amp;gt;&lt;br /&gt;
cons: need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
===Console===&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: https://console.anthropic.com &amp;lt;br&amp;gt;&lt;br /&gt;
pro: fully integrated in code base&amp;lt;br&amp;gt;&lt;br /&gt;
cons: costs money, 5€ are easily spend&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
&lt;br /&gt;
==Tips==&lt;br /&gt;
* E2E tests (e.g. Cypress) are very important&lt;br /&gt;
* Use tools for code formatting, linting, type-hints&lt;br /&gt;
* Configure these tools to produce minimum output to reduce token consumption. e.g. for&lt;br /&gt;
** Prettier:  --log-level silent&lt;br /&gt;
** Cypress: cypress run --e2e --quiet&lt;br /&gt;
* For Refactoring/renaming: better do it manually, via vscode, that is a lot faster&lt;br /&gt;
&lt;br /&gt;
===Cavemen style===&lt;br /&gt;
https://github.com/JuliusBrussee/caveman&lt;br /&gt;
 npx skills add JuliusBrussee/caveman -a github-copilot&lt;br /&gt;
It has an option to install to local home dir instead of to project repo.&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Pnpm_Node_Package_Manager&amp;diff=5414</id>
		<title>Pnpm Node Package Manager</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Pnpm_Node_Package_Manager&amp;diff=5414"/>
		<updated>2026-04-18T16:12:11Z</updated>

		<summary type="html">&lt;p&gt;Torben: Created page with &amp;quot;==Commands==  # install packages locally  pnpm install  # add package  pnpm add vue  # add dev package  pnpm add -D vitest    # update  pnpm up    # run package  pnpm exec vitest --watch=false    # run script (defined in package.json)  pnpm run myscript    # check used packages for vulnerabilities  pnpm audit  pnpm audit --fix  pnpm install&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Commands==&lt;br /&gt;
 # install packages locally&lt;br /&gt;
 pnpm install&lt;br /&gt;
 # add package&lt;br /&gt;
 pnpm add vue&lt;br /&gt;
 # add dev package&lt;br /&gt;
 pnpm add -D vitest&lt;br /&gt;
 &lt;br /&gt;
 # update&lt;br /&gt;
 pnpm up&lt;br /&gt;
 &lt;br /&gt;
 # run package&lt;br /&gt;
 pnpm exec vitest --watch=false&lt;br /&gt;
 &lt;br /&gt;
 # run script (defined in package.json)&lt;br /&gt;
 pnpm run myscript&lt;br /&gt;
 &lt;br /&gt;
 # check used packages for vulnerabilities&lt;br /&gt;
 pnpm audit&lt;br /&gt;
 pnpm audit --fix&lt;br /&gt;
 pnpm install&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Mac&amp;diff=5413</id>
		<title>Mac</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Mac&amp;diff=5413"/>
		<updated>2026-04-18T16:04:13Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Software]][[Category:Apple]]&lt;br /&gt;
==Terminal==&lt;br /&gt;
===Set Hostname===&lt;br /&gt;
from [https://www.autodesk.com/support/technical/article/caas/sfdcarticles/sfdcarticles/Setting-the-Mac-hostname-or-computer-name-from-the-terminal.html]&lt;br /&gt;
 sudo scutil --set HostName T-MB-2.fritz.box&lt;br /&gt;
 sudo scutil --set LocalHostName T-MB-2&lt;br /&gt;
 sudo scutil --set ComputerName T-MB-2&lt;br /&gt;
 dscacheutil -flushcache&lt;br /&gt;
&lt;br /&gt;
==Shortcuts==&lt;br /&gt;
 Control + K : fill 2nd clipboad by cut text from cursor till end of line&lt;br /&gt;
 Control + Y : paste from 2nd clipboad&lt;br /&gt;
Finder / Open file Dialogs&lt;br /&gt;
 Command + Shift + . : show hidden files&lt;br /&gt;
Windows Management&lt;br /&gt;
(better use Rectangle App, see below)&lt;br /&gt;
 Control + Tab : Switch between applications&lt;br /&gt;
 Control + &amp;lt;   : Switch between windows of same application&lt;br /&gt;
 swipe 3-finger-upwards : Mission Control&lt;br /&gt;
Emoji&lt;br /&gt;
 Control+Command+Space&lt;br /&gt;
&lt;br /&gt;
==Disabling/speedup animations==&lt;br /&gt;
 # Window opening animations&lt;br /&gt;
 defaults write NSGlobalDomain NSAutomaticWindowAnimationsEnabled -bool true&lt;br /&gt;
 # revert&lt;br /&gt;
 # defaults write NSGlobalDomain&lt;br /&gt;
 &lt;br /&gt;
 # The resizing animation&lt;br /&gt;
 defaults write NSGlobalDomain NSWindowResizeTime -float 0.001&lt;br /&gt;
 # revert&lt;br /&gt;
 # defaults write NSGlobalDomain NSWindowResizeTime -float 0.2&lt;br /&gt;
 &lt;br /&gt;
 # The Quick Look window animation&lt;br /&gt;
 defaults write -g QLPanelAnimationDuration -float 0&lt;br /&gt;
 # revert&lt;br /&gt;
 # defaults delete -g QLPanelAnimationDuration&lt;br /&gt;
 &lt;br /&gt;
 # Launching an app from the Dock&lt;br /&gt;
 defaults write com.apple.dock launchanim -bool false&lt;br /&gt;
 # revert&lt;br /&gt;
 # defaults write com.apple.dock launchanim -bool true&lt;br /&gt;
&lt;br /&gt;
==Time Machine Backups==&lt;br /&gt;
===Prepare external Hard Disk===&lt;br /&gt;
Formating problem &lt;br /&gt;
 Erase process has failed, press done to continue. Mediakit reports not enough space on device for requested operation.&lt;br /&gt;
Quoting [https://www.reddit.com/r/applehelp/comments/40yvjh/disk_utility_fails_to_eraseformat_an_external_hdd/] : &lt;br /&gt;
Here&#039;s what I would try. First run&lt;br /&gt;
 diskutil list&lt;br /&gt;
to get the name to the disk you&#039;re trying to format. The below commands assume this is &amp;quot;disk1&amp;quot;, but replace &amp;quot;disk1&amp;quot; with the correct disk if it&#039;s something different.&lt;br /&gt;
Now unmount the disk:&lt;br /&gt;
 diskutil unmountDisk force disk1&lt;br /&gt;
and then write zeros to the boot sector:&lt;br /&gt;
 sudo dd if=/dev/zero of=/dev/disk1 bs=1024 count=1024&lt;br /&gt;
finally attempt to partition it again:&lt;br /&gt;
 diskutil partitionDisk disk1 GPT JHFS+ &amp;quot;My External HD&amp;quot; 0g&lt;br /&gt;
&lt;br /&gt;
===Encryption Takes For Ever===&lt;br /&gt;
Quoting [https://discussions.apple.com/thread/7813232]: To speed up the process of TM encryption, go to Disk Utility, select your external HDD and erase and use the option journaled/encrypted. Once your external HDD is erased, you can redo the backup and the encryption will be as fast as a flash.&lt;br /&gt;
&lt;br /&gt;
===Delete old backups===&lt;br /&gt;
mount backup drive in filder via connect to server&lt;br /&gt;
 afp://my_DS_Hostname&lt;br /&gt;
now it can be found under Locations and you can delete old backups by sending them to bin (and cleaning that one afterwards)&lt;br /&gt;
&lt;br /&gt;
==Install Google Keep Notes as Application via Chromium==&lt;br /&gt;
First download [https://download-chromium.appspot.com/?platform=Mac&amp;amp;type=snapshots Chromium] and move to applications than follow this [http://www.rawinfopages.com/mac/content/how-run-google-keep-window-desktop-mac-app guide] to create a shell script for automator.&lt;br /&gt;
 Type     = Application&lt;br /&gt;
 Action   = Run Shell Script&lt;br /&gt;
 Contents = /Applications/Chromium.app/Contents/MacOS/Chromium --app=https://keep.google.com&lt;br /&gt;
Save as .app in Application folder and drag to dock, done!&lt;br /&gt;
&lt;br /&gt;
==Settings==&lt;br /&gt;
Hide top panel from secondary screen from [http://osxdaily.com/2013/10/27/hide-menu-bar-external-display-mac-os-x/]&lt;br /&gt;
 Mission Control -&amp;gt; Uncheck the box next to 2Displays have separate Spaces&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==Tools==&lt;br /&gt;
===Git===&lt;br /&gt;
Git requires apple xcode tools &lt;br /&gt;
 xcode-select --install&lt;br /&gt;
&lt;br /&gt;
===HomeBrew Package Manager===&lt;br /&gt;
from [https://docs.brew.sh/FAQ]&lt;br /&gt;
First update the formulae and Homebrew itself:&lt;br /&gt;
 brew update&lt;br /&gt;
install&lt;br /&gt;
 brew install mypackage&lt;br /&gt;
list installed pakages&lt;br /&gt;
 brew list&lt;br /&gt;
uninstall&lt;br /&gt;
 brew remove mypackage&lt;br /&gt;
You can now find out what is outdated with:&lt;br /&gt;
 brew outdated&lt;br /&gt;
Upgrade everything with:&lt;br /&gt;
 brew upgrade&lt;br /&gt;
By default, Homebrew does not uninstall old versions of a formula, so over time you will accumulate old versions. To remove them, simply use:&lt;br /&gt;
 brew cleanup mypackage&lt;br /&gt;
or clean up everything at once:&lt;br /&gt;
 brew cleanup&lt;br /&gt;
or to see what would be cleaned up:&lt;br /&gt;
 brew cleanup -n&lt;br /&gt;
&lt;br /&gt;
====Special Commands====&lt;br /&gt;
show dependancies&lt;br /&gt;
 brew deps gnuplot&lt;br /&gt;
search for a package/version&lt;br /&gt;
 brew search gnuplot&lt;br /&gt;
manual download file into cache to install a local file [http://mygeekdaddy.net/2014/12/05/how-to-install-a-local-file-in-homebrew/]&lt;br /&gt;
ignore dependencies&lt;br /&gt;
 brew install gnuplot --ignore-dependencies&lt;br /&gt;
&lt;br /&gt;
===Eraze Disk/fill by Zeros===&lt;br /&gt;
 diskutil zeroDisk /Volumes/myDiskName/&lt;br /&gt;
alternative: create a big file&lt;br /&gt;
 dd if=/dev/zero of=tmp_file.txt&lt;br /&gt;
&lt;br /&gt;
===Spelling: unlearn a word/edit custom words===&lt;br /&gt;
 vim ~/Library/Spelling/LocalDictionary&lt;br /&gt;
&lt;br /&gt;
==Apps==&lt;br /&gt;
* https://rectangleapp.com Window mover (Control+Command+Arrows)&lt;br /&gt;
* https://maccy.app clipboard manager (Command+Shift+C)&lt;br /&gt;
* https://apphousekitchen.com/ AlDente: charge limiter&lt;br /&gt;
&lt;br /&gt;
Not in use&lt;br /&gt;
* https://github.com/exelban/stats/ system stats&lt;br /&gt;
* https://matthewpalmer.net/rocket/ emoji typing&lt;br /&gt;
* https://www.mowglii.com/itsycal/ tiny menu bar calendar&lt;br /&gt;
* https://macmousefix.com/ Mac Mouse Fix brings all features of an Apple Trackpad&lt;br /&gt;
* https://spoti.ca/ controls for Spotify and Apple Music&lt;br /&gt;
&lt;br /&gt;
==Fixing problems==&lt;br /&gt;
this ssh warning&lt;br /&gt;
 perl: warning: Setting locale failed.&lt;br /&gt;
can be fixed by setting this in .zshrc&lt;br /&gt;
 export LC_ALL=en_US.UTF-8&lt;br /&gt;
&lt;br /&gt;
==Terminal Hacks==&lt;br /&gt;
===Kill Process blocking a Port===&lt;br /&gt;
 lsof -i :5173&lt;br /&gt;
 kill 1234&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Windows_Batch_Scripting&amp;diff=5412</id>
		<title>Windows Batch Scripting</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Windows_Batch_Scripting&amp;diff=5412"/>
		<updated>2026-04-17T17:07:23Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Run as other user */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Software]][[Category:Windows]]&lt;br /&gt;
[http://weblogs.asp.net/jongalloway/top-10-dos-batch-tips-yes-dos-batch see &amp;quot;Top 10 DOS Batch tips&amp;quot;] and [http://www.axel-hahn.de/batch/batchecke/tipps/ BATch-Dateien - kleine Tipps] for more stuff&lt;br /&gt;
&lt;br /&gt;
===Template===&lt;br /&gt;
 @echo off&lt;br /&gt;
 title %~n0&lt;br /&gt;
 setlocal&lt;br /&gt;
&lt;br /&gt;
===current dir and filename===&lt;br /&gt;
 # current working dir&lt;br /&gt;
 echo %CD%&lt;br /&gt;
 # path of current file&lt;br /&gt;
 echo %0&lt;br /&gt;
# filename without the extension.&lt;br /&gt;
 echo %~n0&lt;br /&gt;
# filename and extension.&lt;br /&gt;
 echo %~nx0&lt;br /&gt;
# or &lt;br /&gt;
 echo %~n0%~x0&lt;br /&gt;
&lt;br /&gt;
===Title to Dirname===&lt;br /&gt;
 @echo off&lt;br /&gt;
 for %%f in (%cd%) do set dirname=%%~nxf&lt;br /&gt;
 title %dirname%&lt;br /&gt;
&lt;br /&gt;
===Empty c:\tmp===&lt;br /&gt;
 @echo off&lt;br /&gt;
 rmdir /q /s c:\tmp&lt;br /&gt;
 # or rd /q /s c:\tmp&lt;br /&gt;
 mkdir c:\tmp&lt;br /&gt;
&lt;br /&gt;
===find files / search (and delete) old files===&lt;br /&gt;
via forfiles&lt;br /&gt;
 forfiles /P &amp;quot;C:\tmp\test-del&amp;quot; /S /D -30 /C &amp;quot;cmd /c echo @path&amp;quot;&lt;br /&gt;
 forfiles /P &amp;quot;C:\tmp\test-del&amp;quot; /S /D -30 /C &amp;quot;cmd /c del @path&amp;quot;&lt;br /&gt;
 forfiles /P &amp;quot;C:\tmp\test-del&amp;quot; -S -D 90 -m *.log -c &amp;quot;cmd /c del @path&amp;quot;&lt;br /&gt;
&lt;br /&gt;
via robocopy&lt;br /&gt;
 robocopy &amp;quot;C:\tmp\test-del&amp;quot; &amp;quot;*.log&amp;quot; &amp;quot;C:\cleanup-logs&amp;quot; /mov /minage:90 /NP&lt;br /&gt;
 del C:\cleanup-logs /q&lt;br /&gt;
&lt;br /&gt;
===ReadOnly Flag / WriteProtection===&lt;br /&gt;
Remove&lt;br /&gt;
 attrib -h -r -s /s /d *.*&lt;br /&gt;
Set &lt;br /&gt;
 attrib +r c:\Users\torben\Desktop\SyncedFolder\*.* /s&lt;br /&gt;
&lt;br /&gt;
  R   Read-only file attribute.&lt;br /&gt;
  S   System file attribute.&lt;br /&gt;
  H   Hidden file attribute.&lt;br /&gt;
  /S  Processes matching files in the current folder and all subfolders.&lt;br /&gt;
  /D  Processes folders as well.&lt;br /&gt;
&lt;br /&gt;
===Set Current Working Dir to Script Location ===&lt;br /&gt;
This is important if the script is started using a taskmanager etc.&lt;br /&gt;
 e:&lt;br /&gt;
 cd %~dp0&lt;br /&gt;
&lt;br /&gt;
===Set Window Title===&lt;br /&gt;
 TITLE My Window Title&lt;br /&gt;
or when opening via START from another Batch file&lt;br /&gt;
 START &amp;quot;My Window Title&amp;quot; 2.cmd&lt;br /&gt;
&lt;br /&gt;
===Get FolderName===&lt;br /&gt;
This when excecuted in c:\sub\folder it returns &amp;quot;folder&amp;quot;&lt;br /&gt;
 for %%* in (%CD%) do set CurrDirName=%%~nx*&lt;br /&gt;
 echo %CurrDirName%&lt;br /&gt;
&lt;br /&gt;
===List of files to Textfile===&lt;br /&gt;
 dir *.* /b &amp;gt; ..\liste.txt&lt;br /&gt;
&lt;br /&gt;
===Pipe output to textfile===&lt;br /&gt;
From [https://support.microsoft.com/en-us/help/110930/redirecting-error-messages-from-command-prompt-stderr-stdout] You can print the errors and standard output to a single file by using the &amp;quot;&amp;amp;1&amp;quot; command to redirect the output for STDERR to STDOUT and then sending the output from STDOUT to a file:&lt;br /&gt;
 some_command &amp;gt; output.log 2&amp;gt;&amp;amp;1&lt;br /&gt;
 some_command 1&amp;gt; output.log 2&amp;gt; error.log&lt;br /&gt;
&lt;br /&gt;
===exit if command fails with error===&lt;br /&gt;
 some_command1&lt;br /&gt;
 IF %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%&lt;br /&gt;
 some_command2&lt;br /&gt;
 IF %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%&lt;br /&gt;
&lt;br /&gt;
===Net use for network drive mounting===&lt;br /&gt;
 net use Y: \\server\path /user:myuser&lt;br /&gt;
 &lt;br /&gt;
 net use Y: /delete&lt;br /&gt;
&lt;br /&gt;
===Zip */*.ini===&lt;br /&gt;
 @echo off&lt;br /&gt;
 setlocal EnableDelayedExpansion&lt;br /&gt;
 set &amp;quot;OUT=inis.zip&amp;quot;&lt;br /&gt;
 REM Use PowerShell to expand glob pattern and pipe relative paths to tar with --files-from=-&lt;br /&gt;
 powershell -NoLogo -NoProfile -Command &amp;quot;Get-ChildItem -Path &#039;*\*.ini&#039; -File | ForEach-Object { $_.FullName.Substring((Get-Location).Path.Length + 1) } | tar -c -a -f &#039;%OUT%&#039; --files-from=-&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===Loops===&lt;br /&gt;
====if====&lt;br /&gt;
check if mounting (net use) was successful&lt;br /&gt;
 net use q: \\server\share&lt;br /&gt;
 q:&lt;br /&gt;
 cd \&lt;br /&gt;
 IF NOT EXIST dir1\dir2 (&lt;br /&gt;
   echo mouting of share failed&lt;br /&gt;
   pause&lt;br /&gt;
   c:&lt;br /&gt;
   net use q: /delete&lt;br /&gt;
   EXIT /B 1&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
====for====&lt;br /&gt;
loop over values&lt;br /&gt;
 for %%L in (cn, de, en, es, fr, ko, sk, vn) do (&lt;br /&gt;
 echo %%L&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
loop over all dirs&lt;br /&gt;
 FOR /D %%D in (&amp;quot;*&amp;quot;) DO (&lt;br /&gt;
 echo %%D&lt;br /&gt;
 )&lt;br /&gt;
double loop over all dirs&lt;br /&gt;
 FOR /D %%P in (&amp;quot;*&amp;quot;) DO (&lt;br /&gt;
 echo parent = %%P&lt;br /&gt;
   FOR /D %%C in (&amp;quot;%%P\*&amp;quot;) DO (&lt;br /&gt;
   echo child = %%C&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
loop over files&lt;br /&gt;
 for %%F in (*.pdf) do (&lt;br /&gt;
 echo %%F&lt;br /&gt;
 copy %%F %%~nF.old&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
====for and grep.exe====&lt;br /&gt;
using UnixUtils grep.exe one can easily extract exceptions from logs:&lt;br /&gt;
 for %%F in (*.log) do (&lt;br /&gt;
   grep -B3 -A3 &amp;quot;exception&amp;quot; %%F &amp;gt; %%~nF-error.txt &lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
===Variables===&lt;br /&gt;
 :: no spaces around &#039;=&#039;!!!&lt;br /&gt;
 set xyz=myfile.bat&lt;br /&gt;
 set /p xyz=Variable Eingeben:&lt;br /&gt;
 set /p xyz= &amp;lt; TMP.dat &lt;br /&gt;
 echo %xyz%&lt;br /&gt;
&lt;br /&gt;
====Substrings====&lt;br /&gt;
Substring via &amp;quot;:~&amp;quot;&lt;br /&gt;
 set year=%date:~-4,4&lt;br /&gt;
Path, filename and extension of file stored in variable %%F&lt;br /&gt;
 set path=%%~pF&lt;br /&gt;
 set name=%%~nF&lt;br /&gt;
 set ext=%%~cF&lt;br /&gt;
&lt;br /&gt;
====Set Variable to Program Output====&lt;br /&gt;
 for /f &amp;quot;delims=&amp;quot; %%a in (&#039; powershell -c &amp;quot;$lastmonth = (Get-Date).addMonths(-3); $lastmonth.tostring(\&amp;quot;yyyy-MM\&amp;quot;)&amp;quot; &#039;) do set &amp;quot;MONTH=%%a&amp;quot;&lt;br /&gt;
 echo %MONTH%&lt;br /&gt;
&lt;br /&gt;
===Date &amp;amp; Time ===&lt;br /&gt;
====DateString====&lt;br /&gt;
 set DATESTR=%date:~-4,4%-%date:~-7,2%-%date:~-10,2%_%time:~0,2%:%time:~3,2%:%time:~6,2%&lt;br /&gt;
 or&lt;br /&gt;
 set DATESTR=%date:~-2,4%%date:~-7,2%%date:~-10,2%_%time:~0,2%%time:~3,2%%time:~6,2%&lt;br /&gt;
 :: replace &#039; &#039; in small hours with 0&lt;br /&gt;
 set DATESTR=%DATESTR: =0%&lt;br /&gt;
 = 240419_085724 &lt;br /&gt;
 zip.exe -9 %DATESTR%.zip *.bat&lt;br /&gt;
for better example of zipping see [[Backup#Zip_Folder|zip folder in Backup section]]&lt;br /&gt;
&lt;br /&gt;
====DayOfWeek====&lt;br /&gt;
from [https://stackoverflow.com/questions/25537313/issue-with-getting-batch-script-to-print-keeps-given-a-error-about-set-was-unexp here]&lt;br /&gt;
 SETLOCAL enabledelayedexpansion&lt;br /&gt;
 SET /a count=0&lt;br /&gt;
 FOR /F &amp;quot;skip=1&amp;quot; %%D IN (&#039;wmic path win32_localtime get dayofweek&#039;) DO (&lt;br /&gt;
     if &amp;quot;!count!&amp;quot; GTR &amp;quot;0&amp;quot; GOTO next&lt;br /&gt;
     set dow=%%D&lt;br /&gt;
     SET /a count+=1&lt;br /&gt;
 )&lt;br /&gt;
 :next&lt;br /&gt;
 echo %dow%&lt;br /&gt;
&lt;br /&gt;
====Yesterday====&lt;br /&gt;
from [https://stackoverflow.com/questions/2954359/dos-batch-programming-howto-get-and-display-yesterday-date here]&lt;br /&gt;
 set befehl=&amp;quot;PowerShell $date = Get-Date; $date=$date.AddDays(-1); $date.ToString(&#039;yyyy-MM-dd&#039;)&amp;quot;&lt;br /&gt;
 for /f %%i in (&#039;%befehl%&#039;) do set yesterday=%%i&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Copy all files from subfolders to one folder ===&lt;br /&gt;
from [https://stackoverflow.com/questions/11720681/windows-batch-copy-files-from-subfolders-to-one-folder here]&lt;br /&gt;
 set source=c:\source&lt;br /&gt;
 set target=c:\target&lt;br /&gt;
 cd %source%&lt;br /&gt;
 for /r %%a in (*.*) do (&lt;br /&gt;
  COPY &amp;quot;%%a&amp;quot; &amp;quot;%target%&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
===FTP===&lt;br /&gt;
====FTP-Upload====&lt;br /&gt;
file &amp;quot;ftp.bat&amp;quot;&lt;br /&gt;
 @echo off&lt;br /&gt;
 ftp &amp;quot;-s:FtpScript&amp;quot;&lt;br /&gt;
 pause&lt;br /&gt;
 cls&lt;br /&gt;
&lt;br /&gt;
file &amp;quot;FtpScript&amp;quot;&lt;br /&gt;
 open www.xyz.de&lt;br /&gt;
 [User]&lt;br /&gt;
 [Password]&lt;br /&gt;
 BINARY&lt;br /&gt;
 put [File]&lt;br /&gt;
 quit&lt;br /&gt;
&lt;br /&gt;
===UnixUtils===&lt;br /&gt;
Using [https://sourceforge.net/projects/unxutils/ UnixUtils] for Windows wget, grep, etc are usable to scripts in Windows. In the UnixUtils.zip the .exe files are located in foltder usr/local/wbin/ . Examples:&lt;br /&gt;
 head.exe -c 1000 mylog.log &amp;gt; outHead1000Bytes.log&lt;br /&gt;
 head.exe -n 1000 mylog.log &amp;gt; outHead1000Lines.log&lt;br /&gt;
 tail.exe -c 1000 mylog.log &amp;gt; outTail1000Bytes.log&lt;br /&gt;
 tail.exe -n 1000 mylog.log &amp;gt; outTail1000Lines.log&lt;br /&gt;
 grep.exe -i -B3 -A1 &amp;quot;ERROR&amp;quot; mylog.log &amp;gt; outGrepErrors.log&lt;br /&gt;
&lt;br /&gt;
====Curl to check if page online====&lt;br /&gt;
 @echo off&lt;br /&gt;
 set URL1=https://my.server.com&lt;br /&gt;
 :loop&lt;br /&gt;
 for /f &amp;quot;delims=&amp;quot; %%i in (&#039;curl -o nul -I -s -w &amp;quot;%%{http_code}&amp;quot; &amp;quot;%URL%&amp;quot;&#039;) do set HTTP_STATUS=%%i&lt;br /&gt;
 echo Status: %HTTP_STATUS% at %date% %time%&lt;br /&gt;
 timeout /t 5&lt;br /&gt;
 goto loop&lt;br /&gt;
&lt;br /&gt;
===TOP CPU RAM Disk I/O===&lt;br /&gt;
# CPU + RAM&lt;br /&gt;
 top&lt;br /&gt;
# I/O&lt;br /&gt;
 sudo iotop -o -d 2 -k&lt;br /&gt;
&lt;br /&gt;
===Run as other user===&lt;br /&gt;
 runas /user:MYDOMAIN\myuser /savecred  &amp;quot;cmd /K cd c:\mydir &amp;amp;&amp;amp; my_programm.exe&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 /K to keep window open when finished&lt;br /&gt;
 /C to close window when finished&lt;br /&gt;
&lt;br /&gt;
===checks.cmd - Run chk_*.cmd===&lt;br /&gt;
run all chk_*.cmd scripts in same dir and print summary of failed ones&lt;br /&gt;
&lt;br /&gt;
 @echo off&lt;br /&gt;
 title %~n0&lt;br /&gt;
 setlocal enabledelayedexpansion&lt;br /&gt;
 cd %~dp0\..&lt;br /&gt;
 &lt;br /&gt;
 set failures=0&lt;br /&gt;
 set &amp;quot;failed_list=&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 for %%f in (%~dp0chk_*.cmd) do (&lt;br /&gt;
     echo.&lt;br /&gt;
     echo === %%~nf ===&lt;br /&gt;
     call &amp;quot;%%f&amp;quot;&lt;br /&gt;
     if errorlevel 1 (&lt;br /&gt;
         set /a failures+=1&lt;br /&gt;
         set &amp;quot;failed_list=!failed_list! %%~nf&amp;quot;&lt;br /&gt;
     )&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 echo.&lt;br /&gt;
 if !failures!==0 (&lt;br /&gt;
     echo All checks passed.&lt;br /&gt;
 ) else (&lt;br /&gt;
     echo !failures! checks failed:!failed_list!&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 echo.&lt;br /&gt;
 pause&lt;br /&gt;
&lt;br /&gt;
Other scripts contain&lt;br /&gt;
 ...&lt;br /&gt;
 some_command&lt;br /&gt;
 if errorlevel 1 exit /b 1&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Windows_Batch_Scripting&amp;diff=5411</id>
		<title>Windows Batch Scripting</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Windows_Batch_Scripting&amp;diff=5411"/>
		<updated>2026-04-17T17:04:49Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Software]][[Category:Windows]]&lt;br /&gt;
[http://weblogs.asp.net/jongalloway/top-10-dos-batch-tips-yes-dos-batch see &amp;quot;Top 10 DOS Batch tips&amp;quot;] and [http://www.axel-hahn.de/batch/batchecke/tipps/ BATch-Dateien - kleine Tipps] for more stuff&lt;br /&gt;
&lt;br /&gt;
===Template===&lt;br /&gt;
 @echo off&lt;br /&gt;
 title %~n0&lt;br /&gt;
 setlocal&lt;br /&gt;
&lt;br /&gt;
===current dir and filename===&lt;br /&gt;
 # current working dir&lt;br /&gt;
 echo %CD%&lt;br /&gt;
 # path of current file&lt;br /&gt;
 echo %0&lt;br /&gt;
# filename without the extension.&lt;br /&gt;
 echo %~n0&lt;br /&gt;
# filename and extension.&lt;br /&gt;
 echo %~nx0&lt;br /&gt;
# or &lt;br /&gt;
 echo %~n0%~x0&lt;br /&gt;
&lt;br /&gt;
===Title to Dirname===&lt;br /&gt;
 @echo off&lt;br /&gt;
 for %%f in (%cd%) do set dirname=%%~nxf&lt;br /&gt;
 title %dirname%&lt;br /&gt;
&lt;br /&gt;
===Empty c:\tmp===&lt;br /&gt;
 @echo off&lt;br /&gt;
 rmdir /q /s c:\tmp&lt;br /&gt;
 # or rd /q /s c:\tmp&lt;br /&gt;
 mkdir c:\tmp&lt;br /&gt;
&lt;br /&gt;
===find files / search (and delete) old files===&lt;br /&gt;
via forfiles&lt;br /&gt;
 forfiles /P &amp;quot;C:\tmp\test-del&amp;quot; /S /D -30 /C &amp;quot;cmd /c echo @path&amp;quot;&lt;br /&gt;
 forfiles /P &amp;quot;C:\tmp\test-del&amp;quot; /S /D -30 /C &amp;quot;cmd /c del @path&amp;quot;&lt;br /&gt;
 forfiles /P &amp;quot;C:\tmp\test-del&amp;quot; -S -D 90 -m *.log -c &amp;quot;cmd /c del @path&amp;quot;&lt;br /&gt;
&lt;br /&gt;
via robocopy&lt;br /&gt;
 robocopy &amp;quot;C:\tmp\test-del&amp;quot; &amp;quot;*.log&amp;quot; &amp;quot;C:\cleanup-logs&amp;quot; /mov /minage:90 /NP&lt;br /&gt;
 del C:\cleanup-logs /q&lt;br /&gt;
&lt;br /&gt;
===ReadOnly Flag / WriteProtection===&lt;br /&gt;
Remove&lt;br /&gt;
 attrib -h -r -s /s /d *.*&lt;br /&gt;
Set &lt;br /&gt;
 attrib +r c:\Users\torben\Desktop\SyncedFolder\*.* /s&lt;br /&gt;
&lt;br /&gt;
  R   Read-only file attribute.&lt;br /&gt;
  S   System file attribute.&lt;br /&gt;
  H   Hidden file attribute.&lt;br /&gt;
  /S  Processes matching files in the current folder and all subfolders.&lt;br /&gt;
  /D  Processes folders as well.&lt;br /&gt;
&lt;br /&gt;
===Set Current Working Dir to Script Location ===&lt;br /&gt;
This is important if the script is started using a taskmanager etc.&lt;br /&gt;
 e:&lt;br /&gt;
 cd %~dp0&lt;br /&gt;
&lt;br /&gt;
===Set Window Title===&lt;br /&gt;
 TITLE My Window Title&lt;br /&gt;
or when opening via START from another Batch file&lt;br /&gt;
 START &amp;quot;My Window Title&amp;quot; 2.cmd&lt;br /&gt;
&lt;br /&gt;
===Get FolderName===&lt;br /&gt;
This when excecuted in c:\sub\folder it returns &amp;quot;folder&amp;quot;&lt;br /&gt;
 for %%* in (%CD%) do set CurrDirName=%%~nx*&lt;br /&gt;
 echo %CurrDirName%&lt;br /&gt;
&lt;br /&gt;
===List of files to Textfile===&lt;br /&gt;
 dir *.* /b &amp;gt; ..\liste.txt&lt;br /&gt;
&lt;br /&gt;
===Pipe output to textfile===&lt;br /&gt;
From [https://support.microsoft.com/en-us/help/110930/redirecting-error-messages-from-command-prompt-stderr-stdout] You can print the errors and standard output to a single file by using the &amp;quot;&amp;amp;1&amp;quot; command to redirect the output for STDERR to STDOUT and then sending the output from STDOUT to a file:&lt;br /&gt;
 some_command &amp;gt; output.log 2&amp;gt;&amp;amp;1&lt;br /&gt;
 some_command 1&amp;gt; output.log 2&amp;gt; error.log&lt;br /&gt;
&lt;br /&gt;
===exit if command fails with error===&lt;br /&gt;
 some_command1&lt;br /&gt;
 IF %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%&lt;br /&gt;
 some_command2&lt;br /&gt;
 IF %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%&lt;br /&gt;
&lt;br /&gt;
===Net use for network drive mounting===&lt;br /&gt;
 net use Y: \\server\path /user:myuser&lt;br /&gt;
 &lt;br /&gt;
 net use Y: /delete&lt;br /&gt;
&lt;br /&gt;
===Zip */*.ini===&lt;br /&gt;
 @echo off&lt;br /&gt;
 setlocal EnableDelayedExpansion&lt;br /&gt;
 set &amp;quot;OUT=inis.zip&amp;quot;&lt;br /&gt;
 REM Use PowerShell to expand glob pattern and pipe relative paths to tar with --files-from=-&lt;br /&gt;
 powershell -NoLogo -NoProfile -Command &amp;quot;Get-ChildItem -Path &#039;*\*.ini&#039; -File | ForEach-Object { $_.FullName.Substring((Get-Location).Path.Length + 1) } | tar -c -a -f &#039;%OUT%&#039; --files-from=-&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===Loops===&lt;br /&gt;
====if====&lt;br /&gt;
check if mounting (net use) was successful&lt;br /&gt;
 net use q: \\server\share&lt;br /&gt;
 q:&lt;br /&gt;
 cd \&lt;br /&gt;
 IF NOT EXIST dir1\dir2 (&lt;br /&gt;
   echo mouting of share failed&lt;br /&gt;
   pause&lt;br /&gt;
   c:&lt;br /&gt;
   net use q: /delete&lt;br /&gt;
   EXIT /B 1&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
====for====&lt;br /&gt;
loop over values&lt;br /&gt;
 for %%L in (cn, de, en, es, fr, ko, sk, vn) do (&lt;br /&gt;
 echo %%L&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
loop over all dirs&lt;br /&gt;
 FOR /D %%D in (&amp;quot;*&amp;quot;) DO (&lt;br /&gt;
 echo %%D&lt;br /&gt;
 )&lt;br /&gt;
double loop over all dirs&lt;br /&gt;
 FOR /D %%P in (&amp;quot;*&amp;quot;) DO (&lt;br /&gt;
 echo parent = %%P&lt;br /&gt;
   FOR /D %%C in (&amp;quot;%%P\*&amp;quot;) DO (&lt;br /&gt;
   echo child = %%C&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
loop over files&lt;br /&gt;
 for %%F in (*.pdf) do (&lt;br /&gt;
 echo %%F&lt;br /&gt;
 copy %%F %%~nF.old&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
====for and grep.exe====&lt;br /&gt;
using UnixUtils grep.exe one can easily extract exceptions from logs:&lt;br /&gt;
 for %%F in (*.log) do (&lt;br /&gt;
   grep -B3 -A3 &amp;quot;exception&amp;quot; %%F &amp;gt; %%~nF-error.txt &lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
===Variables===&lt;br /&gt;
 :: no spaces around &#039;=&#039;!!!&lt;br /&gt;
 set xyz=myfile.bat&lt;br /&gt;
 set /p xyz=Variable Eingeben:&lt;br /&gt;
 set /p xyz= &amp;lt; TMP.dat &lt;br /&gt;
 echo %xyz%&lt;br /&gt;
&lt;br /&gt;
====Substrings====&lt;br /&gt;
Substring via &amp;quot;:~&amp;quot;&lt;br /&gt;
 set year=%date:~-4,4&lt;br /&gt;
Path, filename and extension of file stored in variable %%F&lt;br /&gt;
 set path=%%~pF&lt;br /&gt;
 set name=%%~nF&lt;br /&gt;
 set ext=%%~cF&lt;br /&gt;
&lt;br /&gt;
====Set Variable to Program Output====&lt;br /&gt;
 for /f &amp;quot;delims=&amp;quot; %%a in (&#039; powershell -c &amp;quot;$lastmonth = (Get-Date).addMonths(-3); $lastmonth.tostring(\&amp;quot;yyyy-MM\&amp;quot;)&amp;quot; &#039;) do set &amp;quot;MONTH=%%a&amp;quot;&lt;br /&gt;
 echo %MONTH%&lt;br /&gt;
&lt;br /&gt;
===Date &amp;amp; Time ===&lt;br /&gt;
====DateString====&lt;br /&gt;
 set DATESTR=%date:~-4,4%-%date:~-7,2%-%date:~-10,2%_%time:~0,2%:%time:~3,2%:%time:~6,2%&lt;br /&gt;
 or&lt;br /&gt;
 set DATESTR=%date:~-2,4%%date:~-7,2%%date:~-10,2%_%time:~0,2%%time:~3,2%%time:~6,2%&lt;br /&gt;
 :: replace &#039; &#039; in small hours with 0&lt;br /&gt;
 set DATESTR=%DATESTR: =0%&lt;br /&gt;
 = 240419_085724 &lt;br /&gt;
 zip.exe -9 %DATESTR%.zip *.bat&lt;br /&gt;
for better example of zipping see [[Backup#Zip_Folder|zip folder in Backup section]]&lt;br /&gt;
&lt;br /&gt;
====DayOfWeek====&lt;br /&gt;
from [https://stackoverflow.com/questions/25537313/issue-with-getting-batch-script-to-print-keeps-given-a-error-about-set-was-unexp here]&lt;br /&gt;
 SETLOCAL enabledelayedexpansion&lt;br /&gt;
 SET /a count=0&lt;br /&gt;
 FOR /F &amp;quot;skip=1&amp;quot; %%D IN (&#039;wmic path win32_localtime get dayofweek&#039;) DO (&lt;br /&gt;
     if &amp;quot;!count!&amp;quot; GTR &amp;quot;0&amp;quot; GOTO next&lt;br /&gt;
     set dow=%%D&lt;br /&gt;
     SET /a count+=1&lt;br /&gt;
 )&lt;br /&gt;
 :next&lt;br /&gt;
 echo %dow%&lt;br /&gt;
&lt;br /&gt;
====Yesterday====&lt;br /&gt;
from [https://stackoverflow.com/questions/2954359/dos-batch-programming-howto-get-and-display-yesterday-date here]&lt;br /&gt;
 set befehl=&amp;quot;PowerShell $date = Get-Date; $date=$date.AddDays(-1); $date.ToString(&#039;yyyy-MM-dd&#039;)&amp;quot;&lt;br /&gt;
 for /f %%i in (&#039;%befehl%&#039;) do set yesterday=%%i&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Copy all files from subfolders to one folder ===&lt;br /&gt;
from [https://stackoverflow.com/questions/11720681/windows-batch-copy-files-from-subfolders-to-one-folder here]&lt;br /&gt;
 set source=c:\source&lt;br /&gt;
 set target=c:\target&lt;br /&gt;
 cd %source%&lt;br /&gt;
 for /r %%a in (*.*) do (&lt;br /&gt;
  COPY &amp;quot;%%a&amp;quot; &amp;quot;%target%&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
===FTP===&lt;br /&gt;
====FTP-Upload====&lt;br /&gt;
file &amp;quot;ftp.bat&amp;quot;&lt;br /&gt;
 @echo off&lt;br /&gt;
 ftp &amp;quot;-s:FtpScript&amp;quot;&lt;br /&gt;
 pause&lt;br /&gt;
 cls&lt;br /&gt;
&lt;br /&gt;
file &amp;quot;FtpScript&amp;quot;&lt;br /&gt;
 open www.xyz.de&lt;br /&gt;
 [User]&lt;br /&gt;
 [Password]&lt;br /&gt;
 BINARY&lt;br /&gt;
 put [File]&lt;br /&gt;
 quit&lt;br /&gt;
&lt;br /&gt;
===UnixUtils===&lt;br /&gt;
Using [https://sourceforge.net/projects/unxutils/ UnixUtils] for Windows wget, grep, etc are usable to scripts in Windows. In the UnixUtils.zip the .exe files are located in foltder usr/local/wbin/ . Examples:&lt;br /&gt;
 head.exe -c 1000 mylog.log &amp;gt; outHead1000Bytes.log&lt;br /&gt;
 head.exe -n 1000 mylog.log &amp;gt; outHead1000Lines.log&lt;br /&gt;
 tail.exe -c 1000 mylog.log &amp;gt; outTail1000Bytes.log&lt;br /&gt;
 tail.exe -n 1000 mylog.log &amp;gt; outTail1000Lines.log&lt;br /&gt;
 grep.exe -i -B3 -A1 &amp;quot;ERROR&amp;quot; mylog.log &amp;gt; outGrepErrors.log&lt;br /&gt;
&lt;br /&gt;
====Curl to check if page online====&lt;br /&gt;
 @echo off&lt;br /&gt;
 set URL1=https://my.server.com&lt;br /&gt;
 :loop&lt;br /&gt;
 for /f &amp;quot;delims=&amp;quot; %%i in (&#039;curl -o nul -I -s -w &amp;quot;%%{http_code}&amp;quot; &amp;quot;%URL%&amp;quot;&#039;) do set HTTP_STATUS=%%i&lt;br /&gt;
 echo Status: %HTTP_STATUS% at %date% %time%&lt;br /&gt;
 timeout /t 5&lt;br /&gt;
 goto loop&lt;br /&gt;
&lt;br /&gt;
===TOP CPU RAM Disk I/O===&lt;br /&gt;
# CPU + RAM&lt;br /&gt;
 top&lt;br /&gt;
# I/O&lt;br /&gt;
 sudo iotop -o -d 2 -k&lt;br /&gt;
&lt;br /&gt;
===Run as other user===&lt;br /&gt;
 runas /user:MYDOMAIN\myuser /savecred  &amp;quot;cmd /K cd c:\mydir &amp;amp;&amp;amp; my_programm.exe&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 /K to keep window open when finished&lt;br /&gt;
 /C to close window when finished&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5410</id>
		<title>AI LLM for Coding</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5410"/>
		<updated>2026-04-16T10:24:03Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Tips */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Google Gemini==&lt;br /&gt;
pros: free to use with google account&lt;br /&gt;
create your API key at https://aistudio.google.com/app/api-keys&lt;br /&gt;
&lt;br /&gt;
Install&lt;br /&gt;
 npm install -g @google/gemini-cli@latest&lt;br /&gt;
 gemini&lt;br /&gt;
 # or just run&lt;br /&gt;
 npx https://github.com/google-gemini/gemini-cli&lt;br /&gt;
usage&lt;br /&gt;
 cd /tmp&lt;br /&gt;
 export GEMINI_API_KEY=&amp;quot;xxx&amp;quot;&lt;br /&gt;
 git clone https://github.com/entorb/meeting-meter&lt;br /&gt;
 cd myProject&lt;br /&gt;
 gemini&lt;br /&gt;
&lt;br /&gt;
to switch model from pro to flash:&lt;br /&gt;
 export GEMINI_MODEL=&amp;quot;gemini-2.5-flash&amp;quot;&lt;br /&gt;
&lt;br /&gt;
secret alternative: .env file&lt;br /&gt;
 GEMINI_API_KEY=xxx&lt;br /&gt;
&lt;br /&gt;
==Google Antigravity==&lt;br /&gt;
https://antigravity.google/download&lt;br /&gt;
&lt;br /&gt;
==Claude==&lt;br /&gt;
===Web===&lt;br /&gt;
https://claude.ai &amp;lt;br&amp;gt;&lt;br /&gt;
pro: Daily reset free quota&amp;lt;br&amp;gt;&lt;br /&gt;
cons: need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
===Console===&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: https://console.anthropic.com &amp;lt;br&amp;gt;&lt;br /&gt;
pro: fully integrated in code base&amp;lt;br&amp;gt;&lt;br /&gt;
cons: costs money, 5€ are easily spend&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
&lt;br /&gt;
==Tips==&lt;br /&gt;
* E2E tests (e.g. Cypress) are very important&lt;br /&gt;
* Use tools for code formatting, linting, type-hints&lt;br /&gt;
* Configure these tools to produce minimum output to reduce token consumption. e.g. for&lt;br /&gt;
** Prettier:  --log-level silent&lt;br /&gt;
** Cypress: cypress run --e2e --quiet&lt;br /&gt;
* For Refactoring/renaming: better do it manually, via vscode, that is a lot faster&lt;br /&gt;
&lt;br /&gt;
===Cavemen style===&lt;br /&gt;
https://github.com/JuliusBrussee/caveman&lt;br /&gt;
 npx skills add JuliusBrussee/caveman -a github-copilot&lt;br /&gt;
It has an option to install to local home dir instead of to project repo.&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Python&amp;diff=5409</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5409"/>
		<updated>2026-04-13T12:48:47Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Vulture: Search for dead code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Coding]][[Category:Python]]&lt;br /&gt;
&lt;br /&gt;
==Getting Started==&lt;br /&gt;
===Install===&lt;br /&gt;
====Python====&lt;br /&gt;
Best use [https://docs.astral.sh/uv/ UV] for managing python and package versions.&lt;br /&gt;
&lt;br /&gt;
* for Windows: get and install Python from https://www.python.org&lt;br /&gt;
* for MacOS: use [https://github.com/pyenv/pyenv?tab=readme-ov-file#a-getting-pyenv pyenv]&lt;br /&gt;
 brew install xz &lt;br /&gt;
 brew install pyenv&lt;br /&gt;
 &lt;br /&gt;
 echo &#039;export PYENV_ROOT=&amp;quot;$HOME/.pyenv&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 echo &#039;[ [ -d $PYENV_ROOT/bin ] ] &amp;amp;&amp;amp; export PATH=&amp;quot;$PYENV_ROOT/bin:$PATH&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 echo &#039;eval &amp;quot;$(pyenv init - zsh)&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 exec &amp;quot;$SHELL&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 pyenv install --list&lt;br /&gt;
 pyenv install 3.13.5&lt;br /&gt;
 pyenv global 3.13.5&lt;br /&gt;
 &lt;br /&gt;
 vim ~/.zshrc &lt;br /&gt;
 # add &lt;br /&gt;
 if command -v pyenv 1&amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then&lt;br /&gt;
   eval &amp;quot;$(pyenv init -)&amp;quot;&lt;br /&gt;
 fi&lt;br /&gt;
&lt;br /&gt;
====Editor: Visual Studio Code====&lt;br /&gt;
excellent and free source-code editor that supports many languages.&lt;br /&gt;
* get and install from https://code.visualstudio.com&lt;br /&gt;
&lt;br /&gt;
See Wickie page [[Visual_Studio_Code]] for general setup, extension, config...&lt;br /&gt;
&lt;br /&gt;
Python Extensions&lt;br /&gt;
* Python&lt;br /&gt;
* Pylance&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff Ruff Formater and Linter]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items/?itemName=tamasfe.even-better-toml Even Better TOML]&lt;br /&gt;
Settings (CTRL + ,)&lt;br /&gt;
* Extensions -&amp;gt; Python -&amp;gt; Formatting: Provider = black&lt;br /&gt;
* Text Editor -&amp;gt; Editor: Format On Save&lt;br /&gt;
* Text Editor -&amp;gt; Files:Eol -&amp;gt; \n&lt;br /&gt;
* Linter: Ruff&lt;br /&gt;
Settings in settings.json&lt;br /&gt;
 &amp;quot;python.analysis.completeFunctionParens&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.autoImportCompletions&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.inlayHints.functionReturnTypes&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.typeCheckingMode&amp;quot;: &amp;quot;strict&amp;quot;,&lt;br /&gt;
 // Ruff&lt;br /&gt;
 &amp;quot;[python]&amp;quot;: {&lt;br /&gt;
   &amp;quot;editor.defaultFormatter&amp;quot;: &amp;quot;charliermarsh.ruff&amp;quot;,&lt;br /&gt;
   &amp;quot;editor.formatOnSave&amp;quot;: true,&lt;br /&gt;
   &amp;quot;editor.codeActionsOnSave&amp;quot;: {&lt;br /&gt;
     &amp;quot;source.fixAll&amp;quot;: &amp;quot;explicit&amp;quot;,&lt;br /&gt;
     &amp;quot;source.organizeImports.ruff&amp;quot;: &amp;quot;explicit&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 },&lt;br /&gt;
&lt;br /&gt;
Run your code&lt;br /&gt;
 CTRL + F5 : run&lt;br /&gt;
 F5        : run in debugger&lt;br /&gt;
&lt;br /&gt;
====Code Formatter Ruff====&lt;br /&gt;
use software for handling the code formatting like &amp;quot;ruff&amp;quot; or &amp;quot;black&amp;quot;, where Ruff is much faster and additionally provides linting.&lt;br /&gt;
 pip install ruff&lt;br /&gt;
than activate in editor like vs code&lt;br /&gt;
&lt;br /&gt;
===My Template===&lt;br /&gt;
see latest version at https://github.com/entorb/template-python&lt;br /&gt;
&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Main file.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 # by Torben Menke https://entorb.net &lt;br /&gt;
 import logging&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 def init_logging() -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Initialize logging.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     logging.addLevelName(logging.DEBUG, &amp;quot;D&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.INFO, &amp;quot;I&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.WARNING, &amp;quot;W&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.ERROR, &amp;quot;E&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.CRITICAL, &amp;quot;C&amp;quot;)&lt;br /&gt;
     logging.basicConfig(&lt;br /&gt;
         level=logging.INFO, format=&amp;quot;%(levelname)s: %(name)s: %(message)s&amp;quot;&lt;br /&gt;
     )&lt;br /&gt;
     logging.getLogger(&amp;quot;httpx&amp;quot;).setLevel(logging.WARNING)&lt;br /&gt;
 &lt;br /&gt;
 init_logging()&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
 &lt;br /&gt;
 def main():&lt;br /&gt;
     LOGGER.info(&amp;quot;Moin Moin&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     main()&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Other file&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 import logging&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
&lt;br /&gt;
===Compile to .exe===&lt;br /&gt;
 pip install pyinstaller&lt;br /&gt;
 pyinstaller --onefile --console your.py&lt;br /&gt;
 # for Excel and Matplotlib these options are required&lt;br /&gt;
 --hidden-import=openpyxl --hidden-import=matplotlib --hidden-import pandas.plotting._matplotlib &lt;br /&gt;
([[Python - py2exe]] is deprecated)&lt;br /&gt;
&lt;br /&gt;
==Basics==&lt;br /&gt;
=== Naming Conventions===&lt;br /&gt;
[https://google.github.io/styleguide/pyguide.html Google Python Style Guide]:&lt;br /&gt;
 module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_CONSTANT_NAME, global_var_name, instance_var_name, function_parameter_name, local_var_name&lt;br /&gt;
&lt;br /&gt;
====Doc Strings====&lt;br /&gt;
It is best practice to start a file with a docstring:&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;My Script&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
This can be accessed linke this:&lt;br /&gt;
 title = __doc__[:-1]  # type: ignore&lt;br /&gt;
&lt;br /&gt;
===Installing packages===&lt;br /&gt;
 python -m pip install --upgrade pip&lt;br /&gt;
 &lt;br /&gt;
 pip install somemodule&lt;br /&gt;
 # or &lt;br /&gt;
 pip3 install somemodule&lt;br /&gt;
 # or read from file&lt;br /&gt;
 pip install -r requirements.txt&lt;br /&gt;
 &lt;br /&gt;
 # uninstall&lt;br /&gt;
 pip uninstall somemodule&lt;br /&gt;
 &lt;br /&gt;
 # using a web proxy&lt;br /&gt;
 # set proxy for windows cmd session&lt;br /&gt;
 SET HTTPS_PROXY=http://myProxy:8080&lt;br /&gt;
 (afterwards --proxy setting below no longer required&lt;br /&gt;
 or&lt;br /&gt;
 pip install --proxy http://myProxy:8080 somemodule&lt;br /&gt;
 &lt;br /&gt;
 # list outdated packages&lt;br /&gt;
 pip list --outdated&lt;br /&gt;
 &lt;br /&gt;
 # update package&lt;br /&gt;
 pip install --upgrade pyinstaller&lt;br /&gt;
 &lt;br /&gt;
 # updating all via Windows Powershell (from [https://thecesrom.dev/2021/05/26/the-one-liner-for-updating-pip-and-all-outdated-packages/])&lt;br /&gt;
 pip freeze | %{$_.split(&#039;==&#039;)[0]} | %{pip install --upgrade $_}&lt;br /&gt;
 &lt;br /&gt;
 # updating all via Bash (from [https://thecesrom.dev/2021/05/26/the-one-liner-for-updating-pip-and-all-outdated-packages/])&lt;br /&gt;
 pip freeze | grep -v &#039;^\-e&#039; | cut -d = -f 1  | xargs -n1 pip install --upgrade&lt;br /&gt;
 &lt;br /&gt;
 # downgrade&lt;br /&gt;
 pip install --upgrade pandas==1.2.4&lt;br /&gt;
&lt;br /&gt;
====Oneline Updater for requirements.txt====&lt;br /&gt;
https://pkgui.com/pip&lt;br /&gt;
&lt;br /&gt;
====update packages====&lt;br /&gt;
 pip install pip-review&lt;br /&gt;
 pip-review --auto&lt;br /&gt;
&lt;br /&gt;
====generate requirements.txt====&lt;br /&gt;
 pip install pipreqs&lt;br /&gt;
 pipreqs ./&lt;br /&gt;
&lt;br /&gt;
===Loops===&lt;br /&gt;
ATTENTION: The loops do not create a new variable scope. Only functions and modules introduce a new scope!&lt;br /&gt;
 for p in all_files:&lt;br /&gt;
     print(p)&lt;br /&gt;
 &lt;br /&gt;
 for i, p in enumerate(all_files):&lt;br /&gt;
     print(i, p)&lt;br /&gt;
 &lt;br /&gt;
 for i in range(10):&lt;br /&gt;
     print(i)&lt;br /&gt;
 # del i &lt;br /&gt;
 &lt;br /&gt;
 while i &amp;lt;= 100:&lt;br /&gt;
     i += 1&lt;br /&gt;
     ...&lt;br /&gt;
     if sth1:&lt;br /&gt;
         continue # start next loop&lt;br /&gt;
     if sth2:&lt;br /&gt;
         break # exit loop&lt;br /&gt;
 &lt;br /&gt;
 # inline if (requires a dummy else):&lt;br /&gt;
 print(&amp;quot;something&amp;quot;) if sth else 0&lt;br /&gt;
&lt;br /&gt;
===Math===&lt;br /&gt;
see [[Python - Math]] for linear regression&lt;br /&gt;
&lt;br /&gt;
Modulo&lt;br /&gt;
 15 % 4&lt;br /&gt;
 # &amp;gt; 3&lt;br /&gt;
&lt;br /&gt;
Integer Division&lt;br /&gt;
 17 // 4&lt;br /&gt;
 # &amp;gt; 4&lt;br /&gt;
&lt;br /&gt;
====Random====&lt;br /&gt;
 import random&lt;br /&gt;
 random.randint(1000000, 9999999)&lt;br /&gt;
&lt;br /&gt;
==Basic Objects==&lt;br /&gt;
===Variables===&lt;br /&gt;
 del var  # delete / undef a variable&lt;br /&gt;
 var = None  # sets to null&lt;br /&gt;
 &lt;br /&gt;
 # check if variable is defined&lt;br /&gt;
 if &amp;quot;var&amp;quot; in locals():&lt;br /&gt;
     pass&lt;br /&gt;
 # for object oriented projects:&lt;br /&gt;
 if &amp;quot;var&amp;quot; in self.__dict__:&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # Access global variables in functions&lt;br /&gt;
 var = 123&lt;br /&gt;
 &lt;br /&gt;
 def test() -&amp;gt; None:&lt;br /&gt;
     global var  # point to global instead of creation of local var&lt;br /&gt;
     var = 321&lt;br /&gt;
&lt;br /&gt;
===Strings===&lt;br /&gt;
 # num &amp;lt;-&amp;gt; str&lt;br /&gt;
 s = str(i)  # int to string&lt;br /&gt;
 f = float(s)  # str -&amp;gt; float&lt;br /&gt;
 i = int(s)&lt;br /&gt;
 str(round(f, 1))  # round first&lt;br /&gt;
 # tests&lt;br /&gt;
 s.isdigit()  # 0-9&lt;br /&gt;
 # note isdecimal() does also not match &#039;1.1&#039;&lt;br /&gt;
 &lt;br /&gt;
 # printf: 1 digit&lt;br /&gt;
 s = f&amp;quot;{value:0.1f}&amp;quot;&lt;br /&gt;
 s = &amp;quot;%0.1f&amp;quot; % value&lt;br /&gt;
&lt;br /&gt;
====Modify Strings==== &lt;br /&gt;
 # get string from prompt&lt;br /&gt;
 s = input(&amp;quot;Enter Text: &amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 s = s.strip()  # trim spaces from both sides, rstrip for right only&lt;br /&gt;
 s = s.lower()  # lower case&lt;br /&gt;
 s = s.upper()  # upper case&lt;br /&gt;
 s = s.title()  # upper case for first char of word&lt;br /&gt;
 &lt;br /&gt;
 # upper case first letter of each word and also removes multiple and trailing spaces&lt;br /&gt;
 import string&lt;br /&gt;
 s = string.capwords(s)&lt;br /&gt;
 &lt;br /&gt;
 # replace&lt;br /&gt;
 s.replace(x, y)&lt;br /&gt;
 &lt;br /&gt;
 # trim whitespaces from left and right&lt;br /&gt;
 s.strip()&lt;br /&gt;
 &lt;br /&gt;
 # replace all (multiple) whitespaces by single space &#039; &#039;&lt;br /&gt;
 s = &amp;quot; &amp;quot;.join(s.split())&lt;br /&gt;
 &lt;br /&gt;
 # generate key value pairs from dict&lt;br /&gt;
 # key1=value1&amp;amp;key2=value2&lt;br /&gt;
 param_str = &amp;quot;&amp;amp;&amp;quot;.join(&amp;quot;=&amp;quot;.join(tup) for tup in dict.items())&lt;br /&gt;
 &lt;br /&gt;
 # repeat string multiple times&lt;br /&gt;
 s * 5  # = s+s+s+s+s&lt;br /&gt;
&lt;br /&gt;
====Substrings====&lt;br /&gt;
 # find a substring:&lt;br /&gt;
 if x in s:  # True / False&lt;br /&gt;
     pass&lt;br /&gt;
 if len(s) &amp;gt; 0:&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # handling substrings&lt;br /&gt;
 a = &amp;quot;abcd&amp;quot;&lt;br /&gt;
 b = a[:1] + &amp;quot;o&amp;quot; + a[2:]&lt;br /&gt;
 # &amp;gt; &#039;aocd&#039;&lt;br /&gt;
 &lt;br /&gt;
 s = &amp;quot;Hello there !bob@&amp;quot;&lt;br /&gt;
 i1 = s.find(&amp;quot;!&amp;quot;) + 1&lt;br /&gt;
 i2 = s.find(&amp;quot;@&amp;quot;)&lt;br /&gt;
 substr = s[i1:i2]&lt;br /&gt;
 &lt;br /&gt;
 def substr_between(s: str, s1: str, s2: str) -&amp;gt; str:&lt;br /&gt;
     assert s1 in s, f&amp;quot;E: can&#039;t find &#039;{s1}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     assert s2 in s, f&amp;quot;E: can&#039;t find &#039;{s1}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     i1 = s.find(s1) + len(s1)&lt;br /&gt;
     i2 = s.find(s2)&lt;br /&gt;
     assert i1 &amp;lt; i2, f&amp;quot;E: &#039;{s1}&#039; not before &#039;{s2}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     return s[i1:i2]&lt;br /&gt;
&lt;br /&gt;
====Binary, raw strings, html encoding====&lt;br /&gt;
 # Binary Strings&lt;br /&gt;
 sb = b&amp;quot;asdf&amp;quot;&lt;br /&gt;
 # or&lt;br /&gt;
 sb = str.encode(&amp;quot;asdf&amp;quot;)&lt;br /&gt;
 s = sb.decode(&amp;quot;ascii&amp;quot;)  # decode binary strings&lt;br /&gt;
 sb = s.encode(&amp;quot;ascii&amp;quot;)  # encode string to binary&lt;br /&gt;
 &lt;br /&gt;
 # raw string&lt;br /&gt;
 s = r&amp;quot;c:\Windows&amp;quot;  # no escape of \  needed&lt;br /&gt;
 &lt;br /&gt;
 # convert utf-8 to html umlaute&lt;br /&gt;
 s = &amp;quot;Nürnberg&amp;quot;.encode(&amp;quot;ascii&amp;quot;, &amp;quot;xmlcharrefreplace&amp;quot;).decode()&lt;br /&gt;
 # -&amp;gt; N&amp;amp;#252;rnberg&lt;br /&gt;
&lt;br /&gt;
====Guess encoding via chardet====&lt;br /&gt;
 import chardet  # pip install chardet&lt;br /&gt;
 result = chardet.detect(raw_data)&lt;br /&gt;
 if result[&amp;quot;encoding&amp;quot;] and result[&amp;quot;confidence&amp;quot;] &amp;gt; 0.5:  # noqa: PLR2004&lt;br /&gt;
     encoding = result[&amp;quot;encoding&amp;quot;]&lt;br /&gt;
 else:&lt;br /&gt;
     encoding = &amp;quot;utf-8&amp;quot;&lt;br /&gt;
&lt;br /&gt;
there is a much faster alternative [https://github.com/PyYoshi/cChardet cchardet], but that requires Microsoft Visual C++ 14.0&lt;br /&gt;
&lt;br /&gt;
====Merge variables in string / sprintf====&lt;br /&gt;
 print(&amp;quot;Renner =&amp;quot;, i)&lt;br /&gt;
 print(&amp;quot;Renner = %3d&amp;quot; % i)  # leading 0&#039;s&lt;br /&gt;
 print(f&amp;quot;Renner = {i}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # place formatted numbers in a string / sprintf&lt;br /&gt;
 s = &amp;quot;The {:.1f}% {} cost {:05.2f} euros&amp;quot;.format( 5.1, &amp;quot;beer&amp;quot;, 3.50)&lt;br /&gt;
 print(s)&lt;br /&gt;
 # &amp;gt; The 5.1% beer cost 03.50 euros&lt;br /&gt;
 &lt;br /&gt;
 s = f&amp;quot;The length is {72.8958:.2f} meters&amp;quot;&lt;br /&gt;
 # &amp;gt; The length is 72.90 meters&lt;br /&gt;
&lt;br /&gt;
===Lists===&lt;br /&gt;
like @array in Perl&lt;br /&gt;
 lst = [1, 2, 3, 4, 5, 6]&lt;br /&gt;
 lst = [x / 2 for x in range(10)]&lt;br /&gt;
 lst = [None] * 10 # Initiate list of None elements:&lt;br /&gt;
 len(lst) # length&lt;br /&gt;
 lst2 = lst[0:10]  # get elements 0-10&lt;br /&gt;
 for i in lst:&lt;br /&gt;
     print(i)&lt;br /&gt;
&lt;br /&gt;
====clone list====&lt;br /&gt;
 # dangerous: creates a link to the original list&lt;br /&gt;
 list_2 = my_list  # M&#039;s elements are LINKS to L&#039;s&lt;br /&gt;
 # clones can be achieved via:&lt;br /&gt;
 list_2 = my_list.copy&lt;br /&gt;
 list_2 = my_list[:]&lt;br /&gt;
 list_2 = list(my_list)&lt;br /&gt;
&lt;br /&gt;
====list modifications====&lt;br /&gt;
 lst.pop()  # returns and removes the last item&lt;br /&gt;
 lst.pop(i)  # returns and removes the item at  position int i&lt;br /&gt;
 lst.insert(i, x)  # insert item x at position int i&lt;br /&gt;
 lst.remove(x)  # removes the first occurrence of  item x&lt;br /&gt;
 lst.append(x)  # append a single element&lt;br /&gt;
 lst.extend(m)  # append elements of another list&lt;br /&gt;
 &lt;br /&gt;
 lst.reverse()  # reverse the order of the list&lt;br /&gt;
 lst = sorted([&amp;quot;B&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;c&amp;quot;], key=str.casefold)  #  case insensitive / ignore case&lt;br /&gt;
 &lt;br /&gt;
 # list to string&lt;br /&gt;
 s = &amp;quot;&amp;quot;.join(lst)&lt;br /&gt;
 s = &amp;quot;\n&amp;quot;.join(lst)&lt;br /&gt;
 # string to list&lt;br /&gt;
 lst = s.split()  # default: split on space&lt;br /&gt;
 lst = s.split(&amp;quot;,&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # remove empty values from end&lt;br /&gt;
 while L[-1] == &amp;quot;&amp;quot;:&lt;br /&gt;
     L.pop()&lt;br /&gt;
 &lt;br /&gt;
 # check and remove items from list&lt;br /&gt;
  # from https://stackoverflow.com/a/6024599&lt;br /&gt;
  # iterates in-situ via reversed index&lt;br /&gt;
 for i in range(len(lst) - 1, -1, -1):&lt;br /&gt;
     element = lst[i]&lt;br /&gt;
     if check(element):&lt;br /&gt;
         del lst[i]&lt;br /&gt;
&lt;br /&gt;
====check if item is in list====&lt;br /&gt;
 x in lst&lt;br /&gt;
 x not in lst&lt;br /&gt;
 lst.count(x)  # how many items x are in the list&lt;br /&gt;
 i = lst.index(&amp;quot;word&amp;quot;)  # find in list / returns the  position of the first match in list&lt;br /&gt;
&lt;br /&gt;
====pair 2 lists====&lt;br /&gt;
 # zip: merge 2 lists to list of tuples&lt;br /&gt;
 data = list(zip(data_x, data_y, strict=True))&lt;br /&gt;
 &lt;br /&gt;
 # unzip: split list of pairs into 2 lists&lt;br /&gt;
 data_x, data_y = zip(*data, strict=True)&lt;br /&gt;
 &lt;br /&gt;
 # Cartesian product of lists / tuples&lt;br /&gt;
 import itertools&lt;br /&gt;
 for i in itertools.product(*list_of_lists):&lt;br /&gt;
     print(i)&lt;br /&gt;
&lt;br /&gt;
====sort multidim list====&lt;br /&gt;
 lst = sorted(lst, key=lambda x: x[0], reverse=False)&lt;br /&gt;
 lst = sorted(lst, key=lambda row: (row[&amp;quot;Wann&amp;quot;], row [&amp;quot;Wer&amp;quot;]), reverse=False)&lt;br /&gt;
&lt;br /&gt;
====filter list====&lt;br /&gt;
 lines = [x for x in lines if not x.startswith (&amp;quot;word&amp;quot;)]&lt;br /&gt;
 lst = [&amp;quot;asdf&amp;quot;, &amp;quot;asdf2&amp;quot;, &amp;quot;qwertz&amp;quot;]&lt;br /&gt;
 lst = [elem for elem in lst if &amp;quot;asdf&amp;quot; in elem]&lt;br /&gt;
&lt;br /&gt;
====for each element====&lt;br /&gt;
 # trim/strip spaces for each element&lt;br /&gt;
 lines = [s.strip() for s in lines]&lt;br /&gt;
 # modify each item in list by adding constant string&lt;br /&gt;
 l = [s + &#039;;&#039; + v for v in l]&lt;br /&gt;
  &lt;br /&gt;
 # modify item in list&lt;br /&gt;
 for idx, line in enumerate(cont):&lt;br /&gt;
     if &amp;quot;K1001/1&amp;quot; in line:&lt;br /&gt;
         line = &amp;quot;K1001/1 Test Nr &amp;quot; + str(i) + &amp;quot;\n&amp;quot;&lt;br /&gt;
         cont[idx] = line&lt;br /&gt;
         break&lt;br /&gt;
&lt;br /&gt;
===Tuples===&lt;br /&gt;
Ordered sequence, with no ability to replace or delete items&lt;br /&gt;
 tpl = (1,2,3,4,5,6)&lt;br /&gt;
&lt;br /&gt;
list -&amp;gt; tuple&lt;br /&gt;
 tpl = tuple(lst)&lt;br /&gt;
&lt;br /&gt;
combine 2 tuples&lt;br /&gt;
 tpl = tpl1 + tpl2&lt;br /&gt;
&lt;br /&gt;
===Dictionaries===&lt;br /&gt;
like %hash in Perl&lt;br /&gt;
 d = {&amp;quot;key1&amp;quot;: &amp;quot;value1&amp;quot;, &amp;quot;key2&amp;quot;: 123}&lt;br /&gt;
 d[&amp;quot;key3&amp;quot;] = (1, 2, 3)&lt;br /&gt;
 del d[&amp;quot;key3&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
 len(d)&lt;br /&gt;
 d.clear()&lt;br /&gt;
 d.copy()&lt;br /&gt;
 d.keys()&lt;br /&gt;
 d.values()&lt;br /&gt;
 d.items()  # returns a list of tuples (key, value)&lt;br /&gt;
 d.get(k)  # returns value of key k&lt;br /&gt;
 d.get(k, x)  # returns value of key k; if k is not  in d it returns x&lt;br /&gt;
 count = d.get(&amp;quot;key&amp;quot;, 0) + 1 # nice for counters&lt;br /&gt;
 d.pop(k)  # returns and removes item k&lt;br /&gt;
 d.pop(k, x)  # returns and removes item k; if k is  not in d it returns x&lt;br /&gt;
 # checks&lt;br /&gt;
 x in d&lt;br /&gt;
 x not in d&lt;br /&gt;
 &lt;br /&gt;
 # dict can have tuple as key&lt;br /&gt;
 tpl = (&amp;quot;key1&amp;quot;, &amp;quot;key2&amp;quot;, 123)&lt;br /&gt;
 d[tpl] = 123456&lt;br /&gt;
 &lt;br /&gt;
 # loop over all key-value pairs&lt;br /&gt;
 for key, value in d.items():&lt;br /&gt;
     print(f&amp;quot;{key} = {value}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # sorted keys:&lt;br /&gt;
 for key in sorted(dict.keys()):&lt;br /&gt;
     pass&lt;br /&gt;
 # sorted values, reversed&lt;br /&gt;
 for key, value in sorted(d.items(), key=lambda item:  item[1], reverse=True):&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # join / merge 2 dicts&lt;br /&gt;
 d.update(d2)&lt;br /&gt;
 &lt;br /&gt;
 # flatten dict of dict&lt;br /&gt;
 flat = d.copy()&lt;br /&gt;
 meta = d.pop(&amp;quot;metadata&amp;quot;)&lt;br /&gt;
 flat.update(meta)&lt;br /&gt;
&lt;br /&gt;
====MultiDim Dictionaries====&lt;br /&gt;
 d = {}&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;] = {}&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;][&amp;quot;value&amp;quot;] = 1.909e18&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;][&amp;quot;name&amp;quot;] = &amp;quot;Item 1&amp;quot;&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;] = {}&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;][&amp;quot;value&amp;quot;] = 1.725e18&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;][&amp;quot;name&amp;quot;] = &amp;quot;Item 2&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 for k in d.keys():&lt;br /&gt;
     d[k][&amp;quot;value&amp;quot;] = d[k][&amp;quot;value&amp;quot;] * 1.1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def find_common_strings(dict1: dict, dict2: dict) -&amp;gt; dict[Any, Any]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Find common strings in two dict, returning a new dict of those strings.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     def recursive_match(d1: dict, d2: dict) -&amp;gt; dict[Any, Any]:&lt;br /&gt;
         common_dict = {}&lt;br /&gt;
         for key in d1:  # noqa: PLC0206&lt;br /&gt;
             if key in d2:&lt;br /&gt;
                 if isinstance(d1[key], dict) and isinstance(d2[key], dict):&lt;br /&gt;
                     # Recurse if both values are dicts&lt;br /&gt;
                     nested_common = recursive_match(d1[key], d2[key])&lt;br /&gt;
                     if nested_common:  # Add to result if common values are found&lt;br /&gt;
                         common_dict[key] = nested_common&lt;br /&gt;
                 elif isinstance(d1[key], str) and isinstance(d2[key], str):&lt;br /&gt;
                     # Check if both are strings and identical&lt;br /&gt;
                     if d1[key] == d2[key]:&lt;br /&gt;
                         common_dict[key] = d1[key]&lt;br /&gt;
         return common_dict&lt;br /&gt;
 &lt;br /&gt;
     return recursive_match(dict1, dict2)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def update_dict_with_new_values(original_dict: dict, new_values: dict) -&amp;gt; dict:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Update strings in the original dict with new values from the new_values dict.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     def recursive_update(d1: dict, new_vals: dict) -&amp;gt; None:&lt;br /&gt;
         for key in new_vals:  # noqa: PLC0206&lt;br /&gt;
             if key in d1:&lt;br /&gt;
                 if isinstance(d1[key], dict) and isinstance(new_vals[key], dict):&lt;br /&gt;
                     # Recurse if both values are dicts&lt;br /&gt;
                     recursive_update(d1[key], new_vals[key])&lt;br /&gt;
                 elif isinstance(d1[key], str) and isinstance(new_vals[key], str):&lt;br /&gt;
                     # Update the string&lt;br /&gt;
                     d1[key] = new_vals[key]&lt;br /&gt;
 &lt;br /&gt;
     recursive_update(original_dict, new_values)&lt;br /&gt;
     return original_dict&lt;br /&gt;
&lt;br /&gt;
===Datetime, Date and Time===&lt;br /&gt;
 import datetime as dt&lt;br /&gt;
 from zoneinfo import ZoneInfo&lt;br /&gt;
 TZ_DE = ZoneInfo(&amp;quot;Europe/Berlin&amp;quot;)&lt;br /&gt;
 TZ_UTC = ZoneInfo(&amp;quot;UTC&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
====Create====&lt;br /&gt;
 # date&lt;br /&gt;
 date = dt.date(2023, 12, 31)&lt;br /&gt;
 date = dt.date.fromisocalendar(int(year), int(week),  int(daynum))  # daynum: 1..7&lt;br /&gt;
 date = dt.date.fromisoformat(&amp;quot;2020-03-10&amp;quot;)&lt;br /&gt;
 date = dt.datetime.strptime(datestr, &amp;quot;%y%m%d&amp;quot;).date()&lt;br /&gt;
 date_today = dt.datetime.now(tz=dt.UTC).date()&lt;br /&gt;
 date_yesterday = dt.datetime.now(tz=TZ_DE).date() -  dt.timedelta(days=1)&lt;br /&gt;
 days_overdue = (date_completed - date_due).days&lt;br /&gt;
 # to add one month (which is not supported by timedelta) &lt;br /&gt;
 from dateutil.relativedelta import relativedelta&lt;br /&gt;
 date_end = date_start + relativedelta(months=1)&lt;br /&gt;
 delta_days = 1 + (end - start).days&lt;br /&gt;
 &lt;br /&gt;
 # datetime&lt;br /&gt;
 dt_now = dt.datetime.now(tz=TZ_DE)&lt;br /&gt;
 my_dt = dt.datetime(2023, 12, 31, 14, 31, 56,  tzinfo=TZ_DE)  # 2023-12-31 14:31:56&lt;br /&gt;
 my_dt = dt.datetime.fromtimestamp(my_timestamp,  tz=TZ_DE)&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat (&amp;quot;2017-01-01T12:30:59.000000&amp;quot;)&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(&amp;quot;2020-03-10  06:01:01+00:00&amp;quot;)&lt;br /&gt;
 s = &amp;quot;2020-03-10T06:01:01Z&amp;quot;&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(s.replace(&amp;quot;Z&amp;quot;, &amp;quot; +00:00&amp;quot;))&lt;br /&gt;
 my_date_local = my_dt.astimezone(tz=TZ_DE).date()&lt;br /&gt;
 &lt;br /&gt;
 # datetime -&amp;gt; date&lt;br /&gt;
 my_date = my_dt.date()&lt;br /&gt;
 # date -&amp;gt; datetime&lt;br /&gt;
 my_date = dt.date(2023, 12, 31)&lt;br /&gt;
 my_dt = dt.datetime(my_date.year, my_date.month,  my_date.day, tzinfo=TZ_DE)&lt;br /&gt;
 &lt;br /&gt;
 # to string&lt;br /&gt;
 date_str = my_dt.strftime(&amp;quot;%y%m%d&amp;quot;)&lt;br /&gt;
 date_str = my_dt.strftime(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;)&lt;br /&gt;
 # alternative using time&lt;br /&gt;
 date_str = time.strftime(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;)&lt;br /&gt;
 # now in UTC without milliseconds&lt;br /&gt;
 date_str = dt.datetime.now(tz=dt.timezone.utc).replace(microsecond=0).isoformat() + &amp;quot;Z&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # remove timezone&lt;br /&gt;
 my_dt = my_dt.replace(tzinfo=None)&lt;br /&gt;
 &lt;br /&gt;
 # German format&lt;br /&gt;
 import locale&lt;br /&gt;
 locale.setlocale(locale.LC_ALL, &amp;quot;de_DE&amp;quot;)&lt;br /&gt;
 print(my_date.strftime(&amp;quot;%a %x&amp;quot;))  # Mo 25.12.2023&lt;br /&gt;
 &lt;br /&gt;
 # Calendar week&lt;br /&gt;
 week = my_date.isocalendar()[1]&lt;br /&gt;
 print(&amp;quot;KW%02d&amp;quot; % week)&lt;br /&gt;
 &lt;br /&gt;
 # first day of quarter&lt;br /&gt;
 def get_first_day_of_the_quarter(my_date: dt.date) -&amp;gt; dt.date:&lt;br /&gt;
     return dt.date(my_date.year, 1 + 3 * ((my_date.month - 1) // 3), 1)&lt;br /&gt;
&lt;br /&gt;
====rounding datetimes to minutes====&lt;br /&gt;
 def floor_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Floor (=round down) minutes to X min  resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     min_new = res * (my_dt.minute // res)&lt;br /&gt;
     return my_dt.replace(minute=min_new, second=0,  microsecond=0)&lt;br /&gt;
 &lt;br /&gt;
 def ceil_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Ceil (=round up) minutes to X min resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    min_new = res * (1 + my_dt.minute // res)&lt;br /&gt;
    return my_dt.replace(minute=0, second=0, microsecond=0) + dt.timedelta(&lt;br /&gt;
        minutes=min_new&lt;br /&gt;
    )&lt;br /&gt;
 &lt;br /&gt;
 def round_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Cound minutes to X min resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    min_old_dec = float(my_dt.minute) + float(my_dt.second * 60)&lt;br /&gt;
    min_new = res * round(min_old_dec / res)&lt;br /&gt;
    return my_dt.replace(minute=0, second=0, microsecond=0) + dt.timedelta(&lt;br /&gt;
        minutes=min_new&lt;br /&gt;
    )&lt;br /&gt;
 &lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(&amp;quot;2011-11-04  00:05:23+00:00&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;original: {my_dt}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;floored: {floor_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;ceileded: {ceil_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;rounded: {round_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
====Timing / Time elapsed====&lt;br /&gt;
 import time &lt;br /&gt;
 timestart = time.time()&lt;br /&gt;
 sec = time.time() - timestart&lt;br /&gt;
 print(f&amp;quot;Time elapsed: {sec:.2f} sec&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==OS, Argpars, etc.==&lt;br /&gt;
===Hostname===&lt;br /&gt;
 from socket import gethostname&lt;br /&gt;
 print(gethostname())&lt;br /&gt;
&lt;br /&gt;
===Checking Operating System===&lt;br /&gt;
 import os&lt;br /&gt;
 import sys&lt;br /&gt;
  &lt;br /&gt;
 if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
     print(&amp;quot;posix/Unix/Linux&amp;quot;)&lt;br /&gt;
 elif os.name == &amp;quot;nt&amp;quot;:&lt;br /&gt;
     print(&amp;quot;windows&amp;quot;)&lt;br /&gt;
 else:&lt;br /&gt;
     print(&amp;quot;unknown os&amp;quot;)&lt;br /&gt;
     sys.exit(1)  # throws exception, use quit() to   close / die silently&lt;br /&gt;
&lt;br /&gt;
===Get filename of python script===&lt;br /&gt;
 my_file_path = __file__&lt;br /&gt;
&lt;br /&gt;
alternative using sys package&lt;br /&gt;
 from sys import argv&lt;br /&gt;
 myFilename = argv[0]&lt;br /&gt;
&lt;br /&gt;
===accessing os envrionment variables===&lt;br /&gt;
 import os&lt;br /&gt;
 print(os.getenv(&amp;quot;tmp&amp;quot;))&lt;br /&gt;
 # better:&lt;br /&gt;
 try:&lt;br /&gt;
     s = os.environ[key] # throws error if unset&lt;br /&gt;
 except KeyError:&lt;br /&gt;
     error_message(f&amp;quot;Environment variable {key} not set.&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Command Line Arguments===&lt;br /&gt;
====ArgumentParser====&lt;br /&gt;
 import argparse&lt;br /&gt;
 &lt;br /&gt;
 parser = (&lt;br /&gt;
     argparse.ArgumentParser()&lt;br /&gt;
 )  # construct the argument parser and parse the  arguments&lt;br /&gt;
 # -h comes automatically&lt;br /&gt;
 &lt;br /&gt;
 # Boolean Parameter&lt;br /&gt;
 parser.add_argument(&lt;br /&gt;
     &amp;quot;-v&amp;quot;, &amp;quot;--verbose&amp;quot;, help=&amp;quot;increase output  verbosity&amp;quot;, action=&amp;quot;store_true&amp;quot;&lt;br /&gt;
 )  # store_true -&amp;gt; Boolean Value&lt;br /&gt;
 &lt;br /&gt;
 # Choice Parameter&lt;br /&gt;
 # restrict to a list of possible values / choices&lt;br /&gt;
 # parser.add_argument(&amp;quot;--choice&amp;quot;, type=int, choices= [0, 1, 2], help=&amp;quot;Test choices&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Positional Parameter (like text.py 123)&lt;br /&gt;
 # parser.add_argument(&amp;quot;num&amp;quot;, type=int, help=&amp;quot;Number  of things&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Required Parameter&lt;br /&gt;
 # parser.add_argument(&amp;quot;-i&amp;quot;, &amp;quot;--input&amp;quot;, type=str,  required=True, help=&amp;quot;Path of file&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Optional Parameter&lt;br /&gt;
 parser.add_argument(&amp;quot;-n&amp;quot;, &amp;quot;--number&amp;quot;, type=int,  help=&amp;quot;Number of clicks&amp;quot;)&lt;br /&gt;
 # Optional Parameter with Default&lt;br /&gt;
 parser.add_argument(&lt;br /&gt;
     &amp;quot;-s&amp;quot;,&lt;br /&gt;
     &amp;quot;--seconds&amp;quot;,&lt;br /&gt;
     type=int,&lt;br /&gt;
     default=sec_default,&lt;br /&gt;
     help=&amp;quot;Duration of clicking, default = %i (sec)&amp;quot;  % sec_default,&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 args = vars(parser.parse_args())&lt;br /&gt;
 &lt;br /&gt;
 if args[&amp;quot;verbose&amp;quot;]:&lt;br /&gt;
     pass  # do nothing&lt;br /&gt;
 # print (&amp;quot;verbosity turned on&amp;quot;)&lt;br /&gt;
 if args[&amp;quot;number&amp;quot;]:&lt;br /&gt;
     print(&amp;quot;num=%i&amp;quot; % args[&amp;quot;number&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
==== match case statement ====&lt;br /&gt;
(new in python 3.10)&lt;br /&gt;
 match args:&lt;br /&gt;
   case {&amp;quot;sap&amp;quot;: &amp;quot;prod&amp;quot;, &amp;quot;version&amp;quot;: 1}:&lt;br /&gt;
     ...&lt;br /&gt;
   case {&amp;quot;sap&amp;quot;: &amp;quot;prod&amp;quot;, &amp;quot;version&amp;quot;: 2}:&lt;br /&gt;
     ...&lt;br /&gt;
   case _: # default case&lt;br /&gt;
&lt;br /&gt;
==File Access==&lt;br /&gt;
===Pathlib===&lt;br /&gt;
for migrating see table [https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module]&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 p = Path(&amp;quot;dir/test.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 log_file = Path(__file__).with_suffix(&amp;quot;.log&amp;quot;)  # __file__ is name of python script&lt;br /&gt;
 &lt;br /&gt;
 my_fname = p.name  #  alternative to basename&lt;br /&gt;
 my_ext = p.suffix&lt;br /&gt;
 (filepath_without_ext, file_ext) = (p.stem, p.suffix)&lt;br /&gt;
 my_dir = p.parent&lt;br /&gt;
 my_parent_dir = p.parents[1]&lt;br /&gt;
 p2 = p.with_suffix(&amp;quot;.json&amp;quot;)&lt;br /&gt;
 p3 = p.parent / (p.name + &amp;quot;-autofix.tex&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # checks&lt;br /&gt;
 Path(my_file_str).exists()&lt;br /&gt;
 Path(my_file_str).is_file()&lt;br /&gt;
 Path(my_file_str).is_dir()&lt;br /&gt;
 &lt;br /&gt;
 # loop over glob of files matching wildcard&lt;br /&gt;
 for file_out in Path(&amp;quot;mydir&amp;quot;).glob(&amp;quot;*-autofix.tex&amp;quot;):&lt;br /&gt;
 &lt;br /&gt;
 # loop over dirs&lt;br /&gt;
 p = Path() # cwd&lt;br /&gt;
 list_of_repos = [x for x in p.iterdir() if x.is_dir() and (x / &amp;quot;.git&amp;quot;).is_dir()]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
alternative using old os functions&lt;br /&gt;
Split path into folder, filename, ext&lt;br /&gt;
 import os&lt;br /&gt;
 (dirName, fileName) = os.path.split(f)&lt;br /&gt;
 (fileBaseName, fileExtension) = os.path.splitext(fileName)&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-out.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===File Manipulations: copy, move, delete, touch===&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 # copy&lt;br /&gt;
 from shutil import copyfile&lt;br /&gt;
 copyfile(Path(&amp;quot;file-source.txt&amp;quot;), Path(&amp;quot;dir&amp;quot;) / Path(&amp;quot;file-target.txt&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # move / rename&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).replace(Path(&amp;quot;file2.txt&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # delete&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).unlink()&lt;br /&gt;
 &lt;br /&gt;
 # touch&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).touch()&lt;br /&gt;
&lt;br /&gt;
===File size and timestamp===&lt;br /&gt;
 # size&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).stat().st_size&lt;br /&gt;
 &lt;br /&gt;
 # timestamp last modified&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).stat().st_mtime&lt;br /&gt;
&lt;br /&gt;
===Dir create/mkdir and delete===&lt;br /&gt;
 # create dir&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 Path(&amp;quot;myDir1/myDir2&amp;quot;).mkdir(parents=True, exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
 # delete dir&lt;br /&gt;
 import shutil&lt;br /&gt;
 shutil.rmtree(d)&lt;br /&gt;
&lt;br /&gt;
===Loop over Directories===&lt;br /&gt;
====Fetch Dir Contents / Loop over Files====&lt;br /&gt;
glob via pathlib&lt;br /&gt;
 for fileOut in Path(&amp;quot;mydir&amp;quot;).glob(&amp;quot;*-autofix.tex&amp;quot;):&lt;br /&gt;
&lt;br /&gt;
Get list of files in directory, filter dirs from list, filter by ext&lt;br /&gt;
 dir= &amp;quot;/path/to/some/dir&amp;quot;&lt;br /&gt;
 listoffiles = [ f for f in os.listdir(dir) if os.path.isfile(os.path.join(dir ,f)) and f.lower()[-4:] == &amp;quot;.gpx&amp;quot;]&lt;br /&gt;
 listoffiles.sort()&lt;br /&gt;
&lt;br /&gt;
====Traverse in Subdirs====&lt;br /&gt;
 # walk into path an fetch all files matching extension jpe?g&lt;br /&gt;
 files = []&lt;br /&gt;
 for (dirpath, dirnames, filenames) in os.walk(&amp;quot;.&amp;quot;):&lt;br /&gt;
     dirpath = dirpath.replace(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;)&lt;br /&gt;
     for file in filenames:&lt;br /&gt;
         if file.endswith(&amp;quot;.txt&amp;quot;):&lt;br /&gt;
             files.append(dirpath + &amp;quot;/&amp;quot; + file)&lt;br /&gt;
         elif re.search(r&amp;quot;\.jpe?g$&amp;quot;, file, re.IGNORECASE):&lt;br /&gt;
             files.append(dirpath + &amp;quot;/&amp;quot; + file)&lt;br /&gt;
&lt;br /&gt;
==File Parsing==&lt;br /&gt;
===File General===&lt;br /&gt;
====File Read====&lt;br /&gt;
 path_file_in = Path(&amp;quot;file.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # via pathlib&lt;br /&gt;
 cont = path_file_in.read_text(encoding=&amp;quot;utf-8&amp;quot;) # note: utf-8-sig for UTF-8-BOM&lt;br /&gt;
 &lt;br /&gt;
 # general&lt;br /&gt;
 with path_file_in.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
     cont = fh.read()&lt;br /&gt;
     # or&lt;br /&gt;
     lst = fh.readlines()&lt;br /&gt;
     # or&lt;br /&gt;
     line = fh.readline()&lt;br /&gt;
     # or&lt;br /&gt;
     for line in fh:&lt;br /&gt;
         print(line)&lt;br /&gt;
 &lt;br /&gt;
 fh = path_file_in.open(encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 ...&lt;br /&gt;
 fh.close()&lt;br /&gt;
&lt;br /&gt;
====File Write====&lt;br /&gt;
 path_file_out = Path(&amp;quot;out/1/out.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # write via pathlib&lt;br /&gt;
 path_file_out.write_text(&amp;quot;some text&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # write general&lt;br /&gt;
 with path_file_out.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     # w = overWrite file ; a = append to file&lt;br /&gt;
     # If running Python in Windows, &amp;quot;\n&amp;quot; is automatically replaced by &amp;quot;\r\n&amp;quot;.&lt;br /&gt;
     # To prevent this use newline=&#039;\n&#039;&lt;br /&gt;
     fh.writelines(lst)  # no linebreaks&lt;br /&gt;
     # or&lt;br /&gt;
     fh.write(&amp;quot;\n&amp;quot;.join(lst))&lt;br /&gt;
     # or&lt;br /&gt;
     for line in lst:&lt;br /&gt;
         fh.write(line)&lt;br /&gt;
 &lt;br /&gt;
     # Force update of file contents without closing it&lt;br /&gt;
     fh.flush()&lt;br /&gt;
 &lt;br /&gt;
 # alternative&lt;br /&gt;
 fh = path_file_out.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;)&lt;br /&gt;
 ...&lt;br /&gt;
 fh.close()&lt;br /&gt;
&lt;br /&gt;
===CSV===&lt;br /&gt;
====CSV Read====&lt;br /&gt;
 import csv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 with Path(&amp;quot;data.csv&amp;quot;).open(mode=&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as fh:  # for Excel use ANSI&lt;br /&gt;
     csv_reader = csv.DictReader(fh, dialect=&amp;quot;excel&amp;quot;, delimiter=&amp;quot;\t&amp;quot;)&lt;br /&gt;
     for row in csv_reader:&lt;br /&gt;
         print(f&#039;\t{row[&amp;quot;name&amp;quot;]} works in the {row[&amp;quot;department&amp;quot;]} department&#039;)&lt;br /&gt;
&lt;br /&gt;
====CSV Write====&lt;br /&gt;
 import csv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 # simple&lt;br /&gt;
 with Path(&amp;quot;data.tsv&amp;quot;).open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     csvwriter = csv.writer(fh, delimiter=&amp;quot;\t&amp;quot;)&lt;br /&gt;
     csvwriter.writerow((&amp;quot;Date&amp;quot;, &amp;quot;Confirmed&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # dictwriter&lt;br /&gt;
 with Path(&amp;quot;data.tsv&amp;quot;).open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     csvwriter = csv.DictWriter(&lt;br /&gt;
         fh,&lt;br /&gt;
         delimiter=&amp;quot;\t&amp;quot;,&lt;br /&gt;
         extrasaction=&amp;quot;ignore&amp;quot;,&lt;br /&gt;
         fieldnames=[&amp;quot;date&amp;quot;, &amp;quot;occupied_percent&amp;quot;, &amp;quot;occupied&amp;quot;, &amp;quot;total&amp;quot;],&lt;br /&gt;
     )&lt;br /&gt;
     csvwriter.writeheader()&lt;br /&gt;
     for d in my_list_of_dicts:&lt;br /&gt;
         d[&amp;quot;occupied_percent&amp;quot;] = round(100 * d[&amp;quot;occupied&amp;quot;] / d[&amp;quot;total&amp;quot;], 1)&lt;br /&gt;
         csvwriter.writerow(d)&lt;br /&gt;
&lt;br /&gt;
===JSON===&lt;br /&gt;
====JSON Read====&lt;br /&gt;
 import json&lt;br /&gt;
 # from file&lt;br /&gt;
 with path_to_download_file.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
     d_json = json.load(fh)&lt;br /&gt;
 # from string&lt;br /&gt;
 d_json = json.loads(response_text)&lt;br /&gt;
&lt;br /&gt;
 def json_read(file_path: Path) -&amp;gt; list[dict[str, str]]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Read JSON data from file.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     with file_path.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
         json_data = json.load(fh)&lt;br /&gt;
     return json_data&lt;br /&gt;
&lt;br /&gt;
====JSON Write====&lt;br /&gt;
Write dict to file in JSON format, keeping utf-8 encoding&lt;br /&gt;
 import json&lt;br /&gt;
 &lt;br /&gt;
 with path_to_download_file.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     json.dump(my_dict, fh, ensure_ascii=False, sort_keys=False, indent=2)&lt;br /&gt;
&lt;br /&gt;
 def json_write(file_path: Path, json_data: list[dict[str, str]]) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Write JSON data to file.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     with file_path.open(&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
         json.dump(json_data, fh, ensure_ascii=False, sort_keys=False, indent=2)&lt;br /&gt;
&lt;br /&gt;
===Excel===&lt;br /&gt;
====Excel Read====&lt;br /&gt;
 import openpyxl&lt;br /&gt;
 # or xlsxwriter for write-only&lt;br /&gt;
 &lt;br /&gt;
 workbook = openpyxl.load_workbook(&lt;br /&gt;
     pathToMyExcelFile,&lt;br /&gt;
     data_only=True,  # read values instead of formulas&lt;br /&gt;
     read_only=True,  # suppresses: &amp;quot;UserWarning: wmf image format is not supported so the image is being dropped&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 sheet = workbook[&amp;quot;mySheetName&amp;quot;]&lt;br /&gt;
 # or fetch active sheet&lt;br /&gt;
 sheet = workbook.active&lt;br /&gt;
 cell = sheet[&amp;quot;A34&amp;quot;]&lt;br /&gt;
 # or&lt;br /&gt;
 cell = sheet.cell(row=34, column=1)  # index start here with 1&lt;br /&gt;
 print(cell.value)&lt;br /&gt;
 # or&lt;br /&gt;
 print(sheet.cell(column=col, row=row).value)&lt;br /&gt;
&lt;br /&gt;
====Excel Write====&lt;br /&gt;
 import openpyxl&lt;br /&gt;
 &lt;br /&gt;
 workbook = openpyxl.Workbook()&lt;br /&gt;
 sheet = workbook.active&lt;br /&gt;
 cell = sheet[&amp;quot;A34&amp;quot;]&lt;br /&gt;
 # or&lt;br /&gt;
 cell = sheet.cell(row=i, column=j)  # index starts at 1&lt;br /&gt;
 cell.value = &amp;quot;asdf&amp;quot;&lt;br /&gt;
 workbook.save(&amp;quot;out.xlsx&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Word===&lt;br /&gt;
====Word Read====&lt;br /&gt;
 from io import BytesIO&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import pandas as pd&lt;br /&gt;
 from docx import Document&lt;br /&gt;
 from docx.document import Document as DocxDocument&lt;br /&gt;
 &lt;br /&gt;
 def read_doc_to_df(p_doc: Path | BytesIO) -&amp;gt; pd.DataFrame:  # noqa: C901, PLR0912&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Read Word document to DataFrame.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if isinstance(p_doc, Path):&lt;br /&gt;
         doc = Document(str(p_doc))&lt;br /&gt;
     elif isinstance(p_doc, BytesIO):&lt;br /&gt;
         doc = Document(p_doc)&lt;br /&gt;
 &lt;br /&gt;
     # list of multiple paragraphs per key&lt;br /&gt;
     data: dict[str, dict[str, list[str]]] = {}&lt;br /&gt;
 &lt;br /&gt;
     sections1: list[str] = []&lt;br /&gt;
     section1 = &amp;quot;&amp;quot;&lt;br /&gt;
     key = &amp;quot;&amp;quot;&lt;br /&gt;
     section2 = &amp;quot;&amp;quot;&lt;br /&gt;
     score = &amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     for paragraph in doc.paragraphs:&lt;br /&gt;
         assert paragraph.style is not None&lt;br /&gt;
         text = paragraph.text.strip()&lt;br /&gt;
         if paragraph.style.name == &amp;quot;Title&amp;quot;:  # doc title&lt;br /&gt;
             continue&lt;br /&gt;
         if paragraph.style.name == &amp;quot;Heading 1&amp;quot;:  # sheet name&lt;br /&gt;
             section1 = text&lt;br /&gt;
             if section1 not in sections1:&lt;br /&gt;
                 sections1.append(section1)&lt;br /&gt;
             key = &amp;quot;&amp;quot;&lt;br /&gt;
             section2 = &amp;quot;&amp;quot;&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Heading 2&amp;quot;:  # Key of the question&lt;br /&gt;
             key = text.split(&amp;quot; | &amp;quot;)[0]&lt;br /&gt;
             key = section1 + &amp;quot;|&amp;quot; + key&lt;br /&gt;
             section2 = &amp;quot;&amp;quot;&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Normal&amp;quot; and text.startswith(&amp;quot;[&amp;quot;) and &amp;quot;]&amp;quot; in text:&lt;br /&gt;
             section2, t2 = text[1:].split(&amp;quot;]&amp;quot;, maxsplit=1)&lt;br /&gt;
             if section2 == &amp;quot;Score&amp;quot;:&lt;br /&gt;
                 score = t2.strip()&lt;br /&gt;
                 data[key][section2] = [score]&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Normal&amp;quot;:&lt;br /&gt;
             text = paragraph.text.strip()&lt;br /&gt;
             # skip requirements leftovers and empty paragraphs&lt;br /&gt;
             if (section2 == &amp;quot;&amp;quot; and text.startswith(&amp;quot;...&amp;quot;)) or text == &amp;quot;&amp;quot;:&lt;br /&gt;
                 continue&lt;br /&gt;
             lst = data.setdefault(key, {}).setdefault(section2, [])&lt;br /&gt;
             lst.append(text)&lt;br /&gt;
             data[key][section2] = lst&lt;br /&gt;
         else:&lt;br /&gt;
             msg = f&amp;quot;Unexpected style: {paragraph.style.name} in paragraph: {text}&amp;quot;&lt;br /&gt;
             raise ValueError(msg)&lt;br /&gt;
 &lt;br /&gt;
     # flatten the text from list to str&lt;br /&gt;
     data2: dict[str, dict[str, str]] = {}&lt;br /&gt;
     for key, sections in data.items():&lt;br /&gt;
         for section2, lst in sections.items():&lt;br /&gt;
             s = &amp;quot;\n&amp;quot;.join(lst)&lt;br /&gt;
             if key not in data2:&lt;br /&gt;
                 data2[key] = {}&lt;br /&gt;
             data2[key][section2] = s.strip()&lt;br /&gt;
 &lt;br /&gt;
     df1 = pd.DataFrame.from_dict(data2, orient=&amp;quot;index&amp;quot;)&lt;br /&gt;
     df1.index.name = &amp;quot;Key&amp;quot;&lt;br /&gt;
     df1 = df1.reset_index()&lt;br /&gt;
     df1[ [ &amp;quot;Sheet&amp;quot;, &amp;quot;Key&amp;quot; ] ] = df1[&amp;quot;Key&amp;quot;].str.split(&amp;quot;|&amp;quot;, n=1, expand=True)&lt;br /&gt;
 &lt;br /&gt;
     return df1&lt;br /&gt;
&lt;br /&gt;
====Word Write====&lt;br /&gt;
 from io import BytesIO&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import pandas as pd from docx import Document&lt;br /&gt;
 from docx.document import Document as DocxDocument&lt;br /&gt;
 from docx.shared import Cm&lt;br /&gt;
 &lt;br /&gt;
 LAYOUT_SMALL_MARGINS = True&lt;br /&gt;
 if LAYOUT_SMALL_MARGINS:&lt;br /&gt;
     sections = doc.sections&lt;br /&gt;
     for section in sections:&lt;br /&gt;
         section.top_margin = Cm(1)&lt;br /&gt;
         section.bottom_margin = Cm(1)&lt;br /&gt;
         section.left_margin = Cm(1)&lt;br /&gt;
         section.right_margin = Cm(1)&lt;br /&gt;
 &lt;br /&gt;
 doc.add_heading(&amp;quot;My Title&amp;quot;, level=0)&lt;br /&gt;
 doc.add_heading(&amp;quot;My Section&amp;quot;, level=1)&lt;br /&gt;
 doc.add_paragraph(&amp;quot;Some Text)&lt;br /&gt;
 p = doc.add_paragraph()&lt;br /&gt;
 runner = p.add_run(bold text&amp;quot;)&lt;br /&gt;
 runner.bold = True  # runner.italic = True&lt;br /&gt;
&lt;br /&gt;
==Regular Expressions==&lt;br /&gt;
See [http://docs.python.org/library/re.html]&lt;br /&gt;
&lt;br /&gt;
See [https://pythex.org] for an online tester&lt;br /&gt;
&lt;br /&gt;
multiple flags are joined via pipe |&lt;br /&gt;
 s = re.sub(&amp;quot;asdf.*&amp;quot;, r&amp;quot;qwertz&amp;quot;, s, flags=re.DOTALL | re.IGNORECASE)&lt;br /&gt;
&lt;br /&gt;
====Lookahead and Lookbehind====&lt;br /&gt;
 pos lookahead: (?=...)&lt;br /&gt;
 neg lookahead: (?!...)&lt;br /&gt;
 pos lookbehind (?&amp;lt;=...)&lt;br /&gt;
 neg lookbehind (?&amp;lt;!...)&lt;br /&gt;
&lt;br /&gt;
====matching====&lt;br /&gt;
 import re&lt;br /&gt;
 &lt;br /&gt;
 # V0: simple 1&lt;br /&gt;
 myPattern = &amp;quot;(/\*\*\* 0097_210000_0192539580000_2898977_0050 \*\*\*/.*?)($|/\*\*\*)&amp;quot;&lt;br /&gt;
 myRegExp = re.compile(myPattern, re.DOTALL)&lt;br /&gt;
 myMatch = myRegExp.search(cont)&lt;br /&gt;
 assert myMatch != None, f&amp;quot;golden file not found in file {filename}&amp;quot;&lt;br /&gt;
 cont_golden = myMatch.group(1)&lt;br /&gt;
 &lt;br /&gt;
 # V1: simple 2&lt;br /&gt;
 assert (&lt;br /&gt;
     re.match(&amp;quot;^[a-z]{2}$&amp;quot;, d_settings[&amp;quot;country&amp;quot;]) != None&lt;br /&gt;
 ), f&#039;Error: county must be 2 digit lower case. We got: {d_settings[&amp;quot;country&amp;quot;]}&#039;&lt;br /&gt;
 &lt;br /&gt;
 # V2: find and count&lt;br /&gt;
 was = r&amp;quot;&amp;quot;&amp;quot;Part: ([^&amp;lt;+])&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 cnt_parts = len(re.findall(was, cont))&lt;br /&gt;
 cont = re.sub(was, r&amp;quot;\n\nTeil: \1\n\n&amp;quot;, cont)&lt;br /&gt;
 assert cnt_parts == 4, f&amp;quot;{cnt_parts} == 4&amp;quot;&lt;br /&gt;
 assert &amp;quot;Part:&amp;quot; not in cont&lt;br /&gt;
&lt;br /&gt;
=====Match email=====&lt;br /&gt;
 def checkValidEMail(email: str) -&amp;gt; bool:&lt;br /&gt;
     # from https://stackoverflow.com/posts/719543/timeline bottom edit&lt;br /&gt;
     if not re.fullmatch(r&amp;quot;^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$&amp;quot;, email):&lt;br /&gt;
         print(&amp;quot;Error: invalid email&amp;quot;)&lt;br /&gt;
         quit()&lt;br /&gt;
     return True&lt;br /&gt;
&lt;br /&gt;
====Find all====&lt;br /&gt;
 myMatches = re.findall(&#039;href=&amp;quot;([^&amp;quot;]+)&amp;quot;&#039;, cont)&lt;br /&gt;
 for myMatch in myMatches:&lt;br /&gt;
     print(myMatch)&lt;br /&gt;
&lt;br /&gt;
====substring====&lt;br /&gt;
 import re&lt;br /&gt;
 &lt;br /&gt;
 # simple via search&lt;br /&gt;
 lk_id = re.search(&#039;^.*timeseries\-(\d+)\.json$&#039;, f).group(1)&lt;br /&gt;
 &lt;br /&gt;
 # simple via sub&lt;br /&gt;
 myPattern = &amp;quot;^.*&amp;quot; + s1 + &amp;quot;(.*)&amp;quot; + s2 + &amp;quot;.*$&amp;quot;&lt;br /&gt;
 out = re.sub(myPattern, r&amp;quot;\1&amp;quot;, s)&lt;br /&gt;
 &lt;br /&gt;
 # more robust including an assert&lt;br /&gt;
 def substr_between(s: str, s1: str, s2: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     returns substring of s, found between strings s1 and s2&lt;br /&gt;
     s1 and s2 can be regular expressions&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     myPattern = s1 + &#039;(.*)&#039; + s2&lt;br /&gt;
     myRegExp = re.compile(myPattern)&lt;br /&gt;
     myMatches = myRegExp.search(s)&lt;br /&gt;
     assert myMatches != None, f&amp;quot;E: can&#039;t find &#039;{s1}&#039;...&#039;{s2}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     out = myMatches.group(1)&lt;br /&gt;
     return out&lt;br /&gt;
&lt;br /&gt;
 matchObj = re.search(r&amp;quot;(\d+\.\d+)&amp;quot;, text&lt;br /&gt;
 if matchObj:&lt;br /&gt;
   price = float( &#039;%s&#039; % (matchObj).group(0) )&lt;br /&gt;
&lt;br /&gt;
====Naming of match groups====&lt;br /&gt;
(?P&amp;lt;name&amp;gt;...), see [https://docs.python.org/3/library/re.html]&lt;br /&gt;
&lt;br /&gt;
====Search and Replace====&lt;br /&gt;
From [http://www.regular-expressions.info/python.html]&amp;lt;br&amp;gt;&lt;br /&gt;
re.sub(regex, replacement, str) performs a search-and-replace across subject, replacing all matches of regex in str with replacement. The result is returned by the sub() function. The str string you pass is not modified.&lt;br /&gt;
&lt;br /&gt;
 s = re.sub(&amp;quot;  +&amp;quot;, &amp;quot; &amp;quot;, s)&lt;br /&gt;
&lt;br /&gt;
====Splitting====&lt;br /&gt;
From [http://docs.python.org/library/re.html]&amp;lt;br&amp;gt;&lt;br /&gt;
split() splits a string into a list delimited by the passed pattern. The method is invaluable for converting textual data into data structures that can be easily read and modified by Python as demonstrated in the following example that creates a phonebook.&lt;br /&gt;
&lt;br /&gt;
First, here is the input. Normally it may come from a file, here we are using triple-quoted string syntax:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; input = &amp;quot;&amp;quot;&amp;quot;Ross McFluff: 834.345.1254 155 Elm Street&lt;br /&gt;
 ...&lt;br /&gt;
 ... Ronald Heathmore: 892.345.3428 436 Finley Avenue&lt;br /&gt;
 ... Frank Burger: 925.541.7625 662 South Dogwood Way&lt;br /&gt;
 ...&lt;br /&gt;
 ...&lt;br /&gt;
 ... Heather Albrecht: 548.326.4584 919 Park Place&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The entries are separated by one or more newlines. Now we convert the string into a list with each nonempty line having its own entry:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; entries = re.split(&amp;quot;\n+&amp;quot;, input)&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; entries&lt;br /&gt;
 [&#039;Ross McFluff: 834.345.1254 155 Elm Street&#039;,&lt;br /&gt;
 &#039;Ronald Heathmore: 892.345.3428 436 Finley Avenue&#039;,&lt;br /&gt;
 &#039;Frank Burger: 925.541.7625 662 South Dogwood Way&#039;,&lt;br /&gt;
 &#039;Heather Albrecht: 548.326.4584 919 Park Place&#039;]&lt;br /&gt;
&lt;br /&gt;
Finally, split each entry into a list with first name, last name, telephone number, and address. We use the maxsplit parameter of split() because the address has spaces, our splitting pattern, in it:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; [re.split(&amp;quot;:? &amp;quot;, entry, 3) for entry in entries]&lt;br /&gt;
 [[&#039;Ross&#039;, &#039;McFluff&#039;, &#039;834.345.1254&#039;, &#039;155 Elm Street&#039;],&lt;br /&gt;
 [&#039;Ronald&#039;, &#039;Heathmore&#039;, &#039;892.345.3428&#039;, &#039;436 Finley Avenue&#039;],&lt;br /&gt;
 [&#039;Frank&#039;, &#039;Burger&#039;, &#039;925.541.7625&#039;, &#039;662 South Dogwood Way&#039;],&lt;br /&gt;
 [&#039;Heather&#039;, &#039;Albrecht&#039;, &#039;548.326.4584&#039;, &#039;919 Park Place&#039;]]&lt;br /&gt;
&lt;br /&gt;
==== replace cont by linebreaks====&lt;br /&gt;
 def replace_cont_by_linebreaks(s: str, regex: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Replace regex in s by the number of linebreaks it originally contained.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     myMatches = re.findall(regex, s, flags=re.DOTALL)&lt;br /&gt;
     for match in myMatches:&lt;br /&gt;
         linebreaks = match.count(&amp;quot;\n&amp;quot;)&lt;br /&gt;
         s = s.replace(match, &amp;quot;\n&amp;quot; * linebreaks, 1)&lt;br /&gt;
     return s&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Image/Picture/Photo==&lt;br /&gt;
 from PIL import Image, ImageFilter  # pip install Pillow&lt;br /&gt;
 &lt;br /&gt;
 fileIn = &amp;quot;2018-02-09 13.56.25.jpg&amp;quot;&lt;br /&gt;
 # Read image&lt;br /&gt;
 img = Image.open(fileIn)&lt;br /&gt;
PROBLEM:&amp;lt;br/&amp;gt;&lt;br /&gt;
PIL Image.save() drops the IPTC data like tags, keywords, copywrite, ...&amp;lt;br/&amp;gt;&lt;br /&gt;
better using https://imagemagick.org instead when tags shall be kept&lt;br /&gt;
&lt;br /&gt;
====Resize====&lt;br /&gt;
 # Resize keeping aspect ration -&amp;gt; img.thumbnail&lt;br /&gt;
 # drops exif data, exif can be added from source file via exif= in save, see below&lt;br /&gt;
 size = 1920, 1920&lt;br /&gt;
 img.thumbnail(size, Image.ANTIALIAS)&lt;br /&gt;
&lt;br /&gt;
====Export file====&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-edit.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img = Image.open(fileIn)&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=&#039;keep&#039;)  # exif=dict_exif_bytes&lt;br /&gt;
     # JPEG Parameters&lt;br /&gt;
     # * qualitiy : &#039;keep&#039; or 1 (worst) to 95 (best), default = 75. Values above 95 should be avoided.&lt;br /&gt;
     # * dpi : tuple of integers representing the pixel density, (x,y)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     print(&amp;quot;cannot write file &#039;%s&#039;&amp;quot; % fileOut)&lt;br /&gt;
&lt;br /&gt;
====Export Progressive / web optimized JPEG====&lt;br /&gt;
 from PIL import ImageFile  # for MAXBLOCK for progressive export&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-progressive.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=80, optimize=True, progressive=True)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     ImageFile.MAXBLOCK = img.size[0] * img.size[1]&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=80, optimize=True, progressive=True)&lt;br /&gt;
&lt;br /&gt;
====JPEG Meta Data: EXIF and IPTC====&lt;br /&gt;
=====IPTC: Tags/Keywords=====&lt;br /&gt;
 from iptcinfo3 import IPTCInfo  # this works in pyhton 3!&lt;br /&gt;
 iptc = IPTCInfo(fileIn)&lt;br /&gt;
 if len(iptc[&#039;keywords&#039;]) &amp;gt; 0:  # or supplementalCategories or contacts&lt;br /&gt;
     print(&#039;====&amp;gt; Keywords&#039;)&lt;br /&gt;
     for key in sorted(iptc[&#039;keywords&#039;]):&lt;br /&gt;
         s = key.decode(&#039;ascii&#039;)  # decode binary strings&lt;br /&gt;
         print(s)&lt;br /&gt;
&lt;br /&gt;
=====EXIF via piexif=====&lt;br /&gt;
 import piexif  # pip install piexif&lt;br /&gt;
 exif_dict = piexif.load(img.info[&#039;exif&#039;])&lt;br /&gt;
 print(exif_dict[&#039;GPS&#039;][piexif.GPSIFD.GPSAltitude])&lt;br /&gt;
 # returns list of 2 integers: value and donator  -&amp;gt; v / d&lt;br /&gt;
 # (340000, 1000) =&amp;gt; 340m&lt;br /&gt;
 # (51, 2) =&amp;gt; 25.5m&lt;br /&gt;
 &lt;br /&gt;
 # Modify altitude&lt;br /&gt;
 exif_dict[&#039;GPS&#039;][piexif.GPSIFD.GPSAltitude] = (140, 1)  # 140m&lt;br /&gt;
 &lt;br /&gt;
 # write to file&lt;br /&gt;
 exif_bytes = piexif.dump(exif_dict)&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-modExif.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;jpeg&amp;quot;, exif=exif_bytes, quality=&#039;keep&#039;)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     print(&amp;quot;cannot write file &#039;%s&#039;&amp;quot; % fileOut)&lt;br /&gt;
or&lt;br /&gt;
 exif_dict = piexif.load(fileIn)&lt;br /&gt;
 for ifd in (&amp;quot;0th&amp;quot;, &amp;quot;Exif&amp;quot;, &amp;quot;GPS&amp;quot;, &amp;quot;1st&amp;quot;):&lt;br /&gt;
     print(&amp;quot;===&amp;quot; + ifd)&lt;br /&gt;
     for tag in exif_dict[ifd]:&lt;br /&gt;
         print(piexif.TAGS[ifd][tag][&amp;quot;name&amp;quot;], &amp;quot;\t&amp;quot;,&lt;br /&gt;
               tag, &amp;quot;\t&amp;quot;, exif_dict[ifd][tag])&lt;br /&gt;
 print(exif_dict[&#039;0th&#039;][306]) # 306 = DateTime&lt;br /&gt;
&lt;br /&gt;
=====EXIF via exifread=====&lt;br /&gt;
 # Open image file for reading (binary mode)&lt;br /&gt;
 fh = open(fileIn, &amp;quot;rb&amp;quot;)&lt;br /&gt;
 # Return Exif tags&lt;br /&gt;
 exif = exifread.process_file(fh)&lt;br /&gt;
 fh.close()&lt;br /&gt;
 # for tag in exif.keys():&lt;br /&gt;
 #     if tag not in (&#039;JPEGThumbnail&#039;, &#039;TIFFThumbnail&#039;, &#039;Filename&#039;, &#039;EXIF MakerNote&#039;):&lt;br /&gt;
 #         print(&amp;quot;%s\t%s&amp;quot; % (tag, exif[tag]))&lt;br /&gt;
 print(exif[&amp;quot;Image DateTime&amp;quot;])&lt;br /&gt;
 print(exif[&amp;quot;GPS GPSLatitude&amp;quot;])&lt;br /&gt;
 print(exif[&amp;quot;GPS GPSLongitude&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
=====EXIF GPS via PIL=====&lt;br /&gt;
 # from https://developer.here.com/blog/getting-started-with-geocoding-exif-image-metadata-in-python3&lt;br /&gt;
 def get_exif(filename):&lt;br /&gt;
     image = Image.open(filename)&lt;br /&gt;
     image.verify()&lt;br /&gt;
     image.close()&lt;br /&gt;
     return image._getexif()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_labeled_exif(exif):&lt;br /&gt;
     labeled = {}&lt;br /&gt;
     for (key, val) in exif.items():&lt;br /&gt;
         labeled[TAGS.get(key)] = val&lt;br /&gt;
     return labeled&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_geotagging(exif):&lt;br /&gt;
     if not exif:&lt;br /&gt;
         raise ValueError(&amp;quot;No EXIF metadata found&amp;quot;)&lt;br /&gt;
     geotagging = {}&lt;br /&gt;
     for (idx, tag) in TAGS.items():&lt;br /&gt;
         if tag == &amp;quot;GPSInfo&amp;quot;:&lt;br /&gt;
             if idx not in exif:&lt;br /&gt;
                 raise ValueError(&amp;quot;No EXIF geotagging found&amp;quot;)&lt;br /&gt;
             for (key, val) in GPSTAGS.items():&lt;br /&gt;
                 if key in exif[idx]:&lt;br /&gt;
                     geotagging[val] = exif[idx][key]&lt;br /&gt;
     return geotagging&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_decimal_from_dms(dms, ref):&lt;br /&gt;
     degrees = dms[0][0] / dms[0][1]&lt;br /&gt;
     minutes = dms[1][0] / dms[1][1] / 60.0&lt;br /&gt;
     seconds = dms[2][0] / dms[2][1] / 3600.0&lt;br /&gt;
     if ref in [&amp;quot;S&amp;quot;, &amp;quot;W&amp;quot;]:&lt;br /&gt;
         degrees = -degrees&lt;br /&gt;
         minutes = -minutes&lt;br /&gt;
         seconds = -seconds&lt;br /&gt;
     return round(degrees + minutes + seconds, 5)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_coordinates(geotags):&lt;br /&gt;
     lat = get_decimal_from_dms(geotags[&amp;quot;GPSLatitude&amp;quot;], geotags[&amp;quot;GPSLatitudeRef&amp;quot;])&lt;br /&gt;
     lon = get_decimal_from_dms(geotags[&amp;quot;GPSLongitude&amp;quot;], geotags[&amp;quot;GPSLongitudeRef&amp;quot;])&lt;br /&gt;
     return (lat, lon)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 exif = get_exif(fileIn)&lt;br /&gt;
 exif_labeled = get_labeled_exif(exif)&lt;br /&gt;
 print(exif_labeled[&amp;quot;DateTime&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
 geotags = get_geotagging(exif)&lt;br /&gt;
 print(get_coordinates(geotags))&lt;br /&gt;
&lt;br /&gt;
====Template Matching / Image Regocnition Using CV2 / OpenCV====&lt;br /&gt;
see [[Python - CV2]]&lt;br /&gt;
&lt;br /&gt;
====Optical Character Recognition (OCR) ====&lt;br /&gt;
see [[Python - OCR]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Templates/Snippets==&lt;br /&gt;
===Retry on Exception===&lt;br /&gt;
 for retry_no in range(MAX_RETRIES):&lt;br /&gt;
     try:&lt;br /&gt;
         if retry_no &amp;gt; 0:&lt;br /&gt;
             logger.warning(&amp;quot;retrying %d/%d...&amp;quot;, retry_no + 1, MAX_RETRIES)&lt;br /&gt;
         doit()&lt;br /&gt;
     except json.JSONDecodeError:&lt;br /&gt;
         logger.exception(&amp;quot;JSONDecodeError&amp;quot;)&lt;br /&gt;
         if retry_no == MAX_RETRIES - 1:&lt;br /&gt;
             # end of retries&lt;br /&gt;
             raise&lt;br /&gt;
&lt;br /&gt;
===Diff of 2 files===&lt;br /&gt;
 import difflib&lt;br /&gt;
 &lt;br /&gt;
 fh1 = p_file1.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 fh2 = p_file1.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 diff = difflib.ndiff(fh1.readlines(), fh2.readlines())&lt;br /&gt;
 delta = &amp;quot;&amp;quot;.join(line for line in diff if line.startswith((&amp;quot;+ &amp;quot;, &amp;quot;- &amp;quot;)))&lt;br /&gt;
 print(delta)&lt;br /&gt;
&lt;br /&gt;
===GPX parsing===&lt;br /&gt;
 import gpxpy&lt;br /&gt;
 import gpxpy.gpx&lt;br /&gt;
 # Elevation data by NASA: see lib at https://github.com/tkrajina/srtm.py&lt;br /&gt;
 fh_gpx_file = open(gpx_file_path, &#039;r&#039;)&lt;br /&gt;
 gpx = gpxpy.parse(fh_gpx_file)&lt;br /&gt;
 #  Loops for accessing the data&lt;br /&gt;
 for track in gpx.tracks:&lt;br /&gt;
     for segment in track.segments:&lt;br /&gt;
         for point in segment.points:&lt;br /&gt;
 for waypoint in gpx.waypoints:&lt;br /&gt;
 for route in gpx.routes:&lt;br /&gt;
     for point in route.points: &lt;br /&gt;
 # interesting properties of point / waypoint objects:&lt;br /&gt;
 point.time&lt;br /&gt;
 point.latitude&lt;br /&gt;
 point.longitude&lt;br /&gt;
 point.source&lt;br /&gt;
 waypoint.name&lt;br /&gt;
&lt;br /&gt;
===TypeHints===&lt;br /&gt;
 from typing import Any, Dict, cast&lt;br /&gt;
 creds = cast(dict[str, str], tomllib.load(f))  # type: ignore&lt;br /&gt;
 o = cast(Dict[str, Any], tomllib.load(f))  # type: ignore&lt;br /&gt;
 o[&amp;quot;sap&amp;quot;] = cast(Dict[str, str], o[&amp;quot;sap&amp;quot;])&lt;br /&gt;
 o[&amp;quot;settings&amp;quot;] = cast(Dict[str, str | int | bool], o[&amp;quot;settings&amp;quot;])&lt;br /&gt;
 o[&amp;quot;settings&amp;quot;][&amp;quot;sleep_time&amp;quot;] = cast(int, o[&amp;quot;settings&amp;quot;][&amp;quot;sleep_time&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
===Pylance/Pyright===&lt;br /&gt;
 import tomllib&lt;br /&gt;
 # shows warning: Import &amp;quot;xyz&amp;quot; could not be resolved&lt;br /&gt;
 # fix by &lt;br /&gt;
 import tomllib # pyright: ignore&lt;br /&gt;
&lt;br /&gt;
===asserts function argument validation===&lt;br /&gt;
aus [https://bildungsportal.sachsen.de/opal/auth/RepositoryEntry/12518195218/CourseNode/94506982489539?0 Python Kurs von Carsten Knoll]&lt;br /&gt;
 def eine_funktion(satz, ganzzahl, zahl2, liste):&lt;br /&gt;
   if not type(satz) == str:&lt;br /&gt;
     print &amp;quot;Datentpyfehler: satz&amp;quot;&lt;br /&gt;
     return -1&lt;br /&gt;
   if not isinstance(ganzzahl, int):&lt;br /&gt;
     print &amp;quot;Datentpyfehler: ganzzahl&amp;quot;&lt;br /&gt;
     return -2&lt;br /&gt;
   if not isinstance(liste, (tuple, list)):&lt;br /&gt;
     print &amp;quot;Datentpyfehler: liste&amp;quot;&lt;br /&gt;
     return -3&lt;br /&gt;
   # Kompakteste Variante (empfohlen): &lt;br /&gt;
   assert zahl2 &amp;gt; 0, &amp;quot;Error: zahl2 ist nicht &amp;gt; 0&amp;quot; # Assertation-Error bei Nichterfuellung&lt;br /&gt;
&lt;br /&gt;
 def F(x):&lt;br /&gt;
   if not isinstance(x, (float, int)):&lt;br /&gt;
     msg = &amp;quot;Zahl erwartet, %s bekommen&amp;quot; % type(x)&lt;br /&gt;
     raise ValueError(msg)&lt;br /&gt;
   return x**2&lt;br /&gt;
better:&lt;br /&gt;
 def F(x):&lt;br /&gt;
   assert isinstance(x, (float, int)), &amp;quot;Error: x is not of type float or int&amp;quot;&lt;br /&gt;
   return x**2&lt;br /&gt;
&lt;br /&gt;
 assert variant in [&lt;br /&gt;
     &amp;quot;normal&amp;quot;,&lt;br /&gt;
     &amp;quot;gray&amp;quot;,&lt;br /&gt;
     &amp;quot;cannyedge&amp;quot;,&lt;br /&gt;
 ], &amp;quot;Error: variant is not in &#039;normal&#039;, &#039;gray&#039;, &#039;cannyedge&#039;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===Exceptions===&lt;br /&gt;
see [https://medium.com/@saadjamilakhtar/5-best-practices-for-python-exception-handling-5e54b876a20]&lt;br /&gt;
&lt;br /&gt;
====Catching====&lt;br /&gt;
Catch keyboard interrupt and do a &amp;quot;save exit&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     i = 0&lt;br /&gt;
     while 1:&lt;br /&gt;
         i += 1&lt;br /&gt;
         print(i)&lt;br /&gt;
 except KeyboardInterrupt:&lt;br /&gt;
     print(&amp;quot;stopped&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
Catch &#039;&#039;&#039;all&#039;&#039;&#039; exceptions&lt;br /&gt;
 try:&lt;br /&gt;
   [...]&lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     print(&amp;quot;Exception: &amp;quot;, e)&lt;br /&gt;
&lt;br /&gt;
====Raising Exceptions====&lt;br /&gt;
 if resp.status_code != 200:  # noqa: PLR2004&lt;br /&gt;
     msg = f&amp;quot;Bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
     raise ValueError(msg) from None&lt;br /&gt;
&lt;br /&gt;
Custom Exceptions&lt;br /&gt;
 try: &lt;br /&gt;
   raise Exception(&amp;quot;HiHo&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
 raise ValueError¶&lt;br /&gt;
&lt;br /&gt;
===perl grep and map===&lt;br /&gt;
from [https://stackoverflow.com/questions/12845288/grep-on-elements-of-a-list]&lt;br /&gt;
 def grep(list, pattern):&lt;br /&gt;
     expr = re.compile(pattern)&lt;br /&gt;
     return [elem for elem in list if expr.match(elem)]&lt;br /&gt;
 or&lt;br /&gt;
 filteredList = filter(lambda x: x &amp;lt; 7 and x &amp;gt; 2, unfilteredList)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def map(list, was, womit):&lt;br /&gt;
     return list(map(lambda i: re.sub(was, womit, i), list))&lt;br /&gt;
     # was = &#039;.*&amp;quot;(\d+)&amp;quot;.*&#039;&lt;br /&gt;
     # womit = r&amp;quot;\1&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===unit testing using pytest===&lt;br /&gt;
install via&lt;br /&gt;
 pip install pytest&lt;br /&gt;
activate in vscode, see [https://code.visualstudio.com/docs/python/testing#_enable-a-test-framework]: To enable testing, use the Python: Configure Tests command on the Command Palette.&lt;br /&gt;
&lt;br /&gt;
see https://docs.pytest.org/en/6.2.x/assert.html#assert&lt;br /&gt;
&lt;br /&gt;
test_1.py:&lt;br /&gt;
 import myLib # my custom lib to test&lt;br /&gt;
 &lt;br /&gt;
 class TestClass:&lt;br /&gt;
     def test_one(self):&lt;br /&gt;
         x = &amp;quot;this&amp;quot;&lt;br /&gt;
         assert &amp;quot;h&amp;quot; in x&lt;br /&gt;
 &lt;br /&gt;
     def test_two(self):&lt;br /&gt;
         assert myLib.multiply(1, 2) == 2&lt;br /&gt;
 &lt;br /&gt;
     def test_three(self):&lt;br /&gt;
         assert myLib.multiply(2, 2) == 2&lt;br /&gt;
&lt;br /&gt;
====project structure====&lt;br /&gt;
=====simplest structore: test next to script=====&lt;br /&gt;
Alternative without tests dir:&lt;br /&gt;
 ./main.py&lt;br /&gt;
 ./helper.py &lt;br /&gt;
 ./helper_test.py&lt;br /&gt;
with helper_test.py:&lt;br /&gt;
 from helper import fnc1&lt;br /&gt;
&lt;br /&gt;
=====standalone model with src and tests dirs=====&lt;br /&gt;
for this dir structure&lt;br /&gt;
 src/helper.py &lt;br /&gt;
 tests/helper_test.py&lt;br /&gt;
helper_test.py needs&lt;br /&gt;
 import sys&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 sys.path.insert(0, (Path(__file__).parent.parent / &amp;quot;src&amp;quot;).as_posix())&lt;br /&gt;
&lt;br /&gt;
=====standalone model with tests dir=====&lt;br /&gt;
if the test files are placed inside a ./tests/ dir&lt;br /&gt;
 ./main.py&lt;br /&gt;
 ./helper.py &lt;br /&gt;
 tests/helper_test.py&lt;br /&gt;
helper_test.py needs &lt;br /&gt;
 sys.path.insert(0, Path(__file__).parent.parent.as_posix())&lt;br /&gt;
 from helper import fnc1&lt;br /&gt;
&lt;br /&gt;
====parametrize====&lt;br /&gt;
 import pytest&lt;br /&gt;
 @pytest.mark.parametrize(&lt;br /&gt;
     (&amp;quot;test_input&amp;quot;, &amp;quot;expected&amp;quot;),&lt;br /&gt;
     [(&amp;quot;&amp;quot;, None), (&amp;quot;PT30M&amp;quot;, 30), (&amp;quot;PT3H&amp;quot;, 3 * 60), (&amp;quot;PT2H30M&amp;quot;, 2 * 60 + 30)],&lt;br /&gt;
 )&lt;br /&gt;
 def test_task_est_to_minutes(test_input: str, expected: int) -&amp;gt; None:&lt;br /&gt;
     assert task_est_to_minutes(test_input) == expected&lt;br /&gt;
&lt;br /&gt;
=====fixures: prepare and cleanup test_data=====&lt;br /&gt;
to automatically run before and after each testcase&lt;br /&gt;
 @pytest.fixture(autouse=True)&lt;br /&gt;
 def _setup_tests():  # noqa: ANN202&lt;br /&gt;
     cache_prepare_lists()&lt;br /&gt;
     cache_prepare_tasks()&lt;br /&gt;
 &lt;br /&gt;
     yield&lt;br /&gt;
 &lt;br /&gt;
     cache_cleanup_test_data()&lt;br /&gt;
&lt;br /&gt;
====Test coverage report====&lt;br /&gt;
 pip install pytest-cov&lt;br /&gt;
 # console output&lt;br /&gt;
 pytest --cov&lt;br /&gt;
 &lt;br /&gt;
 # html report in coverage_report/index.html with details per file&lt;br /&gt;
 pytest --cov --cov-report=html:coverage_report&lt;br /&gt;
to exclude a code block from coverage report, add&lt;br /&gt;
 # pragma: no cover&lt;br /&gt;
&lt;br /&gt;
=== Sleep / Wait for input ===&lt;br /&gt;
sleep for a while&lt;br /&gt;
 import time&lt;br /&gt;
 time.sleep(60)&lt;br /&gt;
&lt;br /&gt;
wait for user input&lt;br /&gt;
 input(&amp;quot;press Enter to close&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Suppress Warnings===&lt;br /&gt;
see [https://docs.python.org/3/library/warnings.html#temporarily-suppressing-warnings]&lt;br /&gt;
 import warnings&lt;br /&gt;
 warnings.filterwarnings(&amp;quot;ignore&amp;quot;, message=&amp;quot;.*native_field_num.*not found in message.*&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Logging===&lt;br /&gt;
====V4 minimal stdout logging====&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.addLevelName(logging.DEBUG, &amp;quot;D&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.INFO, &amp;quot;I&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.WARNING, &amp;quot;W&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.ERROR, &amp;quot;E&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.CRITICAL, &amp;quot;C&amp;quot;)&lt;br /&gt;
 logging.basicConfig(level=logging.INFO, format=&amp;quot;%(levelname)s: %(name)s: %(message)s&amp;quot;)&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
 LOGGER.setLevel(logging.INFO)&lt;br /&gt;
 logging.getLogger(&amp;quot;httpx&amp;quot;).setLevel(logging.WARNING)&lt;br /&gt;
 &lt;br /&gt;
 # usage of variables and rounding of numbers. (%d also rounds floats)&lt;br /&gt;
 LOGGER.info(&amp;quot;file %s has %d lines with avg %.1f char/line&amp;quot;, filename, lines, c_p_l)&lt;br /&gt;
&lt;br /&gt;
====V3 simple logging====&lt;br /&gt;
 # main file:&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.basicConfig(&lt;br /&gt;
     level=logging.INFO,&lt;br /&gt;
     format=&amp;quot;%(asctime)s\t%(levelname)s\t%(name)s\t%(message)s&amp;quot;,&lt;br /&gt;
     handlers=[&lt;br /&gt;
         RotatingFileHandler(&lt;br /&gt;
             Path(__file__).with_suffix(&amp;quot;.log&amp;quot;),&lt;br /&gt;
             maxBytes=10485760,  # 10 MB = 10*1024*1024,&lt;br /&gt;
             backupCount=1,&lt;br /&gt;
             encoding=&amp;quot;utf-8&amp;quot;,&lt;br /&gt;
         )&lt;br /&gt;
     ],&lt;br /&gt;
     datefmt=&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
 logger = logging.getLogger(Path(__file__).stem)&lt;br /&gt;
 logger.info(&amp;quot;Start&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # other files:&lt;br /&gt;
 import logging&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
&lt;br /&gt;
====V2: rotating file and STDOUT====&lt;br /&gt;
 # 1. setup&lt;br /&gt;
 import logging&lt;br /&gt;
 from logging.handlers import RotatingFileHandler&lt;br /&gt;
 &lt;br /&gt;
 logfile = &amp;quot;myApp.log&amp;quot;&lt;br /&gt;
 maxBytes = 20 * 1024 * 1024&lt;br /&gt;
 backupCount = 5&lt;br /&gt;
 loglevel_console = logging.INFO&lt;br /&gt;
 loglevel_file = logging.DEBUG&lt;br /&gt;
 &lt;br /&gt;
 # create logger&lt;br /&gt;
 logger = logging.getLogger(&amp;quot;root&amp;quot;)&lt;br /&gt;
 logger.setLevel(loglevel_file)&lt;br /&gt;
 &lt;br /&gt;
 # console handler&lt;br /&gt;
 ch = logging.StreamHandler()&lt;br /&gt;
 ch.setLevel(loglevel_console)&lt;br /&gt;
 &lt;br /&gt;
 # rotating file handler&lt;br /&gt;
 # fh = logging.FileHandler(logfile)&lt;br /&gt;
 fh = RotatingFileHandler(logfile, maxBytes=maxBytes, backupCount=backupCount)&lt;br /&gt;
 fh.setLevel(loglevel_file)&lt;br /&gt;
 &lt;br /&gt;
 # create formatter and add it to the handlers&lt;br /&gt;
 # %(name)s = LoggerName, %(threadName)s = TreadName&lt;br /&gt;
 formatter = logging.Formatter(&lt;br /&gt;
     &amp;quot;%(asctime)s - %(levelname)s - %(name)s - %(threadName)s - %(message)s &amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 fh.setFormatter(formatter)&lt;br /&gt;
 ch.setFormatter(formatter)&lt;br /&gt;
 &lt;br /&gt;
 # add the handlers to the logger&lt;br /&gt;
 logger.addHandler(ch)&lt;br /&gt;
 logger.addHandler(fh)&lt;br /&gt;
 &lt;br /&gt;
 logger.debug(&amp;quot;DebugMe&amp;quot;)&lt;br /&gt;
 logger.info(&amp;quot;Starting&amp;quot;)&lt;br /&gt;
 logger.warning(&amp;quot;Attention&amp;quot;)&lt;br /&gt;
 logger.error(&amp;quot;Something went wrong&amp;quot;)&lt;br /&gt;
 logger.critical(&amp;quot;Something seriously went wrong &amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # 2. in other files/modules now use&lt;br /&gt;
 import logging&lt;br /&gt;
 &lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 logger.info(&amp;quot;text&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 ...&lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     logger.exception(&amp;quot;Unhandeled exception&amp;quot;)&lt;br /&gt;
     quit()&lt;br /&gt;
&lt;br /&gt;
====V1: Simple====&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.basicConfig(&lt;br /&gt;
     level=logging.INFO,&lt;br /&gt;
     format=&amp;quot;%(asctime)s\t%(levelname)s\t%(message)s&amp;quot;,  # \t%(name)s&lt;br /&gt;
     filename=Path(__file__).with_suffix(&amp;quot;.log&amp;quot;), # uncomment to switch to stdout&lt;br /&gt;
     filemode=&amp;quot;a&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 logger.debug(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.info(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.warning(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.error(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.critical(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 try:&lt;br /&gt;
 ...&lt;br /&gt;
 except psycopg2.DatabaseError:&lt;br /&gt;
     logger.exception(&amp;quot;Database connection error: %s&amp;quot;)&lt;br /&gt;
     raise&lt;br /&gt;
&lt;br /&gt;
===Process Bar===&lt;br /&gt;
see [https://tqdm.github.io tqdm]&lt;br /&gt;
 from tqdm import tqdm&lt;br /&gt;
 for i in tqdm(range(10000)):&lt;br /&gt;
     ....&lt;br /&gt;
or&lt;br /&gt;
 with tqdm(total=total_iterations, desc=&amp;quot;Processing&amp;quot;) as progress_bar:&lt;br /&gt;
     ...&lt;br /&gt;
     progress_bar.update(1)&lt;br /&gt;
&lt;br /&gt;
===CGI Web development===&lt;br /&gt;
 # Print necessary headers.&lt;br /&gt;
 print(&amp;quot;Content-Type: text/html&amp;quot;)&lt;br /&gt;
 print()&lt;br /&gt;
 &lt;br /&gt;
 # errors and debugging info to browser&lt;br /&gt;
 import cgitb&lt;br /&gt;
 cgitb.enable()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Access URL or Form Parameters&lt;br /&gt;
 # V2 from https://www.tutorialspoint.com/python/python_cgi_programming.htm&lt;br /&gt;
 import cgi&lt;br /&gt;
 form = cgi.FieldStorage()&lt;br /&gt;
 username = form.getvalue(&#039;username&#039;)&lt;br /&gt;
 print(username)&lt;br /&gt;
&lt;br /&gt;
 # V1&lt;br /&gt;
 import sys&lt;br /&gt;
 import urllib.parse&lt;br /&gt;
 query = os.environ.get(&#039;QUERY_STRING&#039;)&lt;br /&gt;
 query = urllib.parse.unquote(query, errors=&amp;quot;surrogateescape&amp;quot;)&lt;br /&gt;
 d = dict(qc.split(&amp;quot;=&amp;quot;) for qc in query.split(&amp;quot;&amp;amp;&amp;quot;))&lt;br /&gt;
 print(d)&lt;br /&gt;
&lt;br /&gt;
====CGI Backend Returning JSONs====&lt;br /&gt;
 #!/usr/local/bin/python3.6&lt;br /&gt;
 # -*- coding: utf-8 -*-&lt;br /&gt;
 &lt;br /&gt;
 import cgi&lt;br /&gt;
 import json&lt;br /&gt;
 &lt;br /&gt;
 # Print necessary headers.&lt;br /&gt;
 print(&amp;quot;Content-type: application/json&amp;quot;)&lt;br /&gt;
 print()&lt;br /&gt;
 &lt;br /&gt;
 def get_form_parameter(para: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;asserts that a given parameter is set and returns its value&amp;quot;&lt;br /&gt;
     value = form.getvalue(para)&lt;br /&gt;
     assert value, f&amp;quot;Error: parameter {para} missing&amp;quot;&lt;br /&gt;
     assert value != &amp;quot;&amp;quot;, f&amp;quot;Error: parameter {para} missing&amp;quot;&lt;br /&gt;
     return value&lt;br /&gt;
  &lt;br /&gt;
 response = {}&lt;br /&gt;
 response[&#039;status&#039;] = &amp;quot;ok&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 try:&lt;br /&gt;
     action = get_form_parameter(&amp;quot;action&amp;quot;)&lt;br /&gt;
     response[&#039;action&#039;] = action&lt;br /&gt;
     if action == &amp;quot;myAction&amp;quot;:&lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     response[&#039;status&#039;] = &amp;quot;error&amp;quot;&lt;br /&gt;
     d = {&amp;quot;type&amp;quot;: str(type(e)), &amp;quot;text&amp;quot;: str(e)}&lt;br /&gt;
     response[&amp;quot;exception&amp;quot;] = d&lt;br /&gt;
 &lt;br /&gt;
 finally:&lt;br /&gt;
     print(json.dumps(response))&lt;br /&gt;
&lt;br /&gt;
==Databases==&lt;br /&gt;
===PostgreSQL===&lt;br /&gt;
====Improved helper function====&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Helper functions for DB access.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 import datetime as dt&lt;br /&gt;
 import logging&lt;br /&gt;
 &lt;br /&gt;
 import psycopg2&lt;br /&gt;
 import psycopg2.extras&lt;br /&gt;
 from creds_db import credentials as creds_db&lt;br /&gt;
 &lt;br /&gt;
 # Configure logging&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 &lt;br /&gt;
 connection: psycopg2.extensions.connection = None  # type: ignore&lt;br /&gt;
 cursor_query: psycopg2.extensions.cursor = None  # type: ignore&lt;br /&gt;
 cursor_write: psycopg2.extensions.cursor = None  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def connect() -&amp;gt; (&lt;br /&gt;
     tuple[&lt;br /&gt;
         psycopg2.extensions.connection,&lt;br /&gt;
         psycopg2.extensions.cursor,&lt;br /&gt;
         psycopg2.extensions.cursor,&lt;br /&gt;
     ]&lt;br /&gt;
 ):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Connect to the database.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     try:&lt;br /&gt;
         connection = psycopg2.connect(**creds_db)&lt;br /&gt;
         cursor_query = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)&lt;br /&gt;
         cursor_write = connection.cursor()&lt;br /&gt;
         logger.info(&lt;br /&gt;
             &amp;quot;Connected to database %s on host %s&amp;quot;,&lt;br /&gt;
             creds_db[&amp;quot;database&amp;quot;],&lt;br /&gt;
             creds_db[&amp;quot;host&amp;quot;],&lt;br /&gt;
         )&lt;br /&gt;
     except psycopg2.DatabaseError:&lt;br /&gt;
         logger.exception(&amp;quot;Database connection error: %s&amp;quot;)&lt;br /&gt;
         raise&lt;br /&gt;
     else:&lt;br /&gt;
         return connection, cursor_query, cursor_write&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def disconnect() -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Disconnect from the database.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     try:&lt;br /&gt;
         if cursor_query:&lt;br /&gt;
             cursor_query.close()&lt;br /&gt;
         if cursor_write:&lt;br /&gt;
             cursor_write.close()&lt;br /&gt;
         if connection:&lt;br /&gt;
             connection.close()&lt;br /&gt;
         logger.info(&amp;quot;Disconnected from the database&amp;quot;)&lt;br /&gt;
     except psycopg2.DatabaseError:&lt;br /&gt;
         logger.exception(&amp;quot;Error during disconnection: %s&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def exists(url: str, valid_hours: float) -&amp;gt; bool | None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Check if a record exists.&lt;br /&gt;
 &lt;br /&gt;
     Returns&lt;br /&gt;
     -------&lt;br /&gt;
     None for missing record&lt;br /&gt;
     True for valid record&lt;br /&gt;
     False for expired record&lt;br /&gt;
 &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=valid_hours)&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT delete_at &amp;gt; %s AS &amp;quot;valid&amp;quot;&lt;br /&gt;
 FROM my_table&lt;br /&gt;
 WHERE url = %s LIMIT 1;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_query.execute(sql, (delete_at, url))&lt;br /&gt;
     res = cursor_query.fetchone()&lt;br /&gt;
     return res[0] if res else None&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def query1(url: str) -&amp;gt; dict | None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Execute a query.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT *&lt;br /&gt;
 FROM my_table&lt;br /&gt;
 WHERE url = %s LIMIT 1&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_query.execute(sql, (url,))&lt;br /&gt;
     return cursor_query.fetchone()  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def insert(url: str, response: str, delete_in_hours: float) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Insert a record.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=delete_in_hours)&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 INSERT INTO my_table (url, response, delete_at)&lt;br /&gt;
 VALUES (%s, %s, %s);&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (url, response, delete_at))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def delete(url: str) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Delete a record.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 DELETE FROM my_table&lt;br /&gt;
 WHERE url = %s;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (url,))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def delete_expired(in_hours: float) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Delete records that expire in less than in_hours from now.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=in_hours)&lt;br /&gt;
 &lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 DELETE FROM my_table&lt;br /&gt;
 WHERE delete_at &amp;lt; %s;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (delete_at,))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 connection, cursor_query, cursor_write = connect()&lt;br /&gt;
&lt;br /&gt;
====Basics====&lt;br /&gt;
 import psycopg2&lt;br /&gt;
 import psycopg2.extras&lt;br /&gt;
 &lt;br /&gt;
 credentials = {&lt;br /&gt;
     &amp;quot;host&amp;quot;: &amp;quot;localhost&amp;quot;,&lt;br /&gt;
     &amp;quot;port&amp;quot;: 5432,&lt;br /&gt;
     &amp;quot;database&amp;quot;: &amp;quot;myDB&amp;quot;,&lt;br /&gt;
     &amp;quot;user&amp;quot;: &amp;quot;myUser&amp;quot;,&lt;br /&gt;
     &amp;quot;password&amp;quot;: &amp;quot;myPwd&amp;quot;,&lt;br /&gt;
 }&lt;br /&gt;
 connection = psycopg2.connect(**credentials)&lt;br /&gt;
 cursor = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)&lt;br /&gt;
 &lt;br /&gt;
 l_bind_vars = [[&amp;quot;A1&amp;quot;, &amp;quot;A2&amp;quot;], [&amp;quot;B1&amp;quot;, &amp;quot;B2&amp;quot;]]&lt;br /&gt;
 &lt;br /&gt;
 sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT * FROM myTable&lt;br /&gt;
 WHERE 1=1 &lt;br /&gt;
 AND status NOT IN (&#039;CLOSED&#039;) &lt;br /&gt;
 AND ColA = %s &lt;br /&gt;
 AND ColB = %s &lt;br /&gt;
 ORDER BY created DESC&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 cursor.execute(sql, l_bind_vars)&lt;br /&gt;
 d_data = dict(cursor.fetchone())&lt;br /&gt;
&lt;br /&gt;
====export result to csv file====&lt;br /&gt;
 sql1 = &amp;quot;SELECT * FROM table&amp;quot;&lt;br /&gt;
 sql2 = &amp;quot;COPY (&amp;quot; + sql1 + &amp;quot;) TO STDOUT WITH CSV HEADER DELIMITER &#039;\t&#039;&amp;quot;&lt;br /&gt;
         with open(&amp;quot;out.csv&amp;quot;, &amp;quot;w&amp;quot;) as file:&lt;br /&gt;
             cursor.copy_expert(sql2, file)&lt;br /&gt;
&lt;br /&gt;
====ReadConfigFile to connect to PostgreSQL====&lt;br /&gt;
from [http://www.postgresqltutorial.com/postgresql-python/connect/]&lt;br /&gt;
database.ini&lt;br /&gt;
 [postgresql]&lt;br /&gt;
 host=dbhost&lt;br /&gt;
 port=5432&lt;br /&gt;
 database=dbname&lt;br /&gt;
 user=dbuser&lt;br /&gt;
 password=dbpass&lt;br /&gt;
&lt;br /&gt;
 from configparser import ConfigParser&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def config(filename=&amp;quot;database.ini&amp;quot;, section=&amp;quot;postgresql&amp;quot;):&lt;br /&gt;
     parser = ConfigParser()&lt;br /&gt;
     parser.read(filename)&lt;br /&gt;
     # get section, default to postgresql&lt;br /&gt;
     db = {}&lt;br /&gt;
     if parser.has_section(section):&lt;br /&gt;
         params = parser.items(section)&lt;br /&gt;
         for param in params:&lt;br /&gt;
             db[param[0]] = param[1]&lt;br /&gt;
     else:&lt;br /&gt;
         raise Exception(&lt;br /&gt;
             &amp;quot;Section {0} not found in the {1} file&amp;quot;.format(section, filename)&lt;br /&gt;
         )&lt;br /&gt;
     return db&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
main.py&lt;br /&gt;
 import psycopg2&lt;br /&gt;
 from config import config&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def connect():&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Connect to the PostgreSQL database server&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     conn = None&lt;br /&gt;
     try:&lt;br /&gt;
         params = config()  # read connection parameters&lt;br /&gt;
         print(&amp;quot;Connecting to the PostgreSQL database...&amp;quot;)&lt;br /&gt;
         conn = psycopg2.connect(**params)&lt;br /&gt;
         cur = conn.cursor()  # create a cursor&lt;br /&gt;
         print(&amp;quot;PostgreSQL database version:&amp;quot;)&lt;br /&gt;
         cur.execute(&amp;quot;SELECT version()&amp;quot;)  # execute a statement&lt;br /&gt;
         db_version = cur.fetchone()&lt;br /&gt;
         print(db_version)&lt;br /&gt;
         cur.close()&lt;br /&gt;
     except (Exception, psycopg2.DatabaseError) as error:&lt;br /&gt;
         print(error)&lt;br /&gt;
     finally:&lt;br /&gt;
         if conn is not None:&lt;br /&gt;
             conn.close()&lt;br /&gt;
             print(&amp;quot;Database connection closed.&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     connect()&lt;br /&gt;
&lt;br /&gt;
===SQL Lite / SQLite===&lt;br /&gt;
see page [[SQLite]]&lt;br /&gt;
&lt;br /&gt;
===InfluxDB===&lt;br /&gt;
see [[InfluxDB]]&lt;br /&gt;
&lt;br /&gt;
==Internet Access==&lt;br /&gt;
===Send E-Mails===&lt;br /&gt;
see [[Python - eMail]]&lt;br /&gt;
&lt;br /&gt;
===Download data using browser UA / REST===&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 headers = {&lt;br /&gt;
     &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0&amp;quot;,&lt;br /&gt;
 }&lt;br /&gt;
 resp = requests.get(&lt;br /&gt;
     url,&lt;br /&gt;
     headers=headers,&lt;br /&gt;
     timeout=3,  # timeout in sec, requests should always have a timeout!&lt;br /&gt;
     # timeout=(1,3), # 1s connect-timout, 3s read-timeout&lt;br /&gt;
 )&lt;br /&gt;
 if resp.status_code != 200:  # noqa: PLR2004&lt;br /&gt;
     msg = f&amp;quot;E: bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
     raise Exception(msg)  # noqa: TRY002&lt;br /&gt;
&lt;br /&gt;
====Download only if cache is too old====&lt;br /&gt;
 import time&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 def fetch_url_or_cache(file_cache: Path, url) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Fetch URL and cache in a file.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if check_cache_file_available_and_recent(&lt;br /&gt;
         file_path=file_cache, max_age=3600, verbose=False&lt;br /&gt;
     ):&lt;br /&gt;
         with file_cache.open(mode=&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
             s = fh.read()&lt;br /&gt;
     else:&lt;br /&gt;
         s = fetch(url=url)&lt;br /&gt;
         with file_cache.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
             fh.writelines(s)&lt;br /&gt;
     return s&lt;br /&gt;
 &lt;br /&gt;
 def check_cache_file_available_and_recent(&lt;br /&gt;
     file_path: Path,&lt;br /&gt;
     max_age: int = 3500,&lt;br /&gt;
 ) -&amp;gt; bool:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Check if cache file exists and is recent.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cache_good = False&lt;br /&gt;
     if file_path.exists() and (time.time() - file_path.stat().st_mtime &amp;lt; max_age):&lt;br /&gt;
         cache_good = True&lt;br /&gt;
     return cache_good&lt;br /&gt;
 &lt;br /&gt;
 # or&lt;br /&gt;
 def check_cache_file_available_and_recent_verbose(&lt;br /&gt;
     file_path: Path, max_age: int = 3600, *, verbose: bool = False&lt;br /&gt;
 ) -&amp;gt; bool:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Check if cache file exists and is recent.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cache_good = True&lt;br /&gt;
     if not file_path.exists():&lt;br /&gt;
         if verbose:&lt;br /&gt;
             print(f&amp;quot;No Cache available: {file_path}&amp;quot;)&lt;br /&gt;
         cache_good = False&lt;br /&gt;
     if cache_good and time.time() - (time.time() - file_path.stat().st_mtime &amp;lt; max_age):&lt;br /&gt;
         if verbose:&lt;br /&gt;
             print(f&amp;quot;Cache too old: {file_path}&amp;quot;)&lt;br /&gt;
         cache_good = False&lt;br /&gt;
     return cache_good&lt;br /&gt;
 &lt;br /&gt;
 def fetch(url) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Fetch URL.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     headers = {&lt;br /&gt;
         &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0&amp;quot;,  # noqa: E501&lt;br /&gt;
     }&lt;br /&gt;
     resp = requests.get(url, headers=headers, timeout=5)&lt;br /&gt;
     if resp.status_code == 200:  # noqa: PLR2004&lt;br /&gt;
         return resp.content.decode(&amp;quot;ascii&amp;quot;)&lt;br /&gt;
     else:  # noqa: RET505&lt;br /&gt;
         msg = f&amp;quot;E: bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
         raise Exception(msg)  # noqa: TRY002&lt;br /&gt;
&lt;br /&gt;
====Download using urllib.request====&lt;br /&gt;
 from urllib.request import urlopen&lt;br /&gt;
 &lt;br /&gt;
 url = &amp;quot;https://pomber.github.io/covid19/timeseries.json&amp;quot;&lt;br /&gt;
 with urlopen(url) as response:  # noqa: S310&lt;br /&gt;
     response_text = response.read()&lt;br /&gt;
&lt;br /&gt;
===HTML parsing and extracting of elements===&lt;br /&gt;
V2: via BeautifulSoup&lt;br /&gt;
 from bs4 import BeautifulSoup  # pip install beautifulsoup4&lt;br /&gt;
 &lt;br /&gt;
 soup = BeautifulSoup(cont, features=&amp;quot;html.parser&amp;quot;)&lt;br /&gt;
 my_element = soup.find(&amp;quot;div&amp;quot;, {&amp;quot;class&amp;quot;: &amp;quot;user-formatted-inner&amp;quot;})&lt;br /&gt;
 my_body = my_element.prettify()&lt;br /&gt;
 # my_body = my_element.encode()&lt;br /&gt;
 # my_body = str(my_element)&lt;br /&gt;
&lt;br /&gt;
V1: via lxml and xpath&lt;br /&gt;
 from lxml import html&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 page = requests.get(url)&lt;br /&gt;
 tree = html.fromstring(page.content)&lt;br /&gt;
 tbody_trs = tree.xpath(&amp;quot;//*/tbody/tr&amp;quot;)&lt;br /&gt;
 l_rows = []&lt;br /&gt;
 for tr in tbody_trs:&lt;br /&gt;
     l_columns = []&lt;br /&gt;
     if len(tr) != 15:&lt;br /&gt;
         continue&lt;br /&gt;
     for td in tr:&lt;br /&gt;
         l_columns.append(td.text_content())&lt;br /&gt;
         l_rows.append(list(l_columns))&lt;br /&gt;
&lt;br /&gt;
===HTML entities to unicode===&lt;br /&gt;
 import html&lt;br /&gt;
 cont = html.unescape(cont)&lt;br /&gt;
&lt;br /&gt;
==GUI Interactions==&lt;br /&gt;
===Take Screenshot===&lt;br /&gt;
 import pyautogui # (c:\Python\Scripts\)pip install pyautogui&lt;br /&gt;
 # pyautogui does only support screenshots on monitor #1&lt;br /&gt;
 ...&lt;br /&gt;
 screenshot = pyautogui.screenshot()&lt;br /&gt;
 # screenshot = pyautogui.screenshot(region=(screenshotX,screenshotY, screenshotW, screenshotH))&lt;br /&gt;
 screenshot = np.array(screenshot) &lt;br /&gt;
 # Convert RGB to BGR &lt;br /&gt;
 screenshot = screenshot[:, :, ::-1].copy()&lt;br /&gt;
&lt;br /&gt;
===Mouse Actions===&lt;br /&gt;
 def clickIt(x,y,key=&amp;quot;&amp;quot;) :&lt;br /&gt;
   x0, y0 = pyautogui.position()&lt;br /&gt;
   if key != &amp;quot;&amp;quot;: # crtl, shift&lt;br /&gt;
     pyautogui.keyDown(key)&lt;br /&gt;
   pyautogui.moveTo(x, y, duration=0.2)&lt;br /&gt;
   pyautogui.click(x=x , y=y, button=&#039;left&#039;, clicks=1, interval=0.1)&lt;br /&gt;
   if key != &amp;quot;&amp;quot;: # crtl, shift&lt;br /&gt;
     pyautogui.keyUp(key)&lt;br /&gt;
   pyautogui.moveTo(x0, y0)&lt;br /&gt;
&lt;br /&gt;
===Web Automation===&lt;br /&gt;
 from selenium import webdriver&lt;br /&gt;
 from selenium.webdriver.common.keys import Keys&lt;br /&gt;
 &lt;br /&gt;
 # from selenium.webdriver import Firefox&lt;br /&gt;
 from selenium.webdriver.firefox.options import Options&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 import glob&lt;br /&gt;
 &lt;br /&gt;
 class StravaUserMapDL():&lt;br /&gt;
     def __init__(self):&lt;br /&gt;
         self.driver = webdriver.Firefox()&lt;br /&gt;
 &lt;br /&gt;
     def login(self):&lt;br /&gt;
         driver = self.driver&lt;br /&gt;
         url = &amp;quot;https://www.somewebpage.com&amp;quot;&lt;br /&gt;
         email = &amp;quot;myemail&amp;quot;&lt;br /&gt;
         password = &amp;quot;mypassword&amp;quot;&lt;br /&gt;
         driver.get(url)&lt;br /&gt;
 &lt;br /&gt;
         title = driver.title&lt;br /&gt;
         urlIs = driver.current_url&lt;br /&gt;
         cont = driver.page_source #  as string&lt;br /&gt;
         FILE = open(filename,&amp;quot;w&amp;quot;) # w = overWrite file ; a = append to file&lt;br /&gt;
         FILE.write(cont)&lt;br /&gt;
         FILE.close()         &lt;br /&gt;
 &lt;br /&gt;
         # handle login if urlIs != url&lt;br /&gt;
         if (urlIs != url): &lt;br /&gt;
             # activate checkbox &#039;remember_me&#039;&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;remember_me&#039;)&lt;br /&gt;
             if (elem.is_selected() == False):&lt;br /&gt;
                 elem.click()&lt;br /&gt;
             assert elem.is_selected() == True&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;email&#039;)&lt;br /&gt;
             elem.send_keys(email)&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;password&#039;)&lt;br /&gt;
             elem.send_keys(password)&lt;br /&gt;
             elem.send_keys(Keys.RETURN)&lt;br /&gt;
             # Wait until login pages is replaced by real page&lt;br /&gt;
             urlIs = driver.current_url&lt;br /&gt;
             while (urlIs != url):&lt;br /&gt;
                 time.sleep(1)&lt;br /&gt;
                 urlIs = driver.current_url&lt;br /&gt;
             print (urlIs)&lt;br /&gt;
 &lt;br /&gt;
             # results = driver.find_elements_by_class_name(&#039;following&#039;)&lt;br /&gt;
             # results = driver.find_elements_by_tag_name(&#039;li&#039;)&lt;br /&gt;
 &lt;br /&gt;
             # print(results[0].text)&lt;br /&gt;
         assert (urlIs == url)&lt;br /&gt;
&lt;br /&gt;
===Unit Tests using Web Automation===&lt;br /&gt;
 import unittest&lt;br /&gt;
 from selenium import webdriver&lt;br /&gt;
 from selenium.webdriver.common.keys import Keys&lt;br /&gt;
 &lt;br /&gt;
 #from selenium.webdriver import Firefox&lt;br /&gt;
 from selenium.webdriver.firefox.options import Options&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 class PythonOrgSearch(unittest.TestCase):&lt;br /&gt;
 #    def __init__(self,asdf):&lt;br /&gt;
 #        self.driver = webdriver.Firefox() &lt;br /&gt;
 &lt;br /&gt;
     def setUp(self):&lt;br /&gt;
         print (&amp;quot;setUp&amp;quot;)&lt;br /&gt;
         # headless mode:&lt;br /&gt;
         # opts = Options()&lt;br /&gt;
         # opts.set_headless()&lt;br /&gt;
         # assert opts.headless  # Operating in headless mode&lt;br /&gt;
         # self.driver = webdriver.Firefox(options=opts)&lt;br /&gt;
 &lt;br /&gt;
         self.driver = webdriver.Firefox()&lt;br /&gt;
 &lt;br /&gt;
     def test_search_in_python_org(self):&lt;br /&gt;
         driver = self.driver&lt;br /&gt;
         driver.get(&amp;quot;http://www.python.org&amp;quot;)&lt;br /&gt;
         self.assertIn(&amp;quot;Python&amp;quot;, driver.title)&lt;br /&gt;
         elem = driver.find_element_by_name(&amp;quot;q&amp;quot;)&lt;br /&gt;
         elem.send_keys(&amp;quot;pycon&amp;quot;)&lt;br /&gt;
         elem.send_keys(Keys.RETURN)&lt;br /&gt;
         assert &amp;quot;No results found.&amp;quot; not in driver.page_source&lt;br /&gt;
         print (&amp;quot;fertig: python_org&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     def tearDown(self):&lt;br /&gt;
         print (&amp;quot;tearDown&amp;quot;)&lt;br /&gt;
         print (&amp;quot;close Firefox&amp;quot;)&lt;br /&gt;
         self.driver.close() # close tab&lt;br /&gt;
         self.driver.quit() # quit browser&lt;br /&gt;
         # os._exit(1) # exit unittest without Exception&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     try:&lt;br /&gt;
         unittest.main()&lt;br /&gt;
     except SystemExit as e:&lt;br /&gt;
         os._exit(1)&lt;br /&gt;
&lt;br /&gt;
==Cryptography and Hashing==&lt;br /&gt;
====Hashing via SHA256====&lt;br /&gt;
 def gen_SHA256_string(s: str) -&amp;gt; str:&lt;br /&gt;
     m = hashlib.sha256()&lt;br /&gt;
     m.update(s.encode(&amp;quot;ascii&amp;quot;))&lt;br /&gt;
     return m.hexdigest()&lt;br /&gt;
&lt;br /&gt;
====Hashing via MD5====&lt;br /&gt;
(MD5 is not secure, better use SHA256)&lt;br /&gt;
 def gen_MD5_string(s: str) -&amp;gt; str:&lt;br /&gt;
     m = hashlib.md5()&lt;br /&gt;
     m.update(s.encode(&amp;quot;ascii&amp;quot;))&lt;br /&gt;
     return m.hexdigest()&lt;br /&gt;
&lt;br /&gt;
====Password hashing via bcrypt====&lt;br /&gt;
 import bcrypt&lt;br /&gt;
 pwd = &#039;geheim&#039;&lt;br /&gt;
 pwd = pwd.encode(&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 # or &lt;br /&gt;
 pwd = b&#039;geheim&#039;&lt;br /&gt;
 &lt;br /&gt;
 hashed = bcrypt.hashpw(pwd, bcrypt.gensalt())&lt;br /&gt;
 if bcrypt.checkpw(pwd, hashed):&lt;br /&gt;
     print(&amp;quot;It Matches!&amp;quot;)&lt;br /&gt;
     print(hashed.decode(&amp;quot;utf-8&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
To use version 2a instead of 2b (default):&lt;br /&gt;
 bcrypt.gensalt(prefix=b&amp;quot;2a&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Multiprocessing, subprocesses and Threading==&lt;br /&gt;
see [[Python_-_Multithreading]] as well&lt;br /&gt;
&lt;br /&gt;
use processes for CPU limited work &amp;lt;br/&amp;gt;&lt;br /&gt;
use threads for I/O limited work&lt;br /&gt;
&lt;br /&gt;
===Simple single process===&lt;br /&gt;
 import subprocess&lt;br /&gt;
 process = subprocess.run([&amp;quot;sudo&amp;quot;, &amp;quot;du&amp;quot;, &amp;quot;--max-depth=1&amp;quot;, mydir], capture_output=True, text=True)&lt;br /&gt;
 print (process.stdout)&lt;br /&gt;
old, depricated way:&lt;br /&gt;
 os.system( &amp;quot;gnuplot &amp;quot; + gpfile)&lt;br /&gt;
&lt;br /&gt;
===Multiprocessing===&lt;br /&gt;
see [[Python - Multithreading]] as well&lt;br /&gt;
&lt;br /&gt;
V2 using pool and starmap&lt;br /&gt;
 import multiprocessing&lt;br /&gt;
 import os&lt;br /&gt;
 &lt;br /&gt;
 def worker(i: int, s: str) -&amp;gt; list:&lt;br /&gt;
     result = (i, s, os.getpid())&lt;br /&gt;
     return result&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     # gen. pile of work&lt;br /&gt;
     l_pile_of_work = []&lt;br /&gt;
     for i in range(1_000):&lt;br /&gt;
         tup = (i, &amp;quot;n&amp;quot; + str(i))&lt;br /&gt;
         l_pile_of_work.append((tup))&lt;br /&gt;
     # gen pool of processes&lt;br /&gt;
     num_processes = min(multiprocessing.cpu_count(), len(l_pile_of_work))&lt;br /&gt;
     pool = multiprocessing.Pool(processes=num_processes)&lt;br /&gt;
     # start processes on pile of work&lt;br /&gt;
     l_results_unsorted = pool.starmap(&lt;br /&gt;
         func=worker, iterable=l_pile_of_work  # each item is a list of 2 parameters&lt;br /&gt;
     )&lt;br /&gt;
     &lt;br /&gt;
     # or if only one parameter:&lt;br /&gt;
     # l_results_unsorted = pool.map(doit_de_district, l_pile_of_work)&lt;br /&gt;
     &lt;br /&gt;
     l_results = sorted(l_results_unsorted)  # sort by i&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
V1&lt;br /&gt;
 import subprocess&lt;br /&gt;
 l_subprocesses = []  # queue list of subprocesses&lt;br /&gt;
 max_processes = 4&lt;br /&gt;
 &lt;br /&gt;
 def process_enqueue(new_process_parameters):&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     # wait for free slot&lt;br /&gt;
     while len(l_subprocesses) &amp;gt;= max_processes:&lt;br /&gt;
         process_remove_finished_from_queue()&lt;br /&gt;
         time.sleep(0.1)  # sleep 0.1s&lt;br /&gt;
     process = subprocess.Popen(new_process_parameters,&lt;br /&gt;
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE,&lt;br /&gt;
                                universal_newlines=True)&lt;br /&gt;
     l_subprocesses.append(process)&lt;br /&gt;
 &lt;br /&gt;
 def process_remove_finished_from_queue():&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     i = 0&lt;br /&gt;
     while i &amp;lt;= len(l_subprocesses) - 1:&lt;br /&gt;
         process = l_subprocesses[i]&lt;br /&gt;
         if process.poll != None:  # has already finished&lt;br /&gt;
             process_print_output(process)&lt;br /&gt;
             l_subprocesses.pop(i)&lt;br /&gt;
         else:  # still running&lt;br /&gt;
             i += 1&lt;br /&gt;
 &lt;br /&gt;
 def process_print_output(process):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;waits for process to finish and prints process output&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     stdout, stderr = process.communicate()&lt;br /&gt;
     if stdout != &#039;&#039;:&lt;br /&gt;
         print(f&#039;Out: {stdout}&#039;)&lt;br /&gt;
     if stderr != &#039;&#039;:&lt;br /&gt;
         print(f&#039;ERROR: {stderr}&#039;)&lt;br /&gt;
 &lt;br /&gt;
 def process_wait_for_all_finished():&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     for process in l_subprocesses:&lt;br /&gt;
         process_print_output(process)&lt;br /&gt;
     l_subprocesses = []  # empty list of done subprocesses&lt;br /&gt;
 &lt;br /&gt;
 process_enqueue(l_parameters1)&lt;br /&gt;
 ...&lt;br /&gt;
 process_enqueue(l_parameters999)&lt;br /&gt;
 process_wait_for_all_finished()&lt;br /&gt;
&lt;br /&gt;
===Threading===&lt;br /&gt;
 import threading&lt;br /&gt;
 import queue&lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 def worker(q_work: queue.Queue, results: dict):&lt;br /&gt;
     while not q_work.empty():&lt;br /&gt;
         i, s = q_work.get()&lt;br /&gt;
         time.sleep(.1)&lt;br /&gt;
         result = (i, s, os.getpid())&lt;br /&gt;
         results[i] = result&lt;br /&gt;
         q_work.task_done()&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &#039;__main__&#039;:&lt;br /&gt;
     d_results = {}  # threads can write into dict&lt;br /&gt;
     # gen. pile of work&lt;br /&gt;
     l_pile_of_work = []&lt;br /&gt;
     for i in range(1_000):&lt;br /&gt;
         tup = (i, &amp;quot;n&amp;quot;+str(i))&lt;br /&gt;
         l_pile_of_work.append((tup))&lt;br /&gt;
     # convert list of work to queue&lt;br /&gt;
     q_pile_of_work = queue.Queue(&lt;br /&gt;
         maxsize=len(l_pile_of_work))  # maxsize=0 -&amp;gt; unlimited&lt;br /&gt;
     for params in l_pile_of_work:&lt;br /&gt;
         q_pile_of_work.put(params)&lt;br /&gt;
     # gen threads&lt;br /&gt;
     num_threads = 100&lt;br /&gt;
     l_threads = []  # List of threads, not used here&lt;br /&gt;
     for i in range(num_threads):&lt;br /&gt;
         t = threading.Thread(name=&#039;myThread-&#039;+str(i),&lt;br /&gt;
                              target=worker,&lt;br /&gt;
                              args=(q_pile_of_work, d_results),&lt;br /&gt;
                              daemon=True)&lt;br /&gt;
         l_threads.append(t)&lt;br /&gt;
         t.start()&lt;br /&gt;
     q_pile_of_work.join()  # wait for all threas to complete&lt;br /&gt;
     l_results_unsorted = d_results.values()&lt;br /&gt;
     l_results = sorted(l_results_unsorted)  # sort by i&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===asyncio — Asynchronous I/O===&lt;br /&gt;
see https://docs.python.org/3/library/asyncio-task.html&lt;br /&gt;
 import asyncio&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 # basics&lt;br /&gt;
 # task = asyncio.create_task(coro())&lt;br /&gt;
 # Wrap the coro coroutine into a Task and schedule its execution. Return the Task object.&lt;br /&gt;
 &lt;br /&gt;
 # sleep&lt;br /&gt;
 # await asyncio.sleep(1)&lt;br /&gt;
 &lt;br /&gt;
 # Running Tasks Concurrently and gathers the return values in list L&lt;br /&gt;
 # L = await asyncio.gather(coro(x1,y1), coro(x2,y2), coro(x3,y3))&lt;br /&gt;
 &lt;br /&gt;
 async def say_after(delay, what):&lt;br /&gt;
     # Coroutines declared with the async/await syntax&lt;br /&gt;
     await asyncio.sleep(delay)&lt;br /&gt;
     print(what)&lt;br /&gt;
 &lt;br /&gt;
 async def main():&lt;br /&gt;
     # The asyncio.create_task() function to run coroutines concurrently as asyncio Tasks.&lt;br /&gt;
     task1 = asyncio.create_task(&lt;br /&gt;
         say_after(1, &#039;hello&#039;))&lt;br /&gt;
 &lt;br /&gt;
     task2 = asyncio.create_task(&lt;br /&gt;
         say_after(1, &#039;world&#039;))&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;started at {time.strftime(&#039;%X&#039;)}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     # Wait until both tasks are completed&lt;br /&gt;
     await task1&lt;br /&gt;
     await task2&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;finished at {time.strftime(&#039;%X&#039;)}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 asyncio.run(main())&lt;br /&gt;
&lt;br /&gt;
==Pandas==&lt;br /&gt;
see [[Pandas]]&lt;br /&gt;
&lt;br /&gt;
==Matplotlib==&lt;br /&gt;
see [[Matplotlib]]&lt;br /&gt;
&lt;br /&gt;
== GUI via tkinter==&lt;br /&gt;
 import tkinter as tk  # no need to install via pip&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 class App(tk.Tk):&lt;br /&gt;
     def __init__(self):&lt;br /&gt;
         super().__init__()&lt;br /&gt;
         self.title(&amp;quot;robot-CClicker&amp;quot;)&lt;br /&gt;
         self.geometry(&amp;quot;200x200+0+1080&amp;quot;)&lt;br /&gt;
         self.resizable(width=False, height=False)&lt;br /&gt;
 &lt;br /&gt;
         self.l_buttons = []&lt;br /&gt;
         self.__create_widgets()&lt;br /&gt;
 &lt;br /&gt;
     def __create_widgets(self):&lt;br /&gt;
         self.btn_click500 = tk.Button(&lt;br /&gt;
             master=self,&lt;br /&gt;
             text=&amp;quot;500 clicks&amp;quot;,&lt;br /&gt;
             width=20,&lt;br /&gt;
             command=lambda: self.clickBigCookie(500),&lt;br /&gt;
         )&lt;br /&gt;
         self.l_buttons.append(self.btn_click500)&lt;br /&gt;
 &lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button.pack(anchor=tk.W)&lt;br /&gt;
 &lt;br /&gt;
     def clickBigCookie(self, num):&lt;br /&gt;
         # TODO: the disabling of the buttons is not working&lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button[&amp;quot;state&amp;quot;] = tk.DISABLED&lt;br /&gt;
         helper.clickIt(self.posBigCockie[0], self.posBigCockie[1], num=num)&lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button[&amp;quot;state&amp;quot;] = tk.NORMAL&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     app = App()&lt;br /&gt;
     app.mainloop()&lt;br /&gt;
&lt;br /&gt;
==Protobuf==&lt;br /&gt;
===convert .proto file to python class===&lt;br /&gt;
see https://www.datascienceblog.net/post/programming/essential-protobuf-guide-python/&lt;br /&gt;
 protoc my_message.proto --python_out ./ &lt;br /&gt;
&lt;br /&gt;
===read/decode protobuf message===&lt;br /&gt;
parse message from file&lt;br /&gt;
 import machine_message_pb2&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.bin&amp;quot;, &amp;quot;rb&amp;quot;) as f:&lt;br /&gt;
     my_message = my_message_pb2.my_message()&lt;br /&gt;
     my_message.ParseFromString(f.read())&lt;br /&gt;
 print(machine_message)&lt;br /&gt;
&lt;br /&gt;
===create/encode protobuf message===&lt;br /&gt;
 import machine_message_pb2&lt;br /&gt;
 &lt;br /&gt;
 my_message = my_message_pb2.my_message()&lt;br /&gt;
 my_message.data.field1 = 1&lt;br /&gt;
 my_message.data.field2 = &amp;quot;asdf&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.bin&amp;quot;, &amp;quot;wb&amp;quot;) as f:&lt;br /&gt;
     f.write(my_message.data.SerializeToString())&lt;br /&gt;
&lt;br /&gt;
==venv / virtual environments==&lt;br /&gt;
 pip install --upgrade pip&lt;br /&gt;
 python -m venv .venv --prompt $(basename $(pwd))&lt;br /&gt;
 source .venv/bin/activate&lt;br /&gt;
 pip install -r requirements.txt&lt;br /&gt;
 ...&lt;br /&gt;
 deactivate&lt;br /&gt;
&lt;br /&gt;
==Pydantic data validation==&lt;br /&gt;
===Function input parameter validation===&lt;br /&gt;
see https://docs.pydantic.dev/usage/validation_decorator/&lt;br /&gt;
 @validate_arguments&lt;br /&gt;
 def sum(x: int, y: float) -&amp;gt; float:&lt;br /&gt;
     print(x, y)&lt;br /&gt;
     return x + y&lt;br /&gt;
  &lt;br /&gt;
 print(sum(1.1, 1.1))&lt;br /&gt;
 # -&amp;gt; 2.1&lt;br /&gt;
&lt;br /&gt;
==Read config file==&lt;br /&gt;
===TOML===&lt;br /&gt;
see https://learnxinyminutes.com/docs/toml/&lt;br /&gt;
&lt;br /&gt;
minimal example:&lt;br /&gt;
config.toml&lt;br /&gt;
 # SAP API endpoint&lt;br /&gt;
 [general]&lt;br /&gt;
 sleep_time = 60&lt;br /&gt;
 use_proxy = true&lt;br /&gt;
tool.py&lt;br /&gt;
 try:&lt;br /&gt;
     import tomllib  # comes with python3.11&lt;br /&gt;
 except ModuleNotFoundError:&lt;br /&gt;
     import tomli as tomllib  # pip install tomli&lt;br /&gt;
 with open(&amp;quot;config.toml&amp;quot;, &amp;quot;rb&amp;quot;) as f:&lt;br /&gt;
    o = tomllib.load(f)  # type: ignore&lt;br /&gt;
 if o[&amp;quot;settings&amp;quot;][&amp;quot;use_proxy&amp;quot;]:&lt;br /&gt;
 ...&lt;br /&gt;
for validation, see https://realpython.com/python-toml/&lt;br /&gt;
&lt;br /&gt;
====TOML Write====&lt;br /&gt;
In for deployments I sometime want to modify a TOML config file for production, based on the dev version.&lt;br /&gt;
 import tomli_w  # pip install tomli-w&lt;br /&gt;
 p_in = Path(__file__).parent.parent / &amp;quot;.streamlit/config.toml&amp;quot;&lt;br /&gt;
 p_out = p_in.parent / &amp;quot;config-prod.toml&amp;quot; &lt;br /&gt;
 with p_in.open(&amp;quot;rb&amp;quot;) as fh:&lt;br /&gt;
     o = tomllib.load(fh)&lt;br /&gt;
 o[&amp;quot;server&amp;quot;][&amp;quot;fileWatcherType&amp;quot;] = &amp;quot;none&amp;quot;&lt;br /&gt;
 del o[&amp;quot;server&amp;quot;][&amp;quot;address&amp;quot;]&lt;br /&gt;
  with p_out.open(&amp;quot;wb&amp;quot;) as fh:&lt;br /&gt;
     tomli_w.dump(o, fh)&lt;br /&gt;
&lt;br /&gt;
===ConfigParser: INI Config File Reading===&lt;br /&gt;
better use TOML&lt;br /&gt;
&lt;br /&gt;
Config.ini&lt;br /&gt;
 [Section1]&lt;br /&gt;
 Cursor         = 205E18&lt;br /&gt;
 Grandma        =  18E18&lt;br /&gt;
 Farm           =  11E18&lt;br /&gt;
 Mine           = 514E18&lt;br /&gt;
 Factory        = 155E18&lt;br /&gt;
test.py&lt;br /&gt;
 from configparser import ConfigParser&lt;br /&gt;
 &lt;br /&gt;
 config = ConfigParser(&lt;br /&gt;
     interpolation=None&lt;br /&gt;
 )  # interpolation=None -&amp;gt; treats % in values as char % instead of interpreting it&lt;br /&gt;
 config.read(&amp;quot;Config.ini&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(config.getint(&amp;quot;Section1&amp;quot;, &amp;quot;key1&amp;quot;))&lt;br /&gt;
 print(config.getfloat(&amp;quot;Section1&amp;quot;, &amp;quot;key2&amp;quot;))&lt;br /&gt;
 print(config.get(&amp;quot;Section1&amp;quot;, &amp;quot;key3&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 for sec in config.sections():&lt;br /&gt;
     d_settings = {}&lt;br /&gt;
     for key in config.options(sec):&lt;br /&gt;
         value = config.get(sec, key)&lt;br /&gt;
         d_settings[key] = value&lt;br /&gt;
         print(&amp;quot;%15s : %s&amp;quot; % (key, value))&lt;br /&gt;
&lt;br /&gt;
==VCard / vcf==&lt;br /&gt;
 import codecs&lt;br /&gt;
 import vobject  # pip install vobject&lt;br /&gt;
 &lt;br /&gt;
 obj = vobject.readComponents(codecs.open(file_in, encoding=&amp;quot;utf-8&amp;quot;).read())  # type: ignore&lt;br /&gt;
 contacts: list[vobject.base.Component] = list(obj)  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 card = contacts[0]&lt;br /&gt;
 &lt;br /&gt;
 if &amp;quot;bday&amp;quot; not in card.contents:&lt;br /&gt;
     continue&lt;br /&gt;
 &lt;br /&gt;
 # bday: remove &#039;VALUE&#039;: [&#039;DATE&#039;]&lt;br /&gt;
 card.contents[&amp;quot;bday&amp;quot;][0].params = {}  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 # remove all fields but &amp;quot;bday&amp;quot;, &amp;quot;n&amp;quot;&lt;br /&gt;
 for key in card.contents.copy():  # loop over copy, to allow for deleting keys&lt;br /&gt;
     if key not in (&amp;quot;n&amp;quot;, &amp;quot;bday&amp;quot;):&lt;br /&gt;
         del card.contents[key]&lt;br /&gt;
 &lt;br /&gt;
 # recreate fn based on n&lt;br /&gt;
 card.add(&amp;quot;fn&amp;quot;)&lt;br /&gt;
 n = card.contents[&amp;quot;n&amp;quot;][0]&lt;br /&gt;
 fn = f&amp;quot;{n.value.given} {n.value.additional} {n.value.family}&amp;quot;  # type: ignore&lt;br /&gt;
 fn = re.sub(r&amp;quot;\s+&amp;quot;, &amp;quot; &amp;quot;, fn)&lt;br /&gt;
 card.fn.value = fn  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.vcf&amp;quot;, mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fhOut:&lt;br /&gt;
     fhOut.write(card.serialize())&lt;br /&gt;
&lt;br /&gt;
==Sentry Exception monitoring==&lt;br /&gt;
see [[Sentry]]&lt;br /&gt;
==MQTT==&lt;br /&gt;
 #!/usr/bin/env python3.12&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Read power data from Tasmota device via MQTT.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 import json&lt;br /&gt;
 from typing import Any&lt;br /&gt;
 &lt;br /&gt;
 import paho.mqtt.client as mqtt&lt;br /&gt;
 from mqtt_credentials import hostname, password, port, username&lt;br /&gt;
 from paho.mqtt.reasoncodes import ReasonCode&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_connect(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     flags: dict[str, int],&lt;br /&gt;
     reason_code: ReasonCode,&lt;br /&gt;
     properties: Any,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when the client connects MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     if reason_code == 0:&lt;br /&gt;
         print(&amp;quot;Connected to MQTT&amp;quot;)&lt;br /&gt;
         # print(f&amp;quot;MQTT protocol version: {mqtt_client._protocol}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         # Subscribing in on_connect() means that if we lose the connection and&lt;br /&gt;
         # reconnect then subscriptions will be renewed.&lt;br /&gt;
         mqtt_client.message_callback_add(&amp;quot;tele/tasmota_MT681/SENSOR&amp;quot;, on_message)&lt;br /&gt;
         mqtt_client.subscribe([(&amp;quot;tele/tasmota_MT681/SENSOR&amp;quot;, 0)])&lt;br /&gt;
 &lt;br /&gt;
     else:&lt;br /&gt;
         print(f&amp;quot;Connection to MQTT broker failed. Error: {reason_code}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_disconnect(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     reason_code: ReasonCode,&lt;br /&gt;
     properties: Any,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when the client is disconnected from the MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     if reason_code &amp;gt; 0:&lt;br /&gt;
         print(f&amp;quot;Unexpected disconnection. Error: {reason_code}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_message(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     message: mqtt.MQTTMessage,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when a Tasmota message is received from the MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     s = message.payload.decode()&lt;br /&gt;
     data = json.loads(s)&lt;br /&gt;
     # {&#039;Time&#039;: &#039;2024-03-16T06:44:08&#039;, &#039;MT681&#039;: {&#039;Total_in&#039;: 16.4396, &#039;Power_cur&#039;: 80, &#039;Total_out&#039;: 14.6403}}  # noqa: E501&lt;br /&gt;
 &lt;br /&gt;
     total_i = data[&amp;quot;MT681&amp;quot;][&amp;quot;Total_in&amp;quot;]&lt;br /&gt;
     total_o = data[&amp;quot;MT681&amp;quot;][&amp;quot;Total_out&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;In:\t{total_i}\nOut:\t{total_o}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     mqtt_client.disconnect()&lt;br /&gt;
     mqtt_client.loop_stop()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 try:&lt;br /&gt;
     # Create an MQTT client instance&lt;br /&gt;
     mqtt_client: mqtt.Client = mqtt.Client(&lt;br /&gt;
         mqtt.CallbackAPIVersion.VERSION2, &amp;quot;Python-Client-1&amp;quot;&lt;br /&gt;
     )  # type: ignore&lt;br /&gt;
     mqtt_client.username_pw_set(username, password)&lt;br /&gt;
 &lt;br /&gt;
     mqtt_client.on_connect = on_connect&lt;br /&gt;
 &lt;br /&gt;
     # Connect to the MQTT broker&lt;br /&gt;
     mqtt_client.connect(hostname, port, keepalive=60)&lt;br /&gt;
 &lt;br /&gt;
     # NOT: while True, as that eats the CPU !!!&lt;br /&gt;
     mqtt_client.loop_forever()&lt;br /&gt;
 except KeyboardInterrupt:&lt;br /&gt;
     print(&amp;quot;KeyboardInterrupt, disconnecting from MQTT broker.&amp;quot;)&lt;br /&gt;
     mqtt_client.disconnect()&lt;br /&gt;
     mqtt_client.loop_stop()&lt;br /&gt;
&lt;br /&gt;
==Caching Functions==&lt;br /&gt;
 from functools import lru_cache&lt;br /&gt;
 &lt;br /&gt;
 @lru_cache(maxsize=128)  # None equals to @cache declarator&lt;br /&gt;
 def fnc(n:int):&lt;br /&gt;
&lt;br /&gt;
==Performance/Resources==&lt;br /&gt;
===RAM/Memory consumption/bottleneck===&lt;br /&gt;
 import tracemalloc&lt;br /&gt;
 tracemalloc.start()&lt;br /&gt;
 &lt;br /&gt;
 # ...&lt;br /&gt;
 # Code&lt;br /&gt;
 # example:&lt;br /&gt;
 lst = list(range(1_000_000))&lt;br /&gt;
 # ...&lt;br /&gt;
 &lt;br /&gt;
 max_bytes = tracemalloc.get_traced_memory()[0]&lt;br /&gt;
 print(f&amp;quot;{round(max_bytes / 10**6)}MB&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 tracemalloc.stop()&lt;br /&gt;
&lt;br /&gt;
===Profiling / Count and Runtime per Function===&lt;br /&gt;
 call_stats = {}&lt;br /&gt;
 &lt;br /&gt;
 def track_function_usage(func: Callable) -&amp;gt; Callable:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Annotation for gathering runtime statistics.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     @wraps(func)&lt;br /&gt;
     def wrapper(*args: tuple, **kwargs: dict):  # noqa: ANN202&lt;br /&gt;
         start_time = time.time()&lt;br /&gt;
         result = func(*args, **kwargs)  # Call the original function&lt;br /&gt;
         end_time = time.time()&lt;br /&gt;
         call_stats[func.__name__][&amp;quot;calls&amp;quot;] += 1&lt;br /&gt;
         call_stats[func.__name__][&amp;quot;total_time&amp;quot;] += end_time - start_time&lt;br /&gt;
         return result&lt;br /&gt;
     return wrapper&lt;br /&gt;
&lt;br /&gt;
==JWT Token==&lt;br /&gt;
  try:&lt;br /&gt;
      decoded_token = jwt.decode(token, options={&amp;quot;verify_signature&amp;quot;: False})&lt;br /&gt;
  except jwt.ExpiredSignatureError:&lt;br /&gt;
      msg = &amp;quot;Token has expired.&amp;quot;&lt;br /&gt;
      logger.exception(msg)&lt;br /&gt;
  except jwt.InvalidTokenError:&lt;br /&gt;
      msg = &amp;quot;Invalid token.&amp;quot;&lt;br /&gt;
      logger.exception(msg)&lt;br /&gt;
&lt;br /&gt;
==Streamlit==&lt;br /&gt;
see https://github.com/entorb/streamlit-examples for a collection of my examples&lt;br /&gt;
&lt;br /&gt;
===Minimum Example===&lt;br /&gt;
 # src/app.py&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import streamlit as st&lt;br /&gt;
 from streamlit.logger import get_logger&lt;br /&gt;
 logger = get_logger(Path(__file__).stem)&lt;br /&gt;
 logger.info(&amp;quot;Start&amp;quot;)&lt;br /&gt;
 st.set_page_config(page_title=&amp;quot;AppTitle&amp;quot;, page_icon=None, layout=&amp;quot;wide&amp;quot;)&lt;br /&gt;
 st.title(&amp;quot;MyPageTitle&amp;quot;)&lt;br /&gt;
 st.header(&amp;quot;MyHeader&amp;quot;)&lt;br /&gt;
 st.subheader(&amp;quot;MySubHeader&amp;quot;)&lt;br /&gt;
 sel_param = st.selectbox(&amp;quot;Parameter&amp;quot;, (&amp;quot;count&amp;quot;, &amp;quot;sum&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
 # .streamlit/config.toml&lt;br /&gt;
 [browser]&lt;br /&gt;
 gatherUsageStats = false&lt;br /&gt;
 &lt;br /&gt;
 [server]&lt;br /&gt;
 port = 8501&lt;br /&gt;
&lt;br /&gt;
 # run it&lt;br /&gt;
 streamlit run src/app.py&lt;br /&gt;
&lt;br /&gt;
====Reduce Logging for K8s Deployed Docker Containers====&lt;br /&gt;
 # reduce log output&lt;br /&gt;
 for logger_name in [&lt;br /&gt;
     &amp;quot;streamlit.runtime.caching.cache_utils&amp;quot;,&lt;br /&gt;
     &amp;quot;streamlit.runtime.caching.storage.in_memory_cache_storage_wrapper&amp;quot;,&lt;br /&gt;
     &amp;quot;streamlit.runtime.runtime&amp;quot;,&lt;br /&gt;
     &amp;quot;urllib3.connectionpool&amp;quot;,&lt;br /&gt;
 ]:&lt;br /&gt;
     get_logger(logger_name).setLevel(logging.WARNING)&lt;br /&gt;
&lt;br /&gt;
===Chart via Altair===&lt;br /&gt;
====Line Chart====&lt;br /&gt;
 chart = st.line_chart(df[&amp;quot;value&amp;quot;])&lt;br /&gt;
 # corresponds to&lt;br /&gt;
 import altair as alt&lt;br /&gt;
 c = (&lt;br /&gt;
    alt.Chart(df)&lt;br /&gt;
    .mark_line()&lt;br /&gt;
    .encode(&lt;br /&gt;
        x=alt.X(&amp;quot;created&amp;quot;, title=None),&lt;br /&gt;
        y=alt.Y(&amp;quot;value&amp;quot;, title=None),&lt;br /&gt;
    )&lt;br /&gt;
 )&lt;br /&gt;
 st.altair_chart(c, use_container_width=True)&lt;br /&gt;
&lt;br /&gt;
====Bar Chart====&lt;br /&gt;
 chart = (&lt;br /&gt;
     alt.Chart(df)&lt;br /&gt;
     .mark_bar()&lt;br /&gt;
     .encode(&lt;br /&gt;
         x=alt.X(&amp;quot;yearmonth(date):T&amp;quot;, title=&amp;quot;Month&amp;quot;),&lt;br /&gt;
         y=alt.Y(f&amp;quot;count:Q&amp;quot;, title=None),&lt;br /&gt;
         color=&amp;quot;status:N&amp;quot;,&lt;br /&gt;
         tooltip=[&amp;quot;yearmonth(date):T&amp;quot;, &amp;quot;status:N&amp;quot;, &amp;quot;cnt:Q&amp;quot;],&lt;br /&gt;
     )&lt;br /&gt;
     # .properties(width=600, height=400)&lt;br /&gt;
 )&lt;br /&gt;
 st.altair_chart(chart, use_container_width=True)&lt;br /&gt;
&lt;br /&gt;
 :T stands for Temporal. It indicates that the field contains date or time values.&lt;br /&gt;
 :N stands for Nominal. It indicates that the field contains categorical data, which represents discrete categories or labels.&lt;br /&gt;
 :Q stands for Quantitative. It indicates that the field contains numerical data that can be measured and compared.&lt;br /&gt;
&lt;br /&gt;
===Excel Download===&lt;br /&gt;
 def excel_download_buttons(df: pd.DataFrame, file_name: str = &amp;quot;export.xlsx&amp;quot;) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Show prepare data and download buttons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if st.button(label=&amp;quot;Click to prepare download&amp;quot;):&lt;br /&gt;
         buffer = io.BytesIO()&lt;br /&gt;
         with pd.ExcelWriter(buffer, engine=&amp;quot;xlsxwriter&amp;quot;) as writer:&lt;br /&gt;
             df.to_excel(writer, sheet_name=&amp;quot;Sheet1&amp;quot;, index=False)&lt;br /&gt;
             writer.close()&lt;br /&gt;
 &lt;br /&gt;
             st.download_button(&lt;br /&gt;
                 label=&amp;quot;Download data as Excel&amp;quot;,&lt;br /&gt;
                 data=buffer,&lt;br /&gt;
                 file_name=file_name,&lt;br /&gt;
                 mime=&amp;quot;application/vnd.ms-excel&amp;quot;,&lt;br /&gt;
             )&lt;br /&gt;
&lt;br /&gt;
==UV Package Manager==&lt;br /&gt;
 # create/update lockfile&lt;br /&gt;
 uv lock --upgrade&lt;br /&gt;
 # sync venv&lt;br /&gt;
 uv sync --upgrade&lt;br /&gt;
 # extract package info to requirements.txt&lt;br /&gt;
 uv export --format requirements.txt --no-dev --no-hashes -o requirements.txt&lt;br /&gt;
&lt;br /&gt;
Update Python version for my current project&lt;br /&gt;
 uv venv --python=3.13.7&lt;br /&gt;
&lt;br /&gt;
===Update UV===&lt;br /&gt;
 uv self update&lt;br /&gt;
 # or&lt;br /&gt;
 brew update &amp;amp;&amp;amp; brew upgrade uv&lt;br /&gt;
&lt;br /&gt;
===Update Python Versions===&lt;br /&gt;
 uv python upgrade&lt;br /&gt;
&lt;br /&gt;
===UV + Docker on readOnlyRootFilesystem===&lt;br /&gt;
Example 1: https://github.com/SWE-Group-23/CM22007---Source/blob/10d02004f3a4208f7b30d37a0a87e89532a23575/create-service.sh#L117&lt;br /&gt;
&lt;br /&gt;
Dockerfile&lt;br /&gt;
 WORKDIR /app&lt;br /&gt;
 COPY . .&lt;br /&gt;
 &lt;br /&gt;
 ENV CASS_DRIVER_NO_EXTENSIONS=1&lt;br /&gt;
 &lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache/uv \\&lt;br /&gt;
     --mount=type=bind,source=uv.lock,target=uv.lock \\&lt;br /&gt;
     --mount=type=bind,source=pyproject.toml,target=pyproject.toml \\&lt;br /&gt;
     uv sync --frozen --no-install-project&lt;br /&gt;
     &lt;br /&gt;
 RUN uv pip install -e shared/&lt;br /&gt;
 &lt;br /&gt;
 RUN addgroup -S group1 &amp;amp;&amp;amp; adduser -S user1 -G group1 -u 1000&lt;br /&gt;
 RUN chown -R user1:group1 /app&lt;br /&gt;
 USER user1:group1&lt;br /&gt;
 &lt;br /&gt;
 CMD [&amp;quot;uv&amp;quot;, &amp;quot;run&amp;quot;, &amp;quot;main.py&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
Deployment&lt;br /&gt;
 securityContext:&lt;br /&gt;
   readOnlyRootFilesystem: true&lt;br /&gt;
   allowPrivilegeEscalation: false&lt;br /&gt;
   runAsNonRoot: true&lt;br /&gt;
   runAsUser: 1000&lt;br /&gt;
   capabilities:&lt;br /&gt;
     drop:&lt;br /&gt;
       - ALL&lt;br /&gt;
   seccompProfile:&lt;br /&gt;
     type: RuntimeDefault&lt;br /&gt;
 volumeMounts:&lt;br /&gt;
   - mountPath: /home/user1/.cache/uv&lt;br /&gt;
     name: uv&lt;br /&gt;
   - mountPath: /tmp&lt;br /&gt;
     name: temp&lt;br /&gt;
&lt;br /&gt;
Example 2: [https://hynek.me/articles/docker-uv/ Production-ready Python Docker Containers with uv]&lt;br /&gt;
&lt;br /&gt;
Dockerfile (without the comments)&lt;br /&gt;
 ENV UV_LINK_MODE=copy \&lt;br /&gt;
     UV_COMPILE_BYTECODE=1 \&lt;br /&gt;
     UV_PYTHON_DOWNLOADS=never \&lt;br /&gt;
     UV_PYTHON=python3.12 \&lt;br /&gt;
     UV_PROJECT_ENVIRONMENT=/app&lt;br /&gt;
 &lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache \&lt;br /&gt;
     --mount=type=bind,source=uv.lock,target=uv.lock \&lt;br /&gt;
     --mount=type=bind,source=pyproject.toml,target=pyproject.toml \&lt;br /&gt;
     uv sync \&lt;br /&gt;
         --locked \&lt;br /&gt;
         --no-dev \&lt;br /&gt;
         --no-install-project&lt;br /&gt;
 &lt;br /&gt;
 COPY . /src&lt;br /&gt;
 WORKDIR /src&lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache \&lt;br /&gt;
     uv sync \&lt;br /&gt;
         --locked \&lt;br /&gt;
         --no-dev \&lt;br /&gt;
         --no-editable&lt;br /&gt;
&lt;br /&gt;
== Vulture: Search for dead code ==&lt;br /&gt;
see [https://github.com/jendrikseipp/vulture GitHub]&lt;br /&gt;
&lt;br /&gt;
pyproject.toml&lt;br /&gt;
 [tool.vulture]&lt;br /&gt;
 paths = [&amp;quot;src&amp;quot;]&lt;br /&gt;
 # exclude = [&amp;quot;src/models.py&amp;quot;]&lt;br /&gt;
 ignore_names = [&lt;br /&gt;
     &amp;quot;LOGGER&amp;quot;,&lt;br /&gt;
 ]&lt;br /&gt;
 # ignore_decorators = []&lt;br /&gt;
&lt;br /&gt;
.pre-commit-config.yaml&lt;br /&gt;
   # Vulture: find dead code&lt;br /&gt;
   - repo: https://github.com/jendrikseipp/vulture&lt;br /&gt;
     rev: v2.16&lt;br /&gt;
     hooks:&lt;br /&gt;
       - id: vulture&lt;br /&gt;
         pass_filenames: false&lt;br /&gt;
&lt;br /&gt;
run&lt;br /&gt;
 pip install vulture&lt;br /&gt;
 vulture src/&lt;br /&gt;
 &lt;br /&gt;
 uv add --dev vulture&lt;br /&gt;
 uv run vulture src/&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Python&amp;diff=5408</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5408"/>
		<updated>2026-04-13T12:35:17Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Vulture: Search for dead code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Coding]][[Category:Python]]&lt;br /&gt;
&lt;br /&gt;
==Getting Started==&lt;br /&gt;
===Install===&lt;br /&gt;
====Python====&lt;br /&gt;
Best use [https://docs.astral.sh/uv/ UV] for managing python and package versions.&lt;br /&gt;
&lt;br /&gt;
* for Windows: get and install Python from https://www.python.org&lt;br /&gt;
* for MacOS: use [https://github.com/pyenv/pyenv?tab=readme-ov-file#a-getting-pyenv pyenv]&lt;br /&gt;
 brew install xz &lt;br /&gt;
 brew install pyenv&lt;br /&gt;
 &lt;br /&gt;
 echo &#039;export PYENV_ROOT=&amp;quot;$HOME/.pyenv&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 echo &#039;[ [ -d $PYENV_ROOT/bin ] ] &amp;amp;&amp;amp; export PATH=&amp;quot;$PYENV_ROOT/bin:$PATH&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 echo &#039;eval &amp;quot;$(pyenv init - zsh)&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 exec &amp;quot;$SHELL&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 pyenv install --list&lt;br /&gt;
 pyenv install 3.13.5&lt;br /&gt;
 pyenv global 3.13.5&lt;br /&gt;
 &lt;br /&gt;
 vim ~/.zshrc &lt;br /&gt;
 # add &lt;br /&gt;
 if command -v pyenv 1&amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then&lt;br /&gt;
   eval &amp;quot;$(pyenv init -)&amp;quot;&lt;br /&gt;
 fi&lt;br /&gt;
&lt;br /&gt;
====Editor: Visual Studio Code====&lt;br /&gt;
excellent and free source-code editor that supports many languages.&lt;br /&gt;
* get and install from https://code.visualstudio.com&lt;br /&gt;
&lt;br /&gt;
See Wickie page [[Visual_Studio_Code]] for general setup, extension, config...&lt;br /&gt;
&lt;br /&gt;
Python Extensions&lt;br /&gt;
* Python&lt;br /&gt;
* Pylance&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff Ruff Formater and Linter]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items/?itemName=tamasfe.even-better-toml Even Better TOML]&lt;br /&gt;
Settings (CTRL + ,)&lt;br /&gt;
* Extensions -&amp;gt; Python -&amp;gt; Formatting: Provider = black&lt;br /&gt;
* Text Editor -&amp;gt; Editor: Format On Save&lt;br /&gt;
* Text Editor -&amp;gt; Files:Eol -&amp;gt; \n&lt;br /&gt;
* Linter: Ruff&lt;br /&gt;
Settings in settings.json&lt;br /&gt;
 &amp;quot;python.analysis.completeFunctionParens&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.autoImportCompletions&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.inlayHints.functionReturnTypes&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.typeCheckingMode&amp;quot;: &amp;quot;strict&amp;quot;,&lt;br /&gt;
 // Ruff&lt;br /&gt;
 &amp;quot;[python]&amp;quot;: {&lt;br /&gt;
   &amp;quot;editor.defaultFormatter&amp;quot;: &amp;quot;charliermarsh.ruff&amp;quot;,&lt;br /&gt;
   &amp;quot;editor.formatOnSave&amp;quot;: true,&lt;br /&gt;
   &amp;quot;editor.codeActionsOnSave&amp;quot;: {&lt;br /&gt;
     &amp;quot;source.fixAll&amp;quot;: &amp;quot;explicit&amp;quot;,&lt;br /&gt;
     &amp;quot;source.organizeImports.ruff&amp;quot;: &amp;quot;explicit&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 },&lt;br /&gt;
&lt;br /&gt;
Run your code&lt;br /&gt;
 CTRL + F5 : run&lt;br /&gt;
 F5        : run in debugger&lt;br /&gt;
&lt;br /&gt;
====Code Formatter Ruff====&lt;br /&gt;
use software for handling the code formatting like &amp;quot;ruff&amp;quot; or &amp;quot;black&amp;quot;, where Ruff is much faster and additionally provides linting.&lt;br /&gt;
 pip install ruff&lt;br /&gt;
than activate in editor like vs code&lt;br /&gt;
&lt;br /&gt;
===My Template===&lt;br /&gt;
see latest version at https://github.com/entorb/template-python&lt;br /&gt;
&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Main file.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 # by Torben Menke https://entorb.net &lt;br /&gt;
 import logging&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 def init_logging() -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Initialize logging.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     logging.addLevelName(logging.DEBUG, &amp;quot;D&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.INFO, &amp;quot;I&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.WARNING, &amp;quot;W&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.ERROR, &amp;quot;E&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.CRITICAL, &amp;quot;C&amp;quot;)&lt;br /&gt;
     logging.basicConfig(&lt;br /&gt;
         level=logging.INFO, format=&amp;quot;%(levelname)s: %(name)s: %(message)s&amp;quot;&lt;br /&gt;
     )&lt;br /&gt;
     logging.getLogger(&amp;quot;httpx&amp;quot;).setLevel(logging.WARNING)&lt;br /&gt;
 &lt;br /&gt;
 init_logging()&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
 &lt;br /&gt;
 def main():&lt;br /&gt;
     LOGGER.info(&amp;quot;Moin Moin&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     main()&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Other file&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 import logging&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
&lt;br /&gt;
===Compile to .exe===&lt;br /&gt;
 pip install pyinstaller&lt;br /&gt;
 pyinstaller --onefile --console your.py&lt;br /&gt;
 # for Excel and Matplotlib these options are required&lt;br /&gt;
 --hidden-import=openpyxl --hidden-import=matplotlib --hidden-import pandas.plotting._matplotlib &lt;br /&gt;
([[Python - py2exe]] is deprecated)&lt;br /&gt;
&lt;br /&gt;
==Basics==&lt;br /&gt;
=== Naming Conventions===&lt;br /&gt;
[https://google.github.io/styleguide/pyguide.html Google Python Style Guide]:&lt;br /&gt;
 module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_CONSTANT_NAME, global_var_name, instance_var_name, function_parameter_name, local_var_name&lt;br /&gt;
&lt;br /&gt;
====Doc Strings====&lt;br /&gt;
It is best practice to start a file with a docstring:&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;My Script&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
This can be accessed linke this:&lt;br /&gt;
 title = __doc__[:-1]  # type: ignore&lt;br /&gt;
&lt;br /&gt;
===Installing packages===&lt;br /&gt;
 python -m pip install --upgrade pip&lt;br /&gt;
 &lt;br /&gt;
 pip install somemodule&lt;br /&gt;
 # or &lt;br /&gt;
 pip3 install somemodule&lt;br /&gt;
 # or read from file&lt;br /&gt;
 pip install -r requirements.txt&lt;br /&gt;
 &lt;br /&gt;
 # uninstall&lt;br /&gt;
 pip uninstall somemodule&lt;br /&gt;
 &lt;br /&gt;
 # using a web proxy&lt;br /&gt;
 # set proxy for windows cmd session&lt;br /&gt;
 SET HTTPS_PROXY=http://myProxy:8080&lt;br /&gt;
 (afterwards --proxy setting below no longer required&lt;br /&gt;
 or&lt;br /&gt;
 pip install --proxy http://myProxy:8080 somemodule&lt;br /&gt;
 &lt;br /&gt;
 # list outdated packages&lt;br /&gt;
 pip list --outdated&lt;br /&gt;
 &lt;br /&gt;
 # update package&lt;br /&gt;
 pip install --upgrade pyinstaller&lt;br /&gt;
 &lt;br /&gt;
 # updating all via Windows Powershell (from [https://thecesrom.dev/2021/05/26/the-one-liner-for-updating-pip-and-all-outdated-packages/])&lt;br /&gt;
 pip freeze | %{$_.split(&#039;==&#039;)[0]} | %{pip install --upgrade $_}&lt;br /&gt;
 &lt;br /&gt;
 # updating all via Bash (from [https://thecesrom.dev/2021/05/26/the-one-liner-for-updating-pip-and-all-outdated-packages/])&lt;br /&gt;
 pip freeze | grep -v &#039;^\-e&#039; | cut -d = -f 1  | xargs -n1 pip install --upgrade&lt;br /&gt;
 &lt;br /&gt;
 # downgrade&lt;br /&gt;
 pip install --upgrade pandas==1.2.4&lt;br /&gt;
&lt;br /&gt;
====Oneline Updater for requirements.txt====&lt;br /&gt;
https://pkgui.com/pip&lt;br /&gt;
&lt;br /&gt;
====update packages====&lt;br /&gt;
 pip install pip-review&lt;br /&gt;
 pip-review --auto&lt;br /&gt;
&lt;br /&gt;
====generate requirements.txt====&lt;br /&gt;
 pip install pipreqs&lt;br /&gt;
 pipreqs ./&lt;br /&gt;
&lt;br /&gt;
===Loops===&lt;br /&gt;
ATTENTION: The loops do not create a new variable scope. Only functions and modules introduce a new scope!&lt;br /&gt;
 for p in all_files:&lt;br /&gt;
     print(p)&lt;br /&gt;
 &lt;br /&gt;
 for i, p in enumerate(all_files):&lt;br /&gt;
     print(i, p)&lt;br /&gt;
 &lt;br /&gt;
 for i in range(10):&lt;br /&gt;
     print(i)&lt;br /&gt;
 # del i &lt;br /&gt;
 &lt;br /&gt;
 while i &amp;lt;= 100:&lt;br /&gt;
     i += 1&lt;br /&gt;
     ...&lt;br /&gt;
     if sth1:&lt;br /&gt;
         continue # start next loop&lt;br /&gt;
     if sth2:&lt;br /&gt;
         break # exit loop&lt;br /&gt;
 &lt;br /&gt;
 # inline if (requires a dummy else):&lt;br /&gt;
 print(&amp;quot;something&amp;quot;) if sth else 0&lt;br /&gt;
&lt;br /&gt;
===Math===&lt;br /&gt;
see [[Python - Math]] for linear regression&lt;br /&gt;
&lt;br /&gt;
Modulo&lt;br /&gt;
 15 % 4&lt;br /&gt;
 # &amp;gt; 3&lt;br /&gt;
&lt;br /&gt;
Integer Division&lt;br /&gt;
 17 // 4&lt;br /&gt;
 # &amp;gt; 4&lt;br /&gt;
&lt;br /&gt;
====Random====&lt;br /&gt;
 import random&lt;br /&gt;
 random.randint(1000000, 9999999)&lt;br /&gt;
&lt;br /&gt;
==Basic Objects==&lt;br /&gt;
===Variables===&lt;br /&gt;
 del var  # delete / undef a variable&lt;br /&gt;
 var = None  # sets to null&lt;br /&gt;
 &lt;br /&gt;
 # check if variable is defined&lt;br /&gt;
 if &amp;quot;var&amp;quot; in locals():&lt;br /&gt;
     pass&lt;br /&gt;
 # for object oriented projects:&lt;br /&gt;
 if &amp;quot;var&amp;quot; in self.__dict__:&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # Access global variables in functions&lt;br /&gt;
 var = 123&lt;br /&gt;
 &lt;br /&gt;
 def test() -&amp;gt; None:&lt;br /&gt;
     global var  # point to global instead of creation of local var&lt;br /&gt;
     var = 321&lt;br /&gt;
&lt;br /&gt;
===Strings===&lt;br /&gt;
 # num &amp;lt;-&amp;gt; str&lt;br /&gt;
 s = str(i)  # int to string&lt;br /&gt;
 f = float(s)  # str -&amp;gt; float&lt;br /&gt;
 i = int(s)&lt;br /&gt;
 str(round(f, 1))  # round first&lt;br /&gt;
 # tests&lt;br /&gt;
 s.isdigit()  # 0-9&lt;br /&gt;
 # note isdecimal() does also not match &#039;1.1&#039;&lt;br /&gt;
 &lt;br /&gt;
 # printf: 1 digit&lt;br /&gt;
 s = f&amp;quot;{value:0.1f}&amp;quot;&lt;br /&gt;
 s = &amp;quot;%0.1f&amp;quot; % value&lt;br /&gt;
&lt;br /&gt;
====Modify Strings==== &lt;br /&gt;
 # get string from prompt&lt;br /&gt;
 s = input(&amp;quot;Enter Text: &amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 s = s.strip()  # trim spaces from both sides, rstrip for right only&lt;br /&gt;
 s = s.lower()  # lower case&lt;br /&gt;
 s = s.upper()  # upper case&lt;br /&gt;
 s = s.title()  # upper case for first char of word&lt;br /&gt;
 &lt;br /&gt;
 # upper case first letter of each word and also removes multiple and trailing spaces&lt;br /&gt;
 import string&lt;br /&gt;
 s = string.capwords(s)&lt;br /&gt;
 &lt;br /&gt;
 # replace&lt;br /&gt;
 s.replace(x, y)&lt;br /&gt;
 &lt;br /&gt;
 # trim whitespaces from left and right&lt;br /&gt;
 s.strip()&lt;br /&gt;
 &lt;br /&gt;
 # replace all (multiple) whitespaces by single space &#039; &#039;&lt;br /&gt;
 s = &amp;quot; &amp;quot;.join(s.split())&lt;br /&gt;
 &lt;br /&gt;
 # generate key value pairs from dict&lt;br /&gt;
 # key1=value1&amp;amp;key2=value2&lt;br /&gt;
 param_str = &amp;quot;&amp;amp;&amp;quot;.join(&amp;quot;=&amp;quot;.join(tup) for tup in dict.items())&lt;br /&gt;
 &lt;br /&gt;
 # repeat string multiple times&lt;br /&gt;
 s * 5  # = s+s+s+s+s&lt;br /&gt;
&lt;br /&gt;
====Substrings====&lt;br /&gt;
 # find a substring:&lt;br /&gt;
 if x in s:  # True / False&lt;br /&gt;
     pass&lt;br /&gt;
 if len(s) &amp;gt; 0:&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # handling substrings&lt;br /&gt;
 a = &amp;quot;abcd&amp;quot;&lt;br /&gt;
 b = a[:1] + &amp;quot;o&amp;quot; + a[2:]&lt;br /&gt;
 # &amp;gt; &#039;aocd&#039;&lt;br /&gt;
 &lt;br /&gt;
 s = &amp;quot;Hello there !bob@&amp;quot;&lt;br /&gt;
 i1 = s.find(&amp;quot;!&amp;quot;) + 1&lt;br /&gt;
 i2 = s.find(&amp;quot;@&amp;quot;)&lt;br /&gt;
 substr = s[i1:i2]&lt;br /&gt;
 &lt;br /&gt;
 def substr_between(s: str, s1: str, s2: str) -&amp;gt; str:&lt;br /&gt;
     assert s1 in s, f&amp;quot;E: can&#039;t find &#039;{s1}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     assert s2 in s, f&amp;quot;E: can&#039;t find &#039;{s1}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     i1 = s.find(s1) + len(s1)&lt;br /&gt;
     i2 = s.find(s2)&lt;br /&gt;
     assert i1 &amp;lt; i2, f&amp;quot;E: &#039;{s1}&#039; not before &#039;{s2}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     return s[i1:i2]&lt;br /&gt;
&lt;br /&gt;
====Binary, raw strings, html encoding====&lt;br /&gt;
 # Binary Strings&lt;br /&gt;
 sb = b&amp;quot;asdf&amp;quot;&lt;br /&gt;
 # or&lt;br /&gt;
 sb = str.encode(&amp;quot;asdf&amp;quot;)&lt;br /&gt;
 s = sb.decode(&amp;quot;ascii&amp;quot;)  # decode binary strings&lt;br /&gt;
 sb = s.encode(&amp;quot;ascii&amp;quot;)  # encode string to binary&lt;br /&gt;
 &lt;br /&gt;
 # raw string&lt;br /&gt;
 s = r&amp;quot;c:\Windows&amp;quot;  # no escape of \  needed&lt;br /&gt;
 &lt;br /&gt;
 # convert utf-8 to html umlaute&lt;br /&gt;
 s = &amp;quot;Nürnberg&amp;quot;.encode(&amp;quot;ascii&amp;quot;, &amp;quot;xmlcharrefreplace&amp;quot;).decode()&lt;br /&gt;
 # -&amp;gt; N&amp;amp;#252;rnberg&lt;br /&gt;
&lt;br /&gt;
====Guess encoding via chardet====&lt;br /&gt;
 import chardet  # pip install chardet&lt;br /&gt;
 result = chardet.detect(raw_data)&lt;br /&gt;
 if result[&amp;quot;encoding&amp;quot;] and result[&amp;quot;confidence&amp;quot;] &amp;gt; 0.5:  # noqa: PLR2004&lt;br /&gt;
     encoding = result[&amp;quot;encoding&amp;quot;]&lt;br /&gt;
 else:&lt;br /&gt;
     encoding = &amp;quot;utf-8&amp;quot;&lt;br /&gt;
&lt;br /&gt;
there is a much faster alternative [https://github.com/PyYoshi/cChardet cchardet], but that requires Microsoft Visual C++ 14.0&lt;br /&gt;
&lt;br /&gt;
====Merge variables in string / sprintf====&lt;br /&gt;
 print(&amp;quot;Renner =&amp;quot;, i)&lt;br /&gt;
 print(&amp;quot;Renner = %3d&amp;quot; % i)  # leading 0&#039;s&lt;br /&gt;
 print(f&amp;quot;Renner = {i}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # place formatted numbers in a string / sprintf&lt;br /&gt;
 s = &amp;quot;The {:.1f}% {} cost {:05.2f} euros&amp;quot;.format( 5.1, &amp;quot;beer&amp;quot;, 3.50)&lt;br /&gt;
 print(s)&lt;br /&gt;
 # &amp;gt; The 5.1% beer cost 03.50 euros&lt;br /&gt;
 &lt;br /&gt;
 s = f&amp;quot;The length is {72.8958:.2f} meters&amp;quot;&lt;br /&gt;
 # &amp;gt; The length is 72.90 meters&lt;br /&gt;
&lt;br /&gt;
===Lists===&lt;br /&gt;
like @array in Perl&lt;br /&gt;
 lst = [1, 2, 3, 4, 5, 6]&lt;br /&gt;
 lst = [x / 2 for x in range(10)]&lt;br /&gt;
 lst = [None] * 10 # Initiate list of None elements:&lt;br /&gt;
 len(lst) # length&lt;br /&gt;
 lst2 = lst[0:10]  # get elements 0-10&lt;br /&gt;
 for i in lst:&lt;br /&gt;
     print(i)&lt;br /&gt;
&lt;br /&gt;
====clone list====&lt;br /&gt;
 # dangerous: creates a link to the original list&lt;br /&gt;
 list_2 = my_list  # M&#039;s elements are LINKS to L&#039;s&lt;br /&gt;
 # clones can be achieved via:&lt;br /&gt;
 list_2 = my_list.copy&lt;br /&gt;
 list_2 = my_list[:]&lt;br /&gt;
 list_2 = list(my_list)&lt;br /&gt;
&lt;br /&gt;
====list modifications====&lt;br /&gt;
 lst.pop()  # returns and removes the last item&lt;br /&gt;
 lst.pop(i)  # returns and removes the item at  position int i&lt;br /&gt;
 lst.insert(i, x)  # insert item x at position int i&lt;br /&gt;
 lst.remove(x)  # removes the first occurrence of  item x&lt;br /&gt;
 lst.append(x)  # append a single element&lt;br /&gt;
 lst.extend(m)  # append elements of another list&lt;br /&gt;
 &lt;br /&gt;
 lst.reverse()  # reverse the order of the list&lt;br /&gt;
 lst = sorted([&amp;quot;B&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;c&amp;quot;], key=str.casefold)  #  case insensitive / ignore case&lt;br /&gt;
 &lt;br /&gt;
 # list to string&lt;br /&gt;
 s = &amp;quot;&amp;quot;.join(lst)&lt;br /&gt;
 s = &amp;quot;\n&amp;quot;.join(lst)&lt;br /&gt;
 # string to list&lt;br /&gt;
 lst = s.split()  # default: split on space&lt;br /&gt;
 lst = s.split(&amp;quot;,&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # remove empty values from end&lt;br /&gt;
 while L[-1] == &amp;quot;&amp;quot;:&lt;br /&gt;
     L.pop()&lt;br /&gt;
 &lt;br /&gt;
 # check and remove items from list&lt;br /&gt;
  # from https://stackoverflow.com/a/6024599&lt;br /&gt;
  # iterates in-situ via reversed index&lt;br /&gt;
 for i in range(len(lst) - 1, -1, -1):&lt;br /&gt;
     element = lst[i]&lt;br /&gt;
     if check(element):&lt;br /&gt;
         del lst[i]&lt;br /&gt;
&lt;br /&gt;
====check if item is in list====&lt;br /&gt;
 x in lst&lt;br /&gt;
 x not in lst&lt;br /&gt;
 lst.count(x)  # how many items x are in the list&lt;br /&gt;
 i = lst.index(&amp;quot;word&amp;quot;)  # find in list / returns the  position of the first match in list&lt;br /&gt;
&lt;br /&gt;
====pair 2 lists====&lt;br /&gt;
 # zip: merge 2 lists to list of tuples&lt;br /&gt;
 data = list(zip(data_x, data_y, strict=True))&lt;br /&gt;
 &lt;br /&gt;
 # unzip: split list of pairs into 2 lists&lt;br /&gt;
 data_x, data_y = zip(*data, strict=True)&lt;br /&gt;
 &lt;br /&gt;
 # Cartesian product of lists / tuples&lt;br /&gt;
 import itertools&lt;br /&gt;
 for i in itertools.product(*list_of_lists):&lt;br /&gt;
     print(i)&lt;br /&gt;
&lt;br /&gt;
====sort multidim list====&lt;br /&gt;
 lst = sorted(lst, key=lambda x: x[0], reverse=False)&lt;br /&gt;
 lst = sorted(lst, key=lambda row: (row[&amp;quot;Wann&amp;quot;], row [&amp;quot;Wer&amp;quot;]), reverse=False)&lt;br /&gt;
&lt;br /&gt;
====filter list====&lt;br /&gt;
 lines = [x for x in lines if not x.startswith (&amp;quot;word&amp;quot;)]&lt;br /&gt;
 lst = [&amp;quot;asdf&amp;quot;, &amp;quot;asdf2&amp;quot;, &amp;quot;qwertz&amp;quot;]&lt;br /&gt;
 lst = [elem for elem in lst if &amp;quot;asdf&amp;quot; in elem]&lt;br /&gt;
&lt;br /&gt;
====for each element====&lt;br /&gt;
 # trim/strip spaces for each element&lt;br /&gt;
 lines = [s.strip() for s in lines]&lt;br /&gt;
 # modify each item in list by adding constant string&lt;br /&gt;
 l = [s + &#039;;&#039; + v for v in l]&lt;br /&gt;
  &lt;br /&gt;
 # modify item in list&lt;br /&gt;
 for idx, line in enumerate(cont):&lt;br /&gt;
     if &amp;quot;K1001/1&amp;quot; in line:&lt;br /&gt;
         line = &amp;quot;K1001/1 Test Nr &amp;quot; + str(i) + &amp;quot;\n&amp;quot;&lt;br /&gt;
         cont[idx] = line&lt;br /&gt;
         break&lt;br /&gt;
&lt;br /&gt;
===Tuples===&lt;br /&gt;
Ordered sequence, with no ability to replace or delete items&lt;br /&gt;
 tpl = (1,2,3,4,5,6)&lt;br /&gt;
&lt;br /&gt;
list -&amp;gt; tuple&lt;br /&gt;
 tpl = tuple(lst)&lt;br /&gt;
&lt;br /&gt;
combine 2 tuples&lt;br /&gt;
 tpl = tpl1 + tpl2&lt;br /&gt;
&lt;br /&gt;
===Dictionaries===&lt;br /&gt;
like %hash in Perl&lt;br /&gt;
 d = {&amp;quot;key1&amp;quot;: &amp;quot;value1&amp;quot;, &amp;quot;key2&amp;quot;: 123}&lt;br /&gt;
 d[&amp;quot;key3&amp;quot;] = (1, 2, 3)&lt;br /&gt;
 del d[&amp;quot;key3&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
 len(d)&lt;br /&gt;
 d.clear()&lt;br /&gt;
 d.copy()&lt;br /&gt;
 d.keys()&lt;br /&gt;
 d.values()&lt;br /&gt;
 d.items()  # returns a list of tuples (key, value)&lt;br /&gt;
 d.get(k)  # returns value of key k&lt;br /&gt;
 d.get(k, x)  # returns value of key k; if k is not  in d it returns x&lt;br /&gt;
 count = d.get(&amp;quot;key&amp;quot;, 0) + 1 # nice for counters&lt;br /&gt;
 d.pop(k)  # returns and removes item k&lt;br /&gt;
 d.pop(k, x)  # returns and removes item k; if k is  not in d it returns x&lt;br /&gt;
 # checks&lt;br /&gt;
 x in d&lt;br /&gt;
 x not in d&lt;br /&gt;
 &lt;br /&gt;
 # dict can have tuple as key&lt;br /&gt;
 tpl = (&amp;quot;key1&amp;quot;, &amp;quot;key2&amp;quot;, 123)&lt;br /&gt;
 d[tpl] = 123456&lt;br /&gt;
 &lt;br /&gt;
 # loop over all key-value pairs&lt;br /&gt;
 for key, value in d.items():&lt;br /&gt;
     print(f&amp;quot;{key} = {value}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # sorted keys:&lt;br /&gt;
 for key in sorted(dict.keys()):&lt;br /&gt;
     pass&lt;br /&gt;
 # sorted values, reversed&lt;br /&gt;
 for key, value in sorted(d.items(), key=lambda item:  item[1], reverse=True):&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # join / merge 2 dicts&lt;br /&gt;
 d.update(d2)&lt;br /&gt;
 &lt;br /&gt;
 # flatten dict of dict&lt;br /&gt;
 flat = d.copy()&lt;br /&gt;
 meta = d.pop(&amp;quot;metadata&amp;quot;)&lt;br /&gt;
 flat.update(meta)&lt;br /&gt;
&lt;br /&gt;
====MultiDim Dictionaries====&lt;br /&gt;
 d = {}&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;] = {}&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;][&amp;quot;value&amp;quot;] = 1.909e18&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;][&amp;quot;name&amp;quot;] = &amp;quot;Item 1&amp;quot;&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;] = {}&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;][&amp;quot;value&amp;quot;] = 1.725e18&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;][&amp;quot;name&amp;quot;] = &amp;quot;Item 2&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 for k in d.keys():&lt;br /&gt;
     d[k][&amp;quot;value&amp;quot;] = d[k][&amp;quot;value&amp;quot;] * 1.1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def find_common_strings(dict1: dict, dict2: dict) -&amp;gt; dict[Any, Any]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Find common strings in two dict, returning a new dict of those strings.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     def recursive_match(d1: dict, d2: dict) -&amp;gt; dict[Any, Any]:&lt;br /&gt;
         common_dict = {}&lt;br /&gt;
         for key in d1:  # noqa: PLC0206&lt;br /&gt;
             if key in d2:&lt;br /&gt;
                 if isinstance(d1[key], dict) and isinstance(d2[key], dict):&lt;br /&gt;
                     # Recurse if both values are dicts&lt;br /&gt;
                     nested_common = recursive_match(d1[key], d2[key])&lt;br /&gt;
                     if nested_common:  # Add to result if common values are found&lt;br /&gt;
                         common_dict[key] = nested_common&lt;br /&gt;
                 elif isinstance(d1[key], str) and isinstance(d2[key], str):&lt;br /&gt;
                     # Check if both are strings and identical&lt;br /&gt;
                     if d1[key] == d2[key]:&lt;br /&gt;
                         common_dict[key] = d1[key]&lt;br /&gt;
         return common_dict&lt;br /&gt;
 &lt;br /&gt;
     return recursive_match(dict1, dict2)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def update_dict_with_new_values(original_dict: dict, new_values: dict) -&amp;gt; dict:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Update strings in the original dict with new values from the new_values dict.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     def recursive_update(d1: dict, new_vals: dict) -&amp;gt; None:&lt;br /&gt;
         for key in new_vals:  # noqa: PLC0206&lt;br /&gt;
             if key in d1:&lt;br /&gt;
                 if isinstance(d1[key], dict) and isinstance(new_vals[key], dict):&lt;br /&gt;
                     # Recurse if both values are dicts&lt;br /&gt;
                     recursive_update(d1[key], new_vals[key])&lt;br /&gt;
                 elif isinstance(d1[key], str) and isinstance(new_vals[key], str):&lt;br /&gt;
                     # Update the string&lt;br /&gt;
                     d1[key] = new_vals[key]&lt;br /&gt;
 &lt;br /&gt;
     recursive_update(original_dict, new_values)&lt;br /&gt;
     return original_dict&lt;br /&gt;
&lt;br /&gt;
===Datetime, Date and Time===&lt;br /&gt;
 import datetime as dt&lt;br /&gt;
 from zoneinfo import ZoneInfo&lt;br /&gt;
 TZ_DE = ZoneInfo(&amp;quot;Europe/Berlin&amp;quot;)&lt;br /&gt;
 TZ_UTC = ZoneInfo(&amp;quot;UTC&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
====Create====&lt;br /&gt;
 # date&lt;br /&gt;
 date = dt.date(2023, 12, 31)&lt;br /&gt;
 date = dt.date.fromisocalendar(int(year), int(week),  int(daynum))  # daynum: 1..7&lt;br /&gt;
 date = dt.date.fromisoformat(&amp;quot;2020-03-10&amp;quot;)&lt;br /&gt;
 date = dt.datetime.strptime(datestr, &amp;quot;%y%m%d&amp;quot;).date()&lt;br /&gt;
 date_today = dt.datetime.now(tz=dt.UTC).date()&lt;br /&gt;
 date_yesterday = dt.datetime.now(tz=TZ_DE).date() -  dt.timedelta(days=1)&lt;br /&gt;
 days_overdue = (date_completed - date_due).days&lt;br /&gt;
 # to add one month (which is not supported by timedelta) &lt;br /&gt;
 from dateutil.relativedelta import relativedelta&lt;br /&gt;
 date_end = date_start + relativedelta(months=1)&lt;br /&gt;
 delta_days = 1 + (end - start).days&lt;br /&gt;
 &lt;br /&gt;
 # datetime&lt;br /&gt;
 dt_now = dt.datetime.now(tz=TZ_DE)&lt;br /&gt;
 my_dt = dt.datetime(2023, 12, 31, 14, 31, 56,  tzinfo=TZ_DE)  # 2023-12-31 14:31:56&lt;br /&gt;
 my_dt = dt.datetime.fromtimestamp(my_timestamp,  tz=TZ_DE)&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat (&amp;quot;2017-01-01T12:30:59.000000&amp;quot;)&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(&amp;quot;2020-03-10  06:01:01+00:00&amp;quot;)&lt;br /&gt;
 s = &amp;quot;2020-03-10T06:01:01Z&amp;quot;&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(s.replace(&amp;quot;Z&amp;quot;, &amp;quot; +00:00&amp;quot;))&lt;br /&gt;
 my_date_local = my_dt.astimezone(tz=TZ_DE).date()&lt;br /&gt;
 &lt;br /&gt;
 # datetime -&amp;gt; date&lt;br /&gt;
 my_date = my_dt.date()&lt;br /&gt;
 # date -&amp;gt; datetime&lt;br /&gt;
 my_date = dt.date(2023, 12, 31)&lt;br /&gt;
 my_dt = dt.datetime(my_date.year, my_date.month,  my_date.day, tzinfo=TZ_DE)&lt;br /&gt;
 &lt;br /&gt;
 # to string&lt;br /&gt;
 date_str = my_dt.strftime(&amp;quot;%y%m%d&amp;quot;)&lt;br /&gt;
 date_str = my_dt.strftime(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;)&lt;br /&gt;
 # alternative using time&lt;br /&gt;
 date_str = time.strftime(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;)&lt;br /&gt;
 # now in UTC without milliseconds&lt;br /&gt;
 date_str = dt.datetime.now(tz=dt.timezone.utc).replace(microsecond=0).isoformat() + &amp;quot;Z&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # remove timezone&lt;br /&gt;
 my_dt = my_dt.replace(tzinfo=None)&lt;br /&gt;
 &lt;br /&gt;
 # German format&lt;br /&gt;
 import locale&lt;br /&gt;
 locale.setlocale(locale.LC_ALL, &amp;quot;de_DE&amp;quot;)&lt;br /&gt;
 print(my_date.strftime(&amp;quot;%a %x&amp;quot;))  # Mo 25.12.2023&lt;br /&gt;
 &lt;br /&gt;
 # Calendar week&lt;br /&gt;
 week = my_date.isocalendar()[1]&lt;br /&gt;
 print(&amp;quot;KW%02d&amp;quot; % week)&lt;br /&gt;
 &lt;br /&gt;
 # first day of quarter&lt;br /&gt;
 def get_first_day_of_the_quarter(my_date: dt.date) -&amp;gt; dt.date:&lt;br /&gt;
     return dt.date(my_date.year, 1 + 3 * ((my_date.month - 1) // 3), 1)&lt;br /&gt;
&lt;br /&gt;
====rounding datetimes to minutes====&lt;br /&gt;
 def floor_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Floor (=round down) minutes to X min  resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     min_new = res * (my_dt.minute // res)&lt;br /&gt;
     return my_dt.replace(minute=min_new, second=0,  microsecond=0)&lt;br /&gt;
 &lt;br /&gt;
 def ceil_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Ceil (=round up) minutes to X min resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    min_new = res * (1 + my_dt.minute // res)&lt;br /&gt;
    return my_dt.replace(minute=0, second=0, microsecond=0) + dt.timedelta(&lt;br /&gt;
        minutes=min_new&lt;br /&gt;
    )&lt;br /&gt;
 &lt;br /&gt;
 def round_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Cound minutes to X min resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    min_old_dec = float(my_dt.minute) + float(my_dt.second * 60)&lt;br /&gt;
    min_new = res * round(min_old_dec / res)&lt;br /&gt;
    return my_dt.replace(minute=0, second=0, microsecond=0) + dt.timedelta(&lt;br /&gt;
        minutes=min_new&lt;br /&gt;
    )&lt;br /&gt;
 &lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(&amp;quot;2011-11-04  00:05:23+00:00&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;original: {my_dt}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;floored: {floor_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;ceileded: {ceil_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;rounded: {round_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
====Timing / Time elapsed====&lt;br /&gt;
 import time &lt;br /&gt;
 timestart = time.time()&lt;br /&gt;
 sec = time.time() - timestart&lt;br /&gt;
 print(f&amp;quot;Time elapsed: {sec:.2f} sec&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==OS, Argpars, etc.==&lt;br /&gt;
===Hostname===&lt;br /&gt;
 from socket import gethostname&lt;br /&gt;
 print(gethostname())&lt;br /&gt;
&lt;br /&gt;
===Checking Operating System===&lt;br /&gt;
 import os&lt;br /&gt;
 import sys&lt;br /&gt;
  &lt;br /&gt;
 if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
     print(&amp;quot;posix/Unix/Linux&amp;quot;)&lt;br /&gt;
 elif os.name == &amp;quot;nt&amp;quot;:&lt;br /&gt;
     print(&amp;quot;windows&amp;quot;)&lt;br /&gt;
 else:&lt;br /&gt;
     print(&amp;quot;unknown os&amp;quot;)&lt;br /&gt;
     sys.exit(1)  # throws exception, use quit() to   close / die silently&lt;br /&gt;
&lt;br /&gt;
===Get filename of python script===&lt;br /&gt;
 my_file_path = __file__&lt;br /&gt;
&lt;br /&gt;
alternative using sys package&lt;br /&gt;
 from sys import argv&lt;br /&gt;
 myFilename = argv[0]&lt;br /&gt;
&lt;br /&gt;
===accessing os envrionment variables===&lt;br /&gt;
 import os&lt;br /&gt;
 print(os.getenv(&amp;quot;tmp&amp;quot;))&lt;br /&gt;
 # better:&lt;br /&gt;
 try:&lt;br /&gt;
     s = os.environ[key] # throws error if unset&lt;br /&gt;
 except KeyError:&lt;br /&gt;
     error_message(f&amp;quot;Environment variable {key} not set.&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Command Line Arguments===&lt;br /&gt;
====ArgumentParser====&lt;br /&gt;
 import argparse&lt;br /&gt;
 &lt;br /&gt;
 parser = (&lt;br /&gt;
     argparse.ArgumentParser()&lt;br /&gt;
 )  # construct the argument parser and parse the  arguments&lt;br /&gt;
 # -h comes automatically&lt;br /&gt;
 &lt;br /&gt;
 # Boolean Parameter&lt;br /&gt;
 parser.add_argument(&lt;br /&gt;
     &amp;quot;-v&amp;quot;, &amp;quot;--verbose&amp;quot;, help=&amp;quot;increase output  verbosity&amp;quot;, action=&amp;quot;store_true&amp;quot;&lt;br /&gt;
 )  # store_true -&amp;gt; Boolean Value&lt;br /&gt;
 &lt;br /&gt;
 # Choice Parameter&lt;br /&gt;
 # restrict to a list of possible values / choices&lt;br /&gt;
 # parser.add_argument(&amp;quot;--choice&amp;quot;, type=int, choices= [0, 1, 2], help=&amp;quot;Test choices&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Positional Parameter (like text.py 123)&lt;br /&gt;
 # parser.add_argument(&amp;quot;num&amp;quot;, type=int, help=&amp;quot;Number  of things&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Required Parameter&lt;br /&gt;
 # parser.add_argument(&amp;quot;-i&amp;quot;, &amp;quot;--input&amp;quot;, type=str,  required=True, help=&amp;quot;Path of file&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Optional Parameter&lt;br /&gt;
 parser.add_argument(&amp;quot;-n&amp;quot;, &amp;quot;--number&amp;quot;, type=int,  help=&amp;quot;Number of clicks&amp;quot;)&lt;br /&gt;
 # Optional Parameter with Default&lt;br /&gt;
 parser.add_argument(&lt;br /&gt;
     &amp;quot;-s&amp;quot;,&lt;br /&gt;
     &amp;quot;--seconds&amp;quot;,&lt;br /&gt;
     type=int,&lt;br /&gt;
     default=sec_default,&lt;br /&gt;
     help=&amp;quot;Duration of clicking, default = %i (sec)&amp;quot;  % sec_default,&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 args = vars(parser.parse_args())&lt;br /&gt;
 &lt;br /&gt;
 if args[&amp;quot;verbose&amp;quot;]:&lt;br /&gt;
     pass  # do nothing&lt;br /&gt;
 # print (&amp;quot;verbosity turned on&amp;quot;)&lt;br /&gt;
 if args[&amp;quot;number&amp;quot;]:&lt;br /&gt;
     print(&amp;quot;num=%i&amp;quot; % args[&amp;quot;number&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
==== match case statement ====&lt;br /&gt;
(new in python 3.10)&lt;br /&gt;
 match args:&lt;br /&gt;
   case {&amp;quot;sap&amp;quot;: &amp;quot;prod&amp;quot;, &amp;quot;version&amp;quot;: 1}:&lt;br /&gt;
     ...&lt;br /&gt;
   case {&amp;quot;sap&amp;quot;: &amp;quot;prod&amp;quot;, &amp;quot;version&amp;quot;: 2}:&lt;br /&gt;
     ...&lt;br /&gt;
   case _: # default case&lt;br /&gt;
&lt;br /&gt;
==File Access==&lt;br /&gt;
===Pathlib===&lt;br /&gt;
for migrating see table [https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module]&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 p = Path(&amp;quot;dir/test.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 log_file = Path(__file__).with_suffix(&amp;quot;.log&amp;quot;)  # __file__ is name of python script&lt;br /&gt;
 &lt;br /&gt;
 my_fname = p.name  #  alternative to basename&lt;br /&gt;
 my_ext = p.suffix&lt;br /&gt;
 (filepath_without_ext, file_ext) = (p.stem, p.suffix)&lt;br /&gt;
 my_dir = p.parent&lt;br /&gt;
 my_parent_dir = p.parents[1]&lt;br /&gt;
 p2 = p.with_suffix(&amp;quot;.json&amp;quot;)&lt;br /&gt;
 p3 = p.parent / (p.name + &amp;quot;-autofix.tex&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # checks&lt;br /&gt;
 Path(my_file_str).exists()&lt;br /&gt;
 Path(my_file_str).is_file()&lt;br /&gt;
 Path(my_file_str).is_dir()&lt;br /&gt;
 &lt;br /&gt;
 # loop over glob of files matching wildcard&lt;br /&gt;
 for file_out in Path(&amp;quot;mydir&amp;quot;).glob(&amp;quot;*-autofix.tex&amp;quot;):&lt;br /&gt;
 &lt;br /&gt;
 # loop over dirs&lt;br /&gt;
 p = Path() # cwd&lt;br /&gt;
 list_of_repos = [x for x in p.iterdir() if x.is_dir() and (x / &amp;quot;.git&amp;quot;).is_dir()]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
alternative using old os functions&lt;br /&gt;
Split path into folder, filename, ext&lt;br /&gt;
 import os&lt;br /&gt;
 (dirName, fileName) = os.path.split(f)&lt;br /&gt;
 (fileBaseName, fileExtension) = os.path.splitext(fileName)&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-out.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===File Manipulations: copy, move, delete, touch===&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 # copy&lt;br /&gt;
 from shutil import copyfile&lt;br /&gt;
 copyfile(Path(&amp;quot;file-source.txt&amp;quot;), Path(&amp;quot;dir&amp;quot;) / Path(&amp;quot;file-target.txt&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # move / rename&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).replace(Path(&amp;quot;file2.txt&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # delete&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).unlink()&lt;br /&gt;
 &lt;br /&gt;
 # touch&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).touch()&lt;br /&gt;
&lt;br /&gt;
===File size and timestamp===&lt;br /&gt;
 # size&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).stat().st_size&lt;br /&gt;
 &lt;br /&gt;
 # timestamp last modified&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).stat().st_mtime&lt;br /&gt;
&lt;br /&gt;
===Dir create/mkdir and delete===&lt;br /&gt;
 # create dir&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 Path(&amp;quot;myDir1/myDir2&amp;quot;).mkdir(parents=True, exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
 # delete dir&lt;br /&gt;
 import shutil&lt;br /&gt;
 shutil.rmtree(d)&lt;br /&gt;
&lt;br /&gt;
===Loop over Directories===&lt;br /&gt;
====Fetch Dir Contents / Loop over Files====&lt;br /&gt;
glob via pathlib&lt;br /&gt;
 for fileOut in Path(&amp;quot;mydir&amp;quot;).glob(&amp;quot;*-autofix.tex&amp;quot;):&lt;br /&gt;
&lt;br /&gt;
Get list of files in directory, filter dirs from list, filter by ext&lt;br /&gt;
 dir= &amp;quot;/path/to/some/dir&amp;quot;&lt;br /&gt;
 listoffiles = [ f for f in os.listdir(dir) if os.path.isfile(os.path.join(dir ,f)) and f.lower()[-4:] == &amp;quot;.gpx&amp;quot;]&lt;br /&gt;
 listoffiles.sort()&lt;br /&gt;
&lt;br /&gt;
====Traverse in Subdirs====&lt;br /&gt;
 # walk into path an fetch all files matching extension jpe?g&lt;br /&gt;
 files = []&lt;br /&gt;
 for (dirpath, dirnames, filenames) in os.walk(&amp;quot;.&amp;quot;):&lt;br /&gt;
     dirpath = dirpath.replace(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;)&lt;br /&gt;
     for file in filenames:&lt;br /&gt;
         if file.endswith(&amp;quot;.txt&amp;quot;):&lt;br /&gt;
             files.append(dirpath + &amp;quot;/&amp;quot; + file)&lt;br /&gt;
         elif re.search(r&amp;quot;\.jpe?g$&amp;quot;, file, re.IGNORECASE):&lt;br /&gt;
             files.append(dirpath + &amp;quot;/&amp;quot; + file)&lt;br /&gt;
&lt;br /&gt;
==File Parsing==&lt;br /&gt;
===File General===&lt;br /&gt;
====File Read====&lt;br /&gt;
 path_file_in = Path(&amp;quot;file.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # via pathlib&lt;br /&gt;
 cont = path_file_in.read_text(encoding=&amp;quot;utf-8&amp;quot;) # note: utf-8-sig for UTF-8-BOM&lt;br /&gt;
 &lt;br /&gt;
 # general&lt;br /&gt;
 with path_file_in.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
     cont = fh.read()&lt;br /&gt;
     # or&lt;br /&gt;
     lst = fh.readlines()&lt;br /&gt;
     # or&lt;br /&gt;
     line = fh.readline()&lt;br /&gt;
     # or&lt;br /&gt;
     for line in fh:&lt;br /&gt;
         print(line)&lt;br /&gt;
 &lt;br /&gt;
 fh = path_file_in.open(encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 ...&lt;br /&gt;
 fh.close()&lt;br /&gt;
&lt;br /&gt;
====File Write====&lt;br /&gt;
 path_file_out = Path(&amp;quot;out/1/out.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # write via pathlib&lt;br /&gt;
 path_file_out.write_text(&amp;quot;some text&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # write general&lt;br /&gt;
 with path_file_out.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     # w = overWrite file ; a = append to file&lt;br /&gt;
     # If running Python in Windows, &amp;quot;\n&amp;quot; is automatically replaced by &amp;quot;\r\n&amp;quot;.&lt;br /&gt;
     # To prevent this use newline=&#039;\n&#039;&lt;br /&gt;
     fh.writelines(lst)  # no linebreaks&lt;br /&gt;
     # or&lt;br /&gt;
     fh.write(&amp;quot;\n&amp;quot;.join(lst))&lt;br /&gt;
     # or&lt;br /&gt;
     for line in lst:&lt;br /&gt;
         fh.write(line)&lt;br /&gt;
 &lt;br /&gt;
     # Force update of file contents without closing it&lt;br /&gt;
     fh.flush()&lt;br /&gt;
 &lt;br /&gt;
 # alternative&lt;br /&gt;
 fh = path_file_out.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;)&lt;br /&gt;
 ...&lt;br /&gt;
 fh.close()&lt;br /&gt;
&lt;br /&gt;
===CSV===&lt;br /&gt;
====CSV Read====&lt;br /&gt;
 import csv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 with Path(&amp;quot;data.csv&amp;quot;).open(mode=&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as fh:  # for Excel use ANSI&lt;br /&gt;
     csv_reader = csv.DictReader(fh, dialect=&amp;quot;excel&amp;quot;, delimiter=&amp;quot;\t&amp;quot;)&lt;br /&gt;
     for row in csv_reader:&lt;br /&gt;
         print(f&#039;\t{row[&amp;quot;name&amp;quot;]} works in the {row[&amp;quot;department&amp;quot;]} department&#039;)&lt;br /&gt;
&lt;br /&gt;
====CSV Write====&lt;br /&gt;
 import csv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 # simple&lt;br /&gt;
 with Path(&amp;quot;data.tsv&amp;quot;).open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     csvwriter = csv.writer(fh, delimiter=&amp;quot;\t&amp;quot;)&lt;br /&gt;
     csvwriter.writerow((&amp;quot;Date&amp;quot;, &amp;quot;Confirmed&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # dictwriter&lt;br /&gt;
 with Path(&amp;quot;data.tsv&amp;quot;).open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     csvwriter = csv.DictWriter(&lt;br /&gt;
         fh,&lt;br /&gt;
         delimiter=&amp;quot;\t&amp;quot;,&lt;br /&gt;
         extrasaction=&amp;quot;ignore&amp;quot;,&lt;br /&gt;
         fieldnames=[&amp;quot;date&amp;quot;, &amp;quot;occupied_percent&amp;quot;, &amp;quot;occupied&amp;quot;, &amp;quot;total&amp;quot;],&lt;br /&gt;
     )&lt;br /&gt;
     csvwriter.writeheader()&lt;br /&gt;
     for d in my_list_of_dicts:&lt;br /&gt;
         d[&amp;quot;occupied_percent&amp;quot;] = round(100 * d[&amp;quot;occupied&amp;quot;] / d[&amp;quot;total&amp;quot;], 1)&lt;br /&gt;
         csvwriter.writerow(d)&lt;br /&gt;
&lt;br /&gt;
===JSON===&lt;br /&gt;
====JSON Read====&lt;br /&gt;
 import json&lt;br /&gt;
 # from file&lt;br /&gt;
 with path_to_download_file.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
     d_json = json.load(fh)&lt;br /&gt;
 # from string&lt;br /&gt;
 d_json = json.loads(response_text)&lt;br /&gt;
&lt;br /&gt;
 def json_read(file_path: Path) -&amp;gt; list[dict[str, str]]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Read JSON data from file.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     with file_path.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
         json_data = json.load(fh)&lt;br /&gt;
     return json_data&lt;br /&gt;
&lt;br /&gt;
====JSON Write====&lt;br /&gt;
Write dict to file in JSON format, keeping utf-8 encoding&lt;br /&gt;
 import json&lt;br /&gt;
 &lt;br /&gt;
 with path_to_download_file.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     json.dump(my_dict, fh, ensure_ascii=False, sort_keys=False, indent=2)&lt;br /&gt;
&lt;br /&gt;
 def json_write(file_path: Path, json_data: list[dict[str, str]]) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Write JSON data to file.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     with file_path.open(&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
         json.dump(json_data, fh, ensure_ascii=False, sort_keys=False, indent=2)&lt;br /&gt;
&lt;br /&gt;
===Excel===&lt;br /&gt;
====Excel Read====&lt;br /&gt;
 import openpyxl&lt;br /&gt;
 # or xlsxwriter for write-only&lt;br /&gt;
 &lt;br /&gt;
 workbook = openpyxl.load_workbook(&lt;br /&gt;
     pathToMyExcelFile,&lt;br /&gt;
     data_only=True,  # read values instead of formulas&lt;br /&gt;
     read_only=True,  # suppresses: &amp;quot;UserWarning: wmf image format is not supported so the image is being dropped&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 sheet = workbook[&amp;quot;mySheetName&amp;quot;]&lt;br /&gt;
 # or fetch active sheet&lt;br /&gt;
 sheet = workbook.active&lt;br /&gt;
 cell = sheet[&amp;quot;A34&amp;quot;]&lt;br /&gt;
 # or&lt;br /&gt;
 cell = sheet.cell(row=34, column=1)  # index start here with 1&lt;br /&gt;
 print(cell.value)&lt;br /&gt;
 # or&lt;br /&gt;
 print(sheet.cell(column=col, row=row).value)&lt;br /&gt;
&lt;br /&gt;
====Excel Write====&lt;br /&gt;
 import openpyxl&lt;br /&gt;
 &lt;br /&gt;
 workbook = openpyxl.Workbook()&lt;br /&gt;
 sheet = workbook.active&lt;br /&gt;
 cell = sheet[&amp;quot;A34&amp;quot;]&lt;br /&gt;
 # or&lt;br /&gt;
 cell = sheet.cell(row=i, column=j)  # index starts at 1&lt;br /&gt;
 cell.value = &amp;quot;asdf&amp;quot;&lt;br /&gt;
 workbook.save(&amp;quot;out.xlsx&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Word===&lt;br /&gt;
====Word Read====&lt;br /&gt;
 from io import BytesIO&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import pandas as pd&lt;br /&gt;
 from docx import Document&lt;br /&gt;
 from docx.document import Document as DocxDocument&lt;br /&gt;
 &lt;br /&gt;
 def read_doc_to_df(p_doc: Path | BytesIO) -&amp;gt; pd.DataFrame:  # noqa: C901, PLR0912&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Read Word document to DataFrame.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if isinstance(p_doc, Path):&lt;br /&gt;
         doc = Document(str(p_doc))&lt;br /&gt;
     elif isinstance(p_doc, BytesIO):&lt;br /&gt;
         doc = Document(p_doc)&lt;br /&gt;
 &lt;br /&gt;
     # list of multiple paragraphs per key&lt;br /&gt;
     data: dict[str, dict[str, list[str]]] = {}&lt;br /&gt;
 &lt;br /&gt;
     sections1: list[str] = []&lt;br /&gt;
     section1 = &amp;quot;&amp;quot;&lt;br /&gt;
     key = &amp;quot;&amp;quot;&lt;br /&gt;
     section2 = &amp;quot;&amp;quot;&lt;br /&gt;
     score = &amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     for paragraph in doc.paragraphs:&lt;br /&gt;
         assert paragraph.style is not None&lt;br /&gt;
         text = paragraph.text.strip()&lt;br /&gt;
         if paragraph.style.name == &amp;quot;Title&amp;quot;:  # doc title&lt;br /&gt;
             continue&lt;br /&gt;
         if paragraph.style.name == &amp;quot;Heading 1&amp;quot;:  # sheet name&lt;br /&gt;
             section1 = text&lt;br /&gt;
             if section1 not in sections1:&lt;br /&gt;
                 sections1.append(section1)&lt;br /&gt;
             key = &amp;quot;&amp;quot;&lt;br /&gt;
             section2 = &amp;quot;&amp;quot;&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Heading 2&amp;quot;:  # Key of the question&lt;br /&gt;
             key = text.split(&amp;quot; | &amp;quot;)[0]&lt;br /&gt;
             key = section1 + &amp;quot;|&amp;quot; + key&lt;br /&gt;
             section2 = &amp;quot;&amp;quot;&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Normal&amp;quot; and text.startswith(&amp;quot;[&amp;quot;) and &amp;quot;]&amp;quot; in text:&lt;br /&gt;
             section2, t2 = text[1:].split(&amp;quot;]&amp;quot;, maxsplit=1)&lt;br /&gt;
             if section2 == &amp;quot;Score&amp;quot;:&lt;br /&gt;
                 score = t2.strip()&lt;br /&gt;
                 data[key][section2] = [score]&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Normal&amp;quot;:&lt;br /&gt;
             text = paragraph.text.strip()&lt;br /&gt;
             # skip requirements leftovers and empty paragraphs&lt;br /&gt;
             if (section2 == &amp;quot;&amp;quot; and text.startswith(&amp;quot;...&amp;quot;)) or text == &amp;quot;&amp;quot;:&lt;br /&gt;
                 continue&lt;br /&gt;
             lst = data.setdefault(key, {}).setdefault(section2, [])&lt;br /&gt;
             lst.append(text)&lt;br /&gt;
             data[key][section2] = lst&lt;br /&gt;
         else:&lt;br /&gt;
             msg = f&amp;quot;Unexpected style: {paragraph.style.name} in paragraph: {text}&amp;quot;&lt;br /&gt;
             raise ValueError(msg)&lt;br /&gt;
 &lt;br /&gt;
     # flatten the text from list to str&lt;br /&gt;
     data2: dict[str, dict[str, str]] = {}&lt;br /&gt;
     for key, sections in data.items():&lt;br /&gt;
         for section2, lst in sections.items():&lt;br /&gt;
             s = &amp;quot;\n&amp;quot;.join(lst)&lt;br /&gt;
             if key not in data2:&lt;br /&gt;
                 data2[key] = {}&lt;br /&gt;
             data2[key][section2] = s.strip()&lt;br /&gt;
 &lt;br /&gt;
     df1 = pd.DataFrame.from_dict(data2, orient=&amp;quot;index&amp;quot;)&lt;br /&gt;
     df1.index.name = &amp;quot;Key&amp;quot;&lt;br /&gt;
     df1 = df1.reset_index()&lt;br /&gt;
     df1[ [ &amp;quot;Sheet&amp;quot;, &amp;quot;Key&amp;quot; ] ] = df1[&amp;quot;Key&amp;quot;].str.split(&amp;quot;|&amp;quot;, n=1, expand=True)&lt;br /&gt;
 &lt;br /&gt;
     return df1&lt;br /&gt;
&lt;br /&gt;
====Word Write====&lt;br /&gt;
 from io import BytesIO&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import pandas as pd from docx import Document&lt;br /&gt;
 from docx.document import Document as DocxDocument&lt;br /&gt;
 from docx.shared import Cm&lt;br /&gt;
 &lt;br /&gt;
 LAYOUT_SMALL_MARGINS = True&lt;br /&gt;
 if LAYOUT_SMALL_MARGINS:&lt;br /&gt;
     sections = doc.sections&lt;br /&gt;
     for section in sections:&lt;br /&gt;
         section.top_margin = Cm(1)&lt;br /&gt;
         section.bottom_margin = Cm(1)&lt;br /&gt;
         section.left_margin = Cm(1)&lt;br /&gt;
         section.right_margin = Cm(1)&lt;br /&gt;
 &lt;br /&gt;
 doc.add_heading(&amp;quot;My Title&amp;quot;, level=0)&lt;br /&gt;
 doc.add_heading(&amp;quot;My Section&amp;quot;, level=1)&lt;br /&gt;
 doc.add_paragraph(&amp;quot;Some Text)&lt;br /&gt;
 p = doc.add_paragraph()&lt;br /&gt;
 runner = p.add_run(bold text&amp;quot;)&lt;br /&gt;
 runner.bold = True  # runner.italic = True&lt;br /&gt;
&lt;br /&gt;
==Regular Expressions==&lt;br /&gt;
See [http://docs.python.org/library/re.html]&lt;br /&gt;
&lt;br /&gt;
See [https://pythex.org] for an online tester&lt;br /&gt;
&lt;br /&gt;
multiple flags are joined via pipe |&lt;br /&gt;
 s = re.sub(&amp;quot;asdf.*&amp;quot;, r&amp;quot;qwertz&amp;quot;, s, flags=re.DOTALL | re.IGNORECASE)&lt;br /&gt;
&lt;br /&gt;
====Lookahead and Lookbehind====&lt;br /&gt;
 pos lookahead: (?=...)&lt;br /&gt;
 neg lookahead: (?!...)&lt;br /&gt;
 pos lookbehind (?&amp;lt;=...)&lt;br /&gt;
 neg lookbehind (?&amp;lt;!...)&lt;br /&gt;
&lt;br /&gt;
====matching====&lt;br /&gt;
 import re&lt;br /&gt;
 &lt;br /&gt;
 # V0: simple 1&lt;br /&gt;
 myPattern = &amp;quot;(/\*\*\* 0097_210000_0192539580000_2898977_0050 \*\*\*/.*?)($|/\*\*\*)&amp;quot;&lt;br /&gt;
 myRegExp = re.compile(myPattern, re.DOTALL)&lt;br /&gt;
 myMatch = myRegExp.search(cont)&lt;br /&gt;
 assert myMatch != None, f&amp;quot;golden file not found in file {filename}&amp;quot;&lt;br /&gt;
 cont_golden = myMatch.group(1)&lt;br /&gt;
 &lt;br /&gt;
 # V1: simple 2&lt;br /&gt;
 assert (&lt;br /&gt;
     re.match(&amp;quot;^[a-z]{2}$&amp;quot;, d_settings[&amp;quot;country&amp;quot;]) != None&lt;br /&gt;
 ), f&#039;Error: county must be 2 digit lower case. We got: {d_settings[&amp;quot;country&amp;quot;]}&#039;&lt;br /&gt;
 &lt;br /&gt;
 # V2: find and count&lt;br /&gt;
 was = r&amp;quot;&amp;quot;&amp;quot;Part: ([^&amp;lt;+])&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 cnt_parts = len(re.findall(was, cont))&lt;br /&gt;
 cont = re.sub(was, r&amp;quot;\n\nTeil: \1\n\n&amp;quot;, cont)&lt;br /&gt;
 assert cnt_parts == 4, f&amp;quot;{cnt_parts} == 4&amp;quot;&lt;br /&gt;
 assert &amp;quot;Part:&amp;quot; not in cont&lt;br /&gt;
&lt;br /&gt;
=====Match email=====&lt;br /&gt;
 def checkValidEMail(email: str) -&amp;gt; bool:&lt;br /&gt;
     # from https://stackoverflow.com/posts/719543/timeline bottom edit&lt;br /&gt;
     if not re.fullmatch(r&amp;quot;^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$&amp;quot;, email):&lt;br /&gt;
         print(&amp;quot;Error: invalid email&amp;quot;)&lt;br /&gt;
         quit()&lt;br /&gt;
     return True&lt;br /&gt;
&lt;br /&gt;
====Find all====&lt;br /&gt;
 myMatches = re.findall(&#039;href=&amp;quot;([^&amp;quot;]+)&amp;quot;&#039;, cont)&lt;br /&gt;
 for myMatch in myMatches:&lt;br /&gt;
     print(myMatch)&lt;br /&gt;
&lt;br /&gt;
====substring====&lt;br /&gt;
 import re&lt;br /&gt;
 &lt;br /&gt;
 # simple via search&lt;br /&gt;
 lk_id = re.search(&#039;^.*timeseries\-(\d+)\.json$&#039;, f).group(1)&lt;br /&gt;
 &lt;br /&gt;
 # simple via sub&lt;br /&gt;
 myPattern = &amp;quot;^.*&amp;quot; + s1 + &amp;quot;(.*)&amp;quot; + s2 + &amp;quot;.*$&amp;quot;&lt;br /&gt;
 out = re.sub(myPattern, r&amp;quot;\1&amp;quot;, s)&lt;br /&gt;
 &lt;br /&gt;
 # more robust including an assert&lt;br /&gt;
 def substr_between(s: str, s1: str, s2: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     returns substring of s, found between strings s1 and s2&lt;br /&gt;
     s1 and s2 can be regular expressions&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     myPattern = s1 + &#039;(.*)&#039; + s2&lt;br /&gt;
     myRegExp = re.compile(myPattern)&lt;br /&gt;
     myMatches = myRegExp.search(s)&lt;br /&gt;
     assert myMatches != None, f&amp;quot;E: can&#039;t find &#039;{s1}&#039;...&#039;{s2}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     out = myMatches.group(1)&lt;br /&gt;
     return out&lt;br /&gt;
&lt;br /&gt;
 matchObj = re.search(r&amp;quot;(\d+\.\d+)&amp;quot;, text&lt;br /&gt;
 if matchObj:&lt;br /&gt;
   price = float( &#039;%s&#039; % (matchObj).group(0) )&lt;br /&gt;
&lt;br /&gt;
====Naming of match groups====&lt;br /&gt;
(?P&amp;lt;name&amp;gt;...), see [https://docs.python.org/3/library/re.html]&lt;br /&gt;
&lt;br /&gt;
====Search and Replace====&lt;br /&gt;
From [http://www.regular-expressions.info/python.html]&amp;lt;br&amp;gt;&lt;br /&gt;
re.sub(regex, replacement, str) performs a search-and-replace across subject, replacing all matches of regex in str with replacement. The result is returned by the sub() function. The str string you pass is not modified.&lt;br /&gt;
&lt;br /&gt;
 s = re.sub(&amp;quot;  +&amp;quot;, &amp;quot; &amp;quot;, s)&lt;br /&gt;
&lt;br /&gt;
====Splitting====&lt;br /&gt;
From [http://docs.python.org/library/re.html]&amp;lt;br&amp;gt;&lt;br /&gt;
split() splits a string into a list delimited by the passed pattern. The method is invaluable for converting textual data into data structures that can be easily read and modified by Python as demonstrated in the following example that creates a phonebook.&lt;br /&gt;
&lt;br /&gt;
First, here is the input. Normally it may come from a file, here we are using triple-quoted string syntax:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; input = &amp;quot;&amp;quot;&amp;quot;Ross McFluff: 834.345.1254 155 Elm Street&lt;br /&gt;
 ...&lt;br /&gt;
 ... Ronald Heathmore: 892.345.3428 436 Finley Avenue&lt;br /&gt;
 ... Frank Burger: 925.541.7625 662 South Dogwood Way&lt;br /&gt;
 ...&lt;br /&gt;
 ...&lt;br /&gt;
 ... Heather Albrecht: 548.326.4584 919 Park Place&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The entries are separated by one or more newlines. Now we convert the string into a list with each nonempty line having its own entry:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; entries = re.split(&amp;quot;\n+&amp;quot;, input)&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; entries&lt;br /&gt;
 [&#039;Ross McFluff: 834.345.1254 155 Elm Street&#039;,&lt;br /&gt;
 &#039;Ronald Heathmore: 892.345.3428 436 Finley Avenue&#039;,&lt;br /&gt;
 &#039;Frank Burger: 925.541.7625 662 South Dogwood Way&#039;,&lt;br /&gt;
 &#039;Heather Albrecht: 548.326.4584 919 Park Place&#039;]&lt;br /&gt;
&lt;br /&gt;
Finally, split each entry into a list with first name, last name, telephone number, and address. We use the maxsplit parameter of split() because the address has spaces, our splitting pattern, in it:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; [re.split(&amp;quot;:? &amp;quot;, entry, 3) for entry in entries]&lt;br /&gt;
 [[&#039;Ross&#039;, &#039;McFluff&#039;, &#039;834.345.1254&#039;, &#039;155 Elm Street&#039;],&lt;br /&gt;
 [&#039;Ronald&#039;, &#039;Heathmore&#039;, &#039;892.345.3428&#039;, &#039;436 Finley Avenue&#039;],&lt;br /&gt;
 [&#039;Frank&#039;, &#039;Burger&#039;, &#039;925.541.7625&#039;, &#039;662 South Dogwood Way&#039;],&lt;br /&gt;
 [&#039;Heather&#039;, &#039;Albrecht&#039;, &#039;548.326.4584&#039;, &#039;919 Park Place&#039;]]&lt;br /&gt;
&lt;br /&gt;
==== replace cont by linebreaks====&lt;br /&gt;
 def replace_cont_by_linebreaks(s: str, regex: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Replace regex in s by the number of linebreaks it originally contained.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     myMatches = re.findall(regex, s, flags=re.DOTALL)&lt;br /&gt;
     for match in myMatches:&lt;br /&gt;
         linebreaks = match.count(&amp;quot;\n&amp;quot;)&lt;br /&gt;
         s = s.replace(match, &amp;quot;\n&amp;quot; * linebreaks, 1)&lt;br /&gt;
     return s&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Image/Picture/Photo==&lt;br /&gt;
 from PIL import Image, ImageFilter  # pip install Pillow&lt;br /&gt;
 &lt;br /&gt;
 fileIn = &amp;quot;2018-02-09 13.56.25.jpg&amp;quot;&lt;br /&gt;
 # Read image&lt;br /&gt;
 img = Image.open(fileIn)&lt;br /&gt;
PROBLEM:&amp;lt;br/&amp;gt;&lt;br /&gt;
PIL Image.save() drops the IPTC data like tags, keywords, copywrite, ...&amp;lt;br/&amp;gt;&lt;br /&gt;
better using https://imagemagick.org instead when tags shall be kept&lt;br /&gt;
&lt;br /&gt;
====Resize====&lt;br /&gt;
 # Resize keeping aspect ration -&amp;gt; img.thumbnail&lt;br /&gt;
 # drops exif data, exif can be added from source file via exif= in save, see below&lt;br /&gt;
 size = 1920, 1920&lt;br /&gt;
 img.thumbnail(size, Image.ANTIALIAS)&lt;br /&gt;
&lt;br /&gt;
====Export file====&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-edit.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img = Image.open(fileIn)&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=&#039;keep&#039;)  # exif=dict_exif_bytes&lt;br /&gt;
     # JPEG Parameters&lt;br /&gt;
     # * qualitiy : &#039;keep&#039; or 1 (worst) to 95 (best), default = 75. Values above 95 should be avoided.&lt;br /&gt;
     # * dpi : tuple of integers representing the pixel density, (x,y)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     print(&amp;quot;cannot write file &#039;%s&#039;&amp;quot; % fileOut)&lt;br /&gt;
&lt;br /&gt;
====Export Progressive / web optimized JPEG====&lt;br /&gt;
 from PIL import ImageFile  # for MAXBLOCK for progressive export&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-progressive.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=80, optimize=True, progressive=True)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     ImageFile.MAXBLOCK = img.size[0] * img.size[1]&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=80, optimize=True, progressive=True)&lt;br /&gt;
&lt;br /&gt;
====JPEG Meta Data: EXIF and IPTC====&lt;br /&gt;
=====IPTC: Tags/Keywords=====&lt;br /&gt;
 from iptcinfo3 import IPTCInfo  # this works in pyhton 3!&lt;br /&gt;
 iptc = IPTCInfo(fileIn)&lt;br /&gt;
 if len(iptc[&#039;keywords&#039;]) &amp;gt; 0:  # or supplementalCategories or contacts&lt;br /&gt;
     print(&#039;====&amp;gt; Keywords&#039;)&lt;br /&gt;
     for key in sorted(iptc[&#039;keywords&#039;]):&lt;br /&gt;
         s = key.decode(&#039;ascii&#039;)  # decode binary strings&lt;br /&gt;
         print(s)&lt;br /&gt;
&lt;br /&gt;
=====EXIF via piexif=====&lt;br /&gt;
 import piexif  # pip install piexif&lt;br /&gt;
 exif_dict = piexif.load(img.info[&#039;exif&#039;])&lt;br /&gt;
 print(exif_dict[&#039;GPS&#039;][piexif.GPSIFD.GPSAltitude])&lt;br /&gt;
 # returns list of 2 integers: value and donator  -&amp;gt; v / d&lt;br /&gt;
 # (340000, 1000) =&amp;gt; 340m&lt;br /&gt;
 # (51, 2) =&amp;gt; 25.5m&lt;br /&gt;
 &lt;br /&gt;
 # Modify altitude&lt;br /&gt;
 exif_dict[&#039;GPS&#039;][piexif.GPSIFD.GPSAltitude] = (140, 1)  # 140m&lt;br /&gt;
 &lt;br /&gt;
 # write to file&lt;br /&gt;
 exif_bytes = piexif.dump(exif_dict)&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-modExif.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;jpeg&amp;quot;, exif=exif_bytes, quality=&#039;keep&#039;)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     print(&amp;quot;cannot write file &#039;%s&#039;&amp;quot; % fileOut)&lt;br /&gt;
or&lt;br /&gt;
 exif_dict = piexif.load(fileIn)&lt;br /&gt;
 for ifd in (&amp;quot;0th&amp;quot;, &amp;quot;Exif&amp;quot;, &amp;quot;GPS&amp;quot;, &amp;quot;1st&amp;quot;):&lt;br /&gt;
     print(&amp;quot;===&amp;quot; + ifd)&lt;br /&gt;
     for tag in exif_dict[ifd]:&lt;br /&gt;
         print(piexif.TAGS[ifd][tag][&amp;quot;name&amp;quot;], &amp;quot;\t&amp;quot;,&lt;br /&gt;
               tag, &amp;quot;\t&amp;quot;, exif_dict[ifd][tag])&lt;br /&gt;
 print(exif_dict[&#039;0th&#039;][306]) # 306 = DateTime&lt;br /&gt;
&lt;br /&gt;
=====EXIF via exifread=====&lt;br /&gt;
 # Open image file for reading (binary mode)&lt;br /&gt;
 fh = open(fileIn, &amp;quot;rb&amp;quot;)&lt;br /&gt;
 # Return Exif tags&lt;br /&gt;
 exif = exifread.process_file(fh)&lt;br /&gt;
 fh.close()&lt;br /&gt;
 # for tag in exif.keys():&lt;br /&gt;
 #     if tag not in (&#039;JPEGThumbnail&#039;, &#039;TIFFThumbnail&#039;, &#039;Filename&#039;, &#039;EXIF MakerNote&#039;):&lt;br /&gt;
 #         print(&amp;quot;%s\t%s&amp;quot; % (tag, exif[tag]))&lt;br /&gt;
 print(exif[&amp;quot;Image DateTime&amp;quot;])&lt;br /&gt;
 print(exif[&amp;quot;GPS GPSLatitude&amp;quot;])&lt;br /&gt;
 print(exif[&amp;quot;GPS GPSLongitude&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
=====EXIF GPS via PIL=====&lt;br /&gt;
 # from https://developer.here.com/blog/getting-started-with-geocoding-exif-image-metadata-in-python3&lt;br /&gt;
 def get_exif(filename):&lt;br /&gt;
     image = Image.open(filename)&lt;br /&gt;
     image.verify()&lt;br /&gt;
     image.close()&lt;br /&gt;
     return image._getexif()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_labeled_exif(exif):&lt;br /&gt;
     labeled = {}&lt;br /&gt;
     for (key, val) in exif.items():&lt;br /&gt;
         labeled[TAGS.get(key)] = val&lt;br /&gt;
     return labeled&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_geotagging(exif):&lt;br /&gt;
     if not exif:&lt;br /&gt;
         raise ValueError(&amp;quot;No EXIF metadata found&amp;quot;)&lt;br /&gt;
     geotagging = {}&lt;br /&gt;
     for (idx, tag) in TAGS.items():&lt;br /&gt;
         if tag == &amp;quot;GPSInfo&amp;quot;:&lt;br /&gt;
             if idx not in exif:&lt;br /&gt;
                 raise ValueError(&amp;quot;No EXIF geotagging found&amp;quot;)&lt;br /&gt;
             for (key, val) in GPSTAGS.items():&lt;br /&gt;
                 if key in exif[idx]:&lt;br /&gt;
                     geotagging[val] = exif[idx][key]&lt;br /&gt;
     return geotagging&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_decimal_from_dms(dms, ref):&lt;br /&gt;
     degrees = dms[0][0] / dms[0][1]&lt;br /&gt;
     minutes = dms[1][0] / dms[1][1] / 60.0&lt;br /&gt;
     seconds = dms[2][0] / dms[2][1] / 3600.0&lt;br /&gt;
     if ref in [&amp;quot;S&amp;quot;, &amp;quot;W&amp;quot;]:&lt;br /&gt;
         degrees = -degrees&lt;br /&gt;
         minutes = -minutes&lt;br /&gt;
         seconds = -seconds&lt;br /&gt;
     return round(degrees + minutes + seconds, 5)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_coordinates(geotags):&lt;br /&gt;
     lat = get_decimal_from_dms(geotags[&amp;quot;GPSLatitude&amp;quot;], geotags[&amp;quot;GPSLatitudeRef&amp;quot;])&lt;br /&gt;
     lon = get_decimal_from_dms(geotags[&amp;quot;GPSLongitude&amp;quot;], geotags[&amp;quot;GPSLongitudeRef&amp;quot;])&lt;br /&gt;
     return (lat, lon)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 exif = get_exif(fileIn)&lt;br /&gt;
 exif_labeled = get_labeled_exif(exif)&lt;br /&gt;
 print(exif_labeled[&amp;quot;DateTime&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
 geotags = get_geotagging(exif)&lt;br /&gt;
 print(get_coordinates(geotags))&lt;br /&gt;
&lt;br /&gt;
====Template Matching / Image Regocnition Using CV2 / OpenCV====&lt;br /&gt;
see [[Python - CV2]]&lt;br /&gt;
&lt;br /&gt;
====Optical Character Recognition (OCR) ====&lt;br /&gt;
see [[Python - OCR]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Templates/Snippets==&lt;br /&gt;
===Retry on Exception===&lt;br /&gt;
 for retry_no in range(MAX_RETRIES):&lt;br /&gt;
     try:&lt;br /&gt;
         if retry_no &amp;gt; 0:&lt;br /&gt;
             logger.warning(&amp;quot;retrying %d/%d...&amp;quot;, retry_no + 1, MAX_RETRIES)&lt;br /&gt;
         doit()&lt;br /&gt;
     except json.JSONDecodeError:&lt;br /&gt;
         logger.exception(&amp;quot;JSONDecodeError&amp;quot;)&lt;br /&gt;
         if retry_no == MAX_RETRIES - 1:&lt;br /&gt;
             # end of retries&lt;br /&gt;
             raise&lt;br /&gt;
&lt;br /&gt;
===Diff of 2 files===&lt;br /&gt;
 import difflib&lt;br /&gt;
 &lt;br /&gt;
 fh1 = p_file1.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 fh2 = p_file1.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 diff = difflib.ndiff(fh1.readlines(), fh2.readlines())&lt;br /&gt;
 delta = &amp;quot;&amp;quot;.join(line for line in diff if line.startswith((&amp;quot;+ &amp;quot;, &amp;quot;- &amp;quot;)))&lt;br /&gt;
 print(delta)&lt;br /&gt;
&lt;br /&gt;
===GPX parsing===&lt;br /&gt;
 import gpxpy&lt;br /&gt;
 import gpxpy.gpx&lt;br /&gt;
 # Elevation data by NASA: see lib at https://github.com/tkrajina/srtm.py&lt;br /&gt;
 fh_gpx_file = open(gpx_file_path, &#039;r&#039;)&lt;br /&gt;
 gpx = gpxpy.parse(fh_gpx_file)&lt;br /&gt;
 #  Loops for accessing the data&lt;br /&gt;
 for track in gpx.tracks:&lt;br /&gt;
     for segment in track.segments:&lt;br /&gt;
         for point in segment.points:&lt;br /&gt;
 for waypoint in gpx.waypoints:&lt;br /&gt;
 for route in gpx.routes:&lt;br /&gt;
     for point in route.points: &lt;br /&gt;
 # interesting properties of point / waypoint objects:&lt;br /&gt;
 point.time&lt;br /&gt;
 point.latitude&lt;br /&gt;
 point.longitude&lt;br /&gt;
 point.source&lt;br /&gt;
 waypoint.name&lt;br /&gt;
&lt;br /&gt;
===TypeHints===&lt;br /&gt;
 from typing import Any, Dict, cast&lt;br /&gt;
 creds = cast(dict[str, str], tomllib.load(f))  # type: ignore&lt;br /&gt;
 o = cast(Dict[str, Any], tomllib.load(f))  # type: ignore&lt;br /&gt;
 o[&amp;quot;sap&amp;quot;] = cast(Dict[str, str], o[&amp;quot;sap&amp;quot;])&lt;br /&gt;
 o[&amp;quot;settings&amp;quot;] = cast(Dict[str, str | int | bool], o[&amp;quot;settings&amp;quot;])&lt;br /&gt;
 o[&amp;quot;settings&amp;quot;][&amp;quot;sleep_time&amp;quot;] = cast(int, o[&amp;quot;settings&amp;quot;][&amp;quot;sleep_time&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
===Pylance/Pyright===&lt;br /&gt;
 import tomllib&lt;br /&gt;
 # shows warning: Import &amp;quot;xyz&amp;quot; could not be resolved&lt;br /&gt;
 # fix by &lt;br /&gt;
 import tomllib # pyright: ignore&lt;br /&gt;
&lt;br /&gt;
===asserts function argument validation===&lt;br /&gt;
aus [https://bildungsportal.sachsen.de/opal/auth/RepositoryEntry/12518195218/CourseNode/94506982489539?0 Python Kurs von Carsten Knoll]&lt;br /&gt;
 def eine_funktion(satz, ganzzahl, zahl2, liste):&lt;br /&gt;
   if not type(satz) == str:&lt;br /&gt;
     print &amp;quot;Datentpyfehler: satz&amp;quot;&lt;br /&gt;
     return -1&lt;br /&gt;
   if not isinstance(ganzzahl, int):&lt;br /&gt;
     print &amp;quot;Datentpyfehler: ganzzahl&amp;quot;&lt;br /&gt;
     return -2&lt;br /&gt;
   if not isinstance(liste, (tuple, list)):&lt;br /&gt;
     print &amp;quot;Datentpyfehler: liste&amp;quot;&lt;br /&gt;
     return -3&lt;br /&gt;
   # Kompakteste Variante (empfohlen): &lt;br /&gt;
   assert zahl2 &amp;gt; 0, &amp;quot;Error: zahl2 ist nicht &amp;gt; 0&amp;quot; # Assertation-Error bei Nichterfuellung&lt;br /&gt;
&lt;br /&gt;
 def F(x):&lt;br /&gt;
   if not isinstance(x, (float, int)):&lt;br /&gt;
     msg = &amp;quot;Zahl erwartet, %s bekommen&amp;quot; % type(x)&lt;br /&gt;
     raise ValueError(msg)&lt;br /&gt;
   return x**2&lt;br /&gt;
better:&lt;br /&gt;
 def F(x):&lt;br /&gt;
   assert isinstance(x, (float, int)), &amp;quot;Error: x is not of type float or int&amp;quot;&lt;br /&gt;
   return x**2&lt;br /&gt;
&lt;br /&gt;
 assert variant in [&lt;br /&gt;
     &amp;quot;normal&amp;quot;,&lt;br /&gt;
     &amp;quot;gray&amp;quot;,&lt;br /&gt;
     &amp;quot;cannyedge&amp;quot;,&lt;br /&gt;
 ], &amp;quot;Error: variant is not in &#039;normal&#039;, &#039;gray&#039;, &#039;cannyedge&#039;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===Exceptions===&lt;br /&gt;
see [https://medium.com/@saadjamilakhtar/5-best-practices-for-python-exception-handling-5e54b876a20]&lt;br /&gt;
&lt;br /&gt;
====Catching====&lt;br /&gt;
Catch keyboard interrupt and do a &amp;quot;save exit&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     i = 0&lt;br /&gt;
     while 1:&lt;br /&gt;
         i += 1&lt;br /&gt;
         print(i)&lt;br /&gt;
 except KeyboardInterrupt:&lt;br /&gt;
     print(&amp;quot;stopped&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
Catch &#039;&#039;&#039;all&#039;&#039;&#039; exceptions&lt;br /&gt;
 try:&lt;br /&gt;
   [...]&lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     print(&amp;quot;Exception: &amp;quot;, e)&lt;br /&gt;
&lt;br /&gt;
====Raising Exceptions====&lt;br /&gt;
 if resp.status_code != 200:  # noqa: PLR2004&lt;br /&gt;
     msg = f&amp;quot;Bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
     raise ValueError(msg) from None&lt;br /&gt;
&lt;br /&gt;
Custom Exceptions&lt;br /&gt;
 try: &lt;br /&gt;
   raise Exception(&amp;quot;HiHo&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
 raise ValueError¶&lt;br /&gt;
&lt;br /&gt;
===perl grep and map===&lt;br /&gt;
from [https://stackoverflow.com/questions/12845288/grep-on-elements-of-a-list]&lt;br /&gt;
 def grep(list, pattern):&lt;br /&gt;
     expr = re.compile(pattern)&lt;br /&gt;
     return [elem for elem in list if expr.match(elem)]&lt;br /&gt;
 or&lt;br /&gt;
 filteredList = filter(lambda x: x &amp;lt; 7 and x &amp;gt; 2, unfilteredList)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def map(list, was, womit):&lt;br /&gt;
     return list(map(lambda i: re.sub(was, womit, i), list))&lt;br /&gt;
     # was = &#039;.*&amp;quot;(\d+)&amp;quot;.*&#039;&lt;br /&gt;
     # womit = r&amp;quot;\1&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===unit testing using pytest===&lt;br /&gt;
install via&lt;br /&gt;
 pip install pytest&lt;br /&gt;
activate in vscode, see [https://code.visualstudio.com/docs/python/testing#_enable-a-test-framework]: To enable testing, use the Python: Configure Tests command on the Command Palette.&lt;br /&gt;
&lt;br /&gt;
see https://docs.pytest.org/en/6.2.x/assert.html#assert&lt;br /&gt;
&lt;br /&gt;
test_1.py:&lt;br /&gt;
 import myLib # my custom lib to test&lt;br /&gt;
 &lt;br /&gt;
 class TestClass:&lt;br /&gt;
     def test_one(self):&lt;br /&gt;
         x = &amp;quot;this&amp;quot;&lt;br /&gt;
         assert &amp;quot;h&amp;quot; in x&lt;br /&gt;
 &lt;br /&gt;
     def test_two(self):&lt;br /&gt;
         assert myLib.multiply(1, 2) == 2&lt;br /&gt;
 &lt;br /&gt;
     def test_three(self):&lt;br /&gt;
         assert myLib.multiply(2, 2) == 2&lt;br /&gt;
&lt;br /&gt;
====project structure====&lt;br /&gt;
=====simplest structore: test next to script=====&lt;br /&gt;
Alternative without tests dir:&lt;br /&gt;
 ./main.py&lt;br /&gt;
 ./helper.py &lt;br /&gt;
 ./helper_test.py&lt;br /&gt;
with helper_test.py:&lt;br /&gt;
 from helper import fnc1&lt;br /&gt;
&lt;br /&gt;
=====standalone model with src and tests dirs=====&lt;br /&gt;
for this dir structure&lt;br /&gt;
 src/helper.py &lt;br /&gt;
 tests/helper_test.py&lt;br /&gt;
helper_test.py needs&lt;br /&gt;
 import sys&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 sys.path.insert(0, (Path(__file__).parent.parent / &amp;quot;src&amp;quot;).as_posix())&lt;br /&gt;
&lt;br /&gt;
=====standalone model with tests dir=====&lt;br /&gt;
if the test files are placed inside a ./tests/ dir&lt;br /&gt;
 ./main.py&lt;br /&gt;
 ./helper.py &lt;br /&gt;
 tests/helper_test.py&lt;br /&gt;
helper_test.py needs &lt;br /&gt;
 sys.path.insert(0, Path(__file__).parent.parent.as_posix())&lt;br /&gt;
 from helper import fnc1&lt;br /&gt;
&lt;br /&gt;
====parametrize====&lt;br /&gt;
 import pytest&lt;br /&gt;
 @pytest.mark.parametrize(&lt;br /&gt;
     (&amp;quot;test_input&amp;quot;, &amp;quot;expected&amp;quot;),&lt;br /&gt;
     [(&amp;quot;&amp;quot;, None), (&amp;quot;PT30M&amp;quot;, 30), (&amp;quot;PT3H&amp;quot;, 3 * 60), (&amp;quot;PT2H30M&amp;quot;, 2 * 60 + 30)],&lt;br /&gt;
 )&lt;br /&gt;
 def test_task_est_to_minutes(test_input: str, expected: int) -&amp;gt; None:&lt;br /&gt;
     assert task_est_to_minutes(test_input) == expected&lt;br /&gt;
&lt;br /&gt;
=====fixures: prepare and cleanup test_data=====&lt;br /&gt;
to automatically run before and after each testcase&lt;br /&gt;
 @pytest.fixture(autouse=True)&lt;br /&gt;
 def _setup_tests():  # noqa: ANN202&lt;br /&gt;
     cache_prepare_lists()&lt;br /&gt;
     cache_prepare_tasks()&lt;br /&gt;
 &lt;br /&gt;
     yield&lt;br /&gt;
 &lt;br /&gt;
     cache_cleanup_test_data()&lt;br /&gt;
&lt;br /&gt;
====Test coverage report====&lt;br /&gt;
 pip install pytest-cov&lt;br /&gt;
 # console output&lt;br /&gt;
 pytest --cov&lt;br /&gt;
 &lt;br /&gt;
 # html report in coverage_report/index.html with details per file&lt;br /&gt;
 pytest --cov --cov-report=html:coverage_report&lt;br /&gt;
to exclude a code block from coverage report, add&lt;br /&gt;
 # pragma: no cover&lt;br /&gt;
&lt;br /&gt;
=== Sleep / Wait for input ===&lt;br /&gt;
sleep for a while&lt;br /&gt;
 import time&lt;br /&gt;
 time.sleep(60)&lt;br /&gt;
&lt;br /&gt;
wait for user input&lt;br /&gt;
 input(&amp;quot;press Enter to close&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Suppress Warnings===&lt;br /&gt;
see [https://docs.python.org/3/library/warnings.html#temporarily-suppressing-warnings]&lt;br /&gt;
 import warnings&lt;br /&gt;
 warnings.filterwarnings(&amp;quot;ignore&amp;quot;, message=&amp;quot;.*native_field_num.*not found in message.*&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Logging===&lt;br /&gt;
====V4 minimal stdout logging====&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.addLevelName(logging.DEBUG, &amp;quot;D&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.INFO, &amp;quot;I&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.WARNING, &amp;quot;W&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.ERROR, &amp;quot;E&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.CRITICAL, &amp;quot;C&amp;quot;)&lt;br /&gt;
 logging.basicConfig(level=logging.INFO, format=&amp;quot;%(levelname)s: %(name)s: %(message)s&amp;quot;)&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
 LOGGER.setLevel(logging.INFO)&lt;br /&gt;
 logging.getLogger(&amp;quot;httpx&amp;quot;).setLevel(logging.WARNING)&lt;br /&gt;
 &lt;br /&gt;
 # usage of variables and rounding of numbers. (%d also rounds floats)&lt;br /&gt;
 LOGGER.info(&amp;quot;file %s has %d lines with avg %.1f char/line&amp;quot;, filename, lines, c_p_l)&lt;br /&gt;
&lt;br /&gt;
====V3 simple logging====&lt;br /&gt;
 # main file:&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.basicConfig(&lt;br /&gt;
     level=logging.INFO,&lt;br /&gt;
     format=&amp;quot;%(asctime)s\t%(levelname)s\t%(name)s\t%(message)s&amp;quot;,&lt;br /&gt;
     handlers=[&lt;br /&gt;
         RotatingFileHandler(&lt;br /&gt;
             Path(__file__).with_suffix(&amp;quot;.log&amp;quot;),&lt;br /&gt;
             maxBytes=10485760,  # 10 MB = 10*1024*1024,&lt;br /&gt;
             backupCount=1,&lt;br /&gt;
             encoding=&amp;quot;utf-8&amp;quot;,&lt;br /&gt;
         )&lt;br /&gt;
     ],&lt;br /&gt;
     datefmt=&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
 logger = logging.getLogger(Path(__file__).stem)&lt;br /&gt;
 logger.info(&amp;quot;Start&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # other files:&lt;br /&gt;
 import logging&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
&lt;br /&gt;
====V2: rotating file and STDOUT====&lt;br /&gt;
 # 1. setup&lt;br /&gt;
 import logging&lt;br /&gt;
 from logging.handlers import RotatingFileHandler&lt;br /&gt;
 &lt;br /&gt;
 logfile = &amp;quot;myApp.log&amp;quot;&lt;br /&gt;
 maxBytes = 20 * 1024 * 1024&lt;br /&gt;
 backupCount = 5&lt;br /&gt;
 loglevel_console = logging.INFO&lt;br /&gt;
 loglevel_file = logging.DEBUG&lt;br /&gt;
 &lt;br /&gt;
 # create logger&lt;br /&gt;
 logger = logging.getLogger(&amp;quot;root&amp;quot;)&lt;br /&gt;
 logger.setLevel(loglevel_file)&lt;br /&gt;
 &lt;br /&gt;
 # console handler&lt;br /&gt;
 ch = logging.StreamHandler()&lt;br /&gt;
 ch.setLevel(loglevel_console)&lt;br /&gt;
 &lt;br /&gt;
 # rotating file handler&lt;br /&gt;
 # fh = logging.FileHandler(logfile)&lt;br /&gt;
 fh = RotatingFileHandler(logfile, maxBytes=maxBytes, backupCount=backupCount)&lt;br /&gt;
 fh.setLevel(loglevel_file)&lt;br /&gt;
 &lt;br /&gt;
 # create formatter and add it to the handlers&lt;br /&gt;
 # %(name)s = LoggerName, %(threadName)s = TreadName&lt;br /&gt;
 formatter = logging.Formatter(&lt;br /&gt;
     &amp;quot;%(asctime)s - %(levelname)s - %(name)s - %(threadName)s - %(message)s &amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 fh.setFormatter(formatter)&lt;br /&gt;
 ch.setFormatter(formatter)&lt;br /&gt;
 &lt;br /&gt;
 # add the handlers to the logger&lt;br /&gt;
 logger.addHandler(ch)&lt;br /&gt;
 logger.addHandler(fh)&lt;br /&gt;
 &lt;br /&gt;
 logger.debug(&amp;quot;DebugMe&amp;quot;)&lt;br /&gt;
 logger.info(&amp;quot;Starting&amp;quot;)&lt;br /&gt;
 logger.warning(&amp;quot;Attention&amp;quot;)&lt;br /&gt;
 logger.error(&amp;quot;Something went wrong&amp;quot;)&lt;br /&gt;
 logger.critical(&amp;quot;Something seriously went wrong &amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # 2. in other files/modules now use&lt;br /&gt;
 import logging&lt;br /&gt;
 &lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 logger.info(&amp;quot;text&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 ...&lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     logger.exception(&amp;quot;Unhandeled exception&amp;quot;)&lt;br /&gt;
     quit()&lt;br /&gt;
&lt;br /&gt;
====V1: Simple====&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.basicConfig(&lt;br /&gt;
     level=logging.INFO,&lt;br /&gt;
     format=&amp;quot;%(asctime)s\t%(levelname)s\t%(message)s&amp;quot;,  # \t%(name)s&lt;br /&gt;
     filename=Path(__file__).with_suffix(&amp;quot;.log&amp;quot;), # uncomment to switch to stdout&lt;br /&gt;
     filemode=&amp;quot;a&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 logger.debug(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.info(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.warning(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.error(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.critical(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 try:&lt;br /&gt;
 ...&lt;br /&gt;
 except psycopg2.DatabaseError:&lt;br /&gt;
     logger.exception(&amp;quot;Database connection error: %s&amp;quot;)&lt;br /&gt;
     raise&lt;br /&gt;
&lt;br /&gt;
===Process Bar===&lt;br /&gt;
see [https://tqdm.github.io tqdm]&lt;br /&gt;
 from tqdm import tqdm&lt;br /&gt;
 for i in tqdm(range(10000)):&lt;br /&gt;
     ....&lt;br /&gt;
or&lt;br /&gt;
 with tqdm(total=total_iterations, desc=&amp;quot;Processing&amp;quot;) as progress_bar:&lt;br /&gt;
     ...&lt;br /&gt;
     progress_bar.update(1)&lt;br /&gt;
&lt;br /&gt;
===CGI Web development===&lt;br /&gt;
 # Print necessary headers.&lt;br /&gt;
 print(&amp;quot;Content-Type: text/html&amp;quot;)&lt;br /&gt;
 print()&lt;br /&gt;
 &lt;br /&gt;
 # errors and debugging info to browser&lt;br /&gt;
 import cgitb&lt;br /&gt;
 cgitb.enable()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Access URL or Form Parameters&lt;br /&gt;
 # V2 from https://www.tutorialspoint.com/python/python_cgi_programming.htm&lt;br /&gt;
 import cgi&lt;br /&gt;
 form = cgi.FieldStorage()&lt;br /&gt;
 username = form.getvalue(&#039;username&#039;)&lt;br /&gt;
 print(username)&lt;br /&gt;
&lt;br /&gt;
 # V1&lt;br /&gt;
 import sys&lt;br /&gt;
 import urllib.parse&lt;br /&gt;
 query = os.environ.get(&#039;QUERY_STRING&#039;)&lt;br /&gt;
 query = urllib.parse.unquote(query, errors=&amp;quot;surrogateescape&amp;quot;)&lt;br /&gt;
 d = dict(qc.split(&amp;quot;=&amp;quot;) for qc in query.split(&amp;quot;&amp;amp;&amp;quot;))&lt;br /&gt;
 print(d)&lt;br /&gt;
&lt;br /&gt;
====CGI Backend Returning JSONs====&lt;br /&gt;
 #!/usr/local/bin/python3.6&lt;br /&gt;
 # -*- coding: utf-8 -*-&lt;br /&gt;
 &lt;br /&gt;
 import cgi&lt;br /&gt;
 import json&lt;br /&gt;
 &lt;br /&gt;
 # Print necessary headers.&lt;br /&gt;
 print(&amp;quot;Content-type: application/json&amp;quot;)&lt;br /&gt;
 print()&lt;br /&gt;
 &lt;br /&gt;
 def get_form_parameter(para: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;asserts that a given parameter is set and returns its value&amp;quot;&lt;br /&gt;
     value = form.getvalue(para)&lt;br /&gt;
     assert value, f&amp;quot;Error: parameter {para} missing&amp;quot;&lt;br /&gt;
     assert value != &amp;quot;&amp;quot;, f&amp;quot;Error: parameter {para} missing&amp;quot;&lt;br /&gt;
     return value&lt;br /&gt;
  &lt;br /&gt;
 response = {}&lt;br /&gt;
 response[&#039;status&#039;] = &amp;quot;ok&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 try:&lt;br /&gt;
     action = get_form_parameter(&amp;quot;action&amp;quot;)&lt;br /&gt;
     response[&#039;action&#039;] = action&lt;br /&gt;
     if action == &amp;quot;myAction&amp;quot;:&lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     response[&#039;status&#039;] = &amp;quot;error&amp;quot;&lt;br /&gt;
     d = {&amp;quot;type&amp;quot;: str(type(e)), &amp;quot;text&amp;quot;: str(e)}&lt;br /&gt;
     response[&amp;quot;exception&amp;quot;] = d&lt;br /&gt;
 &lt;br /&gt;
 finally:&lt;br /&gt;
     print(json.dumps(response))&lt;br /&gt;
&lt;br /&gt;
==Databases==&lt;br /&gt;
===PostgreSQL===&lt;br /&gt;
====Improved helper function====&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Helper functions for DB access.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 import datetime as dt&lt;br /&gt;
 import logging&lt;br /&gt;
 &lt;br /&gt;
 import psycopg2&lt;br /&gt;
 import psycopg2.extras&lt;br /&gt;
 from creds_db import credentials as creds_db&lt;br /&gt;
 &lt;br /&gt;
 # Configure logging&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 &lt;br /&gt;
 connection: psycopg2.extensions.connection = None  # type: ignore&lt;br /&gt;
 cursor_query: psycopg2.extensions.cursor = None  # type: ignore&lt;br /&gt;
 cursor_write: psycopg2.extensions.cursor = None  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def connect() -&amp;gt; (&lt;br /&gt;
     tuple[&lt;br /&gt;
         psycopg2.extensions.connection,&lt;br /&gt;
         psycopg2.extensions.cursor,&lt;br /&gt;
         psycopg2.extensions.cursor,&lt;br /&gt;
     ]&lt;br /&gt;
 ):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Connect to the database.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     try:&lt;br /&gt;
         connection = psycopg2.connect(**creds_db)&lt;br /&gt;
         cursor_query = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)&lt;br /&gt;
         cursor_write = connection.cursor()&lt;br /&gt;
         logger.info(&lt;br /&gt;
             &amp;quot;Connected to database %s on host %s&amp;quot;,&lt;br /&gt;
             creds_db[&amp;quot;database&amp;quot;],&lt;br /&gt;
             creds_db[&amp;quot;host&amp;quot;],&lt;br /&gt;
         )&lt;br /&gt;
     except psycopg2.DatabaseError:&lt;br /&gt;
         logger.exception(&amp;quot;Database connection error: %s&amp;quot;)&lt;br /&gt;
         raise&lt;br /&gt;
     else:&lt;br /&gt;
         return connection, cursor_query, cursor_write&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def disconnect() -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Disconnect from the database.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     try:&lt;br /&gt;
         if cursor_query:&lt;br /&gt;
             cursor_query.close()&lt;br /&gt;
         if cursor_write:&lt;br /&gt;
             cursor_write.close()&lt;br /&gt;
         if connection:&lt;br /&gt;
             connection.close()&lt;br /&gt;
         logger.info(&amp;quot;Disconnected from the database&amp;quot;)&lt;br /&gt;
     except psycopg2.DatabaseError:&lt;br /&gt;
         logger.exception(&amp;quot;Error during disconnection: %s&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def exists(url: str, valid_hours: float) -&amp;gt; bool | None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Check if a record exists.&lt;br /&gt;
 &lt;br /&gt;
     Returns&lt;br /&gt;
     -------&lt;br /&gt;
     None for missing record&lt;br /&gt;
     True for valid record&lt;br /&gt;
     False for expired record&lt;br /&gt;
 &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=valid_hours)&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT delete_at &amp;gt; %s AS &amp;quot;valid&amp;quot;&lt;br /&gt;
 FROM my_table&lt;br /&gt;
 WHERE url = %s LIMIT 1;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_query.execute(sql, (delete_at, url))&lt;br /&gt;
     res = cursor_query.fetchone()&lt;br /&gt;
     return res[0] if res else None&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def query1(url: str) -&amp;gt; dict | None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Execute a query.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT *&lt;br /&gt;
 FROM my_table&lt;br /&gt;
 WHERE url = %s LIMIT 1&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_query.execute(sql, (url,))&lt;br /&gt;
     return cursor_query.fetchone()  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def insert(url: str, response: str, delete_in_hours: float) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Insert a record.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=delete_in_hours)&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 INSERT INTO my_table (url, response, delete_at)&lt;br /&gt;
 VALUES (%s, %s, %s);&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (url, response, delete_at))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def delete(url: str) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Delete a record.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 DELETE FROM my_table&lt;br /&gt;
 WHERE url = %s;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (url,))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def delete_expired(in_hours: float) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Delete records that expire in less than in_hours from now.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=in_hours)&lt;br /&gt;
 &lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 DELETE FROM my_table&lt;br /&gt;
 WHERE delete_at &amp;lt; %s;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (delete_at,))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 connection, cursor_query, cursor_write = connect()&lt;br /&gt;
&lt;br /&gt;
====Basics====&lt;br /&gt;
 import psycopg2&lt;br /&gt;
 import psycopg2.extras&lt;br /&gt;
 &lt;br /&gt;
 credentials = {&lt;br /&gt;
     &amp;quot;host&amp;quot;: &amp;quot;localhost&amp;quot;,&lt;br /&gt;
     &amp;quot;port&amp;quot;: 5432,&lt;br /&gt;
     &amp;quot;database&amp;quot;: &amp;quot;myDB&amp;quot;,&lt;br /&gt;
     &amp;quot;user&amp;quot;: &amp;quot;myUser&amp;quot;,&lt;br /&gt;
     &amp;quot;password&amp;quot;: &amp;quot;myPwd&amp;quot;,&lt;br /&gt;
 }&lt;br /&gt;
 connection = psycopg2.connect(**credentials)&lt;br /&gt;
 cursor = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)&lt;br /&gt;
 &lt;br /&gt;
 l_bind_vars = [[&amp;quot;A1&amp;quot;, &amp;quot;A2&amp;quot;], [&amp;quot;B1&amp;quot;, &amp;quot;B2&amp;quot;]]&lt;br /&gt;
 &lt;br /&gt;
 sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT * FROM myTable&lt;br /&gt;
 WHERE 1=1 &lt;br /&gt;
 AND status NOT IN (&#039;CLOSED&#039;) &lt;br /&gt;
 AND ColA = %s &lt;br /&gt;
 AND ColB = %s &lt;br /&gt;
 ORDER BY created DESC&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 cursor.execute(sql, l_bind_vars)&lt;br /&gt;
 d_data = dict(cursor.fetchone())&lt;br /&gt;
&lt;br /&gt;
====export result to csv file====&lt;br /&gt;
 sql1 = &amp;quot;SELECT * FROM table&amp;quot;&lt;br /&gt;
 sql2 = &amp;quot;COPY (&amp;quot; + sql1 + &amp;quot;) TO STDOUT WITH CSV HEADER DELIMITER &#039;\t&#039;&amp;quot;&lt;br /&gt;
         with open(&amp;quot;out.csv&amp;quot;, &amp;quot;w&amp;quot;) as file:&lt;br /&gt;
             cursor.copy_expert(sql2, file)&lt;br /&gt;
&lt;br /&gt;
====ReadConfigFile to connect to PostgreSQL====&lt;br /&gt;
from [http://www.postgresqltutorial.com/postgresql-python/connect/]&lt;br /&gt;
database.ini&lt;br /&gt;
 [postgresql]&lt;br /&gt;
 host=dbhost&lt;br /&gt;
 port=5432&lt;br /&gt;
 database=dbname&lt;br /&gt;
 user=dbuser&lt;br /&gt;
 password=dbpass&lt;br /&gt;
&lt;br /&gt;
 from configparser import ConfigParser&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def config(filename=&amp;quot;database.ini&amp;quot;, section=&amp;quot;postgresql&amp;quot;):&lt;br /&gt;
     parser = ConfigParser()&lt;br /&gt;
     parser.read(filename)&lt;br /&gt;
     # get section, default to postgresql&lt;br /&gt;
     db = {}&lt;br /&gt;
     if parser.has_section(section):&lt;br /&gt;
         params = parser.items(section)&lt;br /&gt;
         for param in params:&lt;br /&gt;
             db[param[0]] = param[1]&lt;br /&gt;
     else:&lt;br /&gt;
         raise Exception(&lt;br /&gt;
             &amp;quot;Section {0} not found in the {1} file&amp;quot;.format(section, filename)&lt;br /&gt;
         )&lt;br /&gt;
     return db&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
main.py&lt;br /&gt;
 import psycopg2&lt;br /&gt;
 from config import config&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def connect():&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Connect to the PostgreSQL database server&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     conn = None&lt;br /&gt;
     try:&lt;br /&gt;
         params = config()  # read connection parameters&lt;br /&gt;
         print(&amp;quot;Connecting to the PostgreSQL database...&amp;quot;)&lt;br /&gt;
         conn = psycopg2.connect(**params)&lt;br /&gt;
         cur = conn.cursor()  # create a cursor&lt;br /&gt;
         print(&amp;quot;PostgreSQL database version:&amp;quot;)&lt;br /&gt;
         cur.execute(&amp;quot;SELECT version()&amp;quot;)  # execute a statement&lt;br /&gt;
         db_version = cur.fetchone()&lt;br /&gt;
         print(db_version)&lt;br /&gt;
         cur.close()&lt;br /&gt;
     except (Exception, psycopg2.DatabaseError) as error:&lt;br /&gt;
         print(error)&lt;br /&gt;
     finally:&lt;br /&gt;
         if conn is not None:&lt;br /&gt;
             conn.close()&lt;br /&gt;
             print(&amp;quot;Database connection closed.&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     connect()&lt;br /&gt;
&lt;br /&gt;
===SQL Lite / SQLite===&lt;br /&gt;
see page [[SQLite]]&lt;br /&gt;
&lt;br /&gt;
===InfluxDB===&lt;br /&gt;
see [[InfluxDB]]&lt;br /&gt;
&lt;br /&gt;
==Internet Access==&lt;br /&gt;
===Send E-Mails===&lt;br /&gt;
see [[Python - eMail]]&lt;br /&gt;
&lt;br /&gt;
===Download data using browser UA / REST===&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 headers = {&lt;br /&gt;
     &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0&amp;quot;,&lt;br /&gt;
 }&lt;br /&gt;
 resp = requests.get(&lt;br /&gt;
     url,&lt;br /&gt;
     headers=headers,&lt;br /&gt;
     timeout=3,  # timeout in sec, requests should always have a timeout!&lt;br /&gt;
     # timeout=(1,3), # 1s connect-timout, 3s read-timeout&lt;br /&gt;
 )&lt;br /&gt;
 if resp.status_code != 200:  # noqa: PLR2004&lt;br /&gt;
     msg = f&amp;quot;E: bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
     raise Exception(msg)  # noqa: TRY002&lt;br /&gt;
&lt;br /&gt;
====Download only if cache is too old====&lt;br /&gt;
 import time&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 def fetch_url_or_cache(file_cache: Path, url) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Fetch URL and cache in a file.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if check_cache_file_available_and_recent(&lt;br /&gt;
         file_path=file_cache, max_age=3600, verbose=False&lt;br /&gt;
     ):&lt;br /&gt;
         with file_cache.open(mode=&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
             s = fh.read()&lt;br /&gt;
     else:&lt;br /&gt;
         s = fetch(url=url)&lt;br /&gt;
         with file_cache.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
             fh.writelines(s)&lt;br /&gt;
     return s&lt;br /&gt;
 &lt;br /&gt;
 def check_cache_file_available_and_recent(&lt;br /&gt;
     file_path: Path,&lt;br /&gt;
     max_age: int = 3500,&lt;br /&gt;
 ) -&amp;gt; bool:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Check if cache file exists and is recent.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cache_good = False&lt;br /&gt;
     if file_path.exists() and (time.time() - file_path.stat().st_mtime &amp;lt; max_age):&lt;br /&gt;
         cache_good = True&lt;br /&gt;
     return cache_good&lt;br /&gt;
 &lt;br /&gt;
 # or&lt;br /&gt;
 def check_cache_file_available_and_recent_verbose(&lt;br /&gt;
     file_path: Path, max_age: int = 3600, *, verbose: bool = False&lt;br /&gt;
 ) -&amp;gt; bool:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Check if cache file exists and is recent.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cache_good = True&lt;br /&gt;
     if not file_path.exists():&lt;br /&gt;
         if verbose:&lt;br /&gt;
             print(f&amp;quot;No Cache available: {file_path}&amp;quot;)&lt;br /&gt;
         cache_good = False&lt;br /&gt;
     if cache_good and time.time() - (time.time() - file_path.stat().st_mtime &amp;lt; max_age):&lt;br /&gt;
         if verbose:&lt;br /&gt;
             print(f&amp;quot;Cache too old: {file_path}&amp;quot;)&lt;br /&gt;
         cache_good = False&lt;br /&gt;
     return cache_good&lt;br /&gt;
 &lt;br /&gt;
 def fetch(url) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Fetch URL.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     headers = {&lt;br /&gt;
         &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0&amp;quot;,  # noqa: E501&lt;br /&gt;
     }&lt;br /&gt;
     resp = requests.get(url, headers=headers, timeout=5)&lt;br /&gt;
     if resp.status_code == 200:  # noqa: PLR2004&lt;br /&gt;
         return resp.content.decode(&amp;quot;ascii&amp;quot;)&lt;br /&gt;
     else:  # noqa: RET505&lt;br /&gt;
         msg = f&amp;quot;E: bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
         raise Exception(msg)  # noqa: TRY002&lt;br /&gt;
&lt;br /&gt;
====Download using urllib.request====&lt;br /&gt;
 from urllib.request import urlopen&lt;br /&gt;
 &lt;br /&gt;
 url = &amp;quot;https://pomber.github.io/covid19/timeseries.json&amp;quot;&lt;br /&gt;
 with urlopen(url) as response:  # noqa: S310&lt;br /&gt;
     response_text = response.read()&lt;br /&gt;
&lt;br /&gt;
===HTML parsing and extracting of elements===&lt;br /&gt;
V2: via BeautifulSoup&lt;br /&gt;
 from bs4 import BeautifulSoup  # pip install beautifulsoup4&lt;br /&gt;
 &lt;br /&gt;
 soup = BeautifulSoup(cont, features=&amp;quot;html.parser&amp;quot;)&lt;br /&gt;
 my_element = soup.find(&amp;quot;div&amp;quot;, {&amp;quot;class&amp;quot;: &amp;quot;user-formatted-inner&amp;quot;})&lt;br /&gt;
 my_body = my_element.prettify()&lt;br /&gt;
 # my_body = my_element.encode()&lt;br /&gt;
 # my_body = str(my_element)&lt;br /&gt;
&lt;br /&gt;
V1: via lxml and xpath&lt;br /&gt;
 from lxml import html&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 page = requests.get(url)&lt;br /&gt;
 tree = html.fromstring(page.content)&lt;br /&gt;
 tbody_trs = tree.xpath(&amp;quot;//*/tbody/tr&amp;quot;)&lt;br /&gt;
 l_rows = []&lt;br /&gt;
 for tr in tbody_trs:&lt;br /&gt;
     l_columns = []&lt;br /&gt;
     if len(tr) != 15:&lt;br /&gt;
         continue&lt;br /&gt;
     for td in tr:&lt;br /&gt;
         l_columns.append(td.text_content())&lt;br /&gt;
         l_rows.append(list(l_columns))&lt;br /&gt;
&lt;br /&gt;
===HTML entities to unicode===&lt;br /&gt;
 import html&lt;br /&gt;
 cont = html.unescape(cont)&lt;br /&gt;
&lt;br /&gt;
==GUI Interactions==&lt;br /&gt;
===Take Screenshot===&lt;br /&gt;
 import pyautogui # (c:\Python\Scripts\)pip install pyautogui&lt;br /&gt;
 # pyautogui does only support screenshots on monitor #1&lt;br /&gt;
 ...&lt;br /&gt;
 screenshot = pyautogui.screenshot()&lt;br /&gt;
 # screenshot = pyautogui.screenshot(region=(screenshotX,screenshotY, screenshotW, screenshotH))&lt;br /&gt;
 screenshot = np.array(screenshot) &lt;br /&gt;
 # Convert RGB to BGR &lt;br /&gt;
 screenshot = screenshot[:, :, ::-1].copy()&lt;br /&gt;
&lt;br /&gt;
===Mouse Actions===&lt;br /&gt;
 def clickIt(x,y,key=&amp;quot;&amp;quot;) :&lt;br /&gt;
   x0, y0 = pyautogui.position()&lt;br /&gt;
   if key != &amp;quot;&amp;quot;: # crtl, shift&lt;br /&gt;
     pyautogui.keyDown(key)&lt;br /&gt;
   pyautogui.moveTo(x, y, duration=0.2)&lt;br /&gt;
   pyautogui.click(x=x , y=y, button=&#039;left&#039;, clicks=1, interval=0.1)&lt;br /&gt;
   if key != &amp;quot;&amp;quot;: # crtl, shift&lt;br /&gt;
     pyautogui.keyUp(key)&lt;br /&gt;
   pyautogui.moveTo(x0, y0)&lt;br /&gt;
&lt;br /&gt;
===Web Automation===&lt;br /&gt;
 from selenium import webdriver&lt;br /&gt;
 from selenium.webdriver.common.keys import Keys&lt;br /&gt;
 &lt;br /&gt;
 # from selenium.webdriver import Firefox&lt;br /&gt;
 from selenium.webdriver.firefox.options import Options&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 import glob&lt;br /&gt;
 &lt;br /&gt;
 class StravaUserMapDL():&lt;br /&gt;
     def __init__(self):&lt;br /&gt;
         self.driver = webdriver.Firefox()&lt;br /&gt;
 &lt;br /&gt;
     def login(self):&lt;br /&gt;
         driver = self.driver&lt;br /&gt;
         url = &amp;quot;https://www.somewebpage.com&amp;quot;&lt;br /&gt;
         email = &amp;quot;myemail&amp;quot;&lt;br /&gt;
         password = &amp;quot;mypassword&amp;quot;&lt;br /&gt;
         driver.get(url)&lt;br /&gt;
 &lt;br /&gt;
         title = driver.title&lt;br /&gt;
         urlIs = driver.current_url&lt;br /&gt;
         cont = driver.page_source #  as string&lt;br /&gt;
         FILE = open(filename,&amp;quot;w&amp;quot;) # w = overWrite file ; a = append to file&lt;br /&gt;
         FILE.write(cont)&lt;br /&gt;
         FILE.close()         &lt;br /&gt;
 &lt;br /&gt;
         # handle login if urlIs != url&lt;br /&gt;
         if (urlIs != url): &lt;br /&gt;
             # activate checkbox &#039;remember_me&#039;&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;remember_me&#039;)&lt;br /&gt;
             if (elem.is_selected() == False):&lt;br /&gt;
                 elem.click()&lt;br /&gt;
             assert elem.is_selected() == True&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;email&#039;)&lt;br /&gt;
             elem.send_keys(email)&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;password&#039;)&lt;br /&gt;
             elem.send_keys(password)&lt;br /&gt;
             elem.send_keys(Keys.RETURN)&lt;br /&gt;
             # Wait until login pages is replaced by real page&lt;br /&gt;
             urlIs = driver.current_url&lt;br /&gt;
             while (urlIs != url):&lt;br /&gt;
                 time.sleep(1)&lt;br /&gt;
                 urlIs = driver.current_url&lt;br /&gt;
             print (urlIs)&lt;br /&gt;
 &lt;br /&gt;
             # results = driver.find_elements_by_class_name(&#039;following&#039;)&lt;br /&gt;
             # results = driver.find_elements_by_tag_name(&#039;li&#039;)&lt;br /&gt;
 &lt;br /&gt;
             # print(results[0].text)&lt;br /&gt;
         assert (urlIs == url)&lt;br /&gt;
&lt;br /&gt;
===Unit Tests using Web Automation===&lt;br /&gt;
 import unittest&lt;br /&gt;
 from selenium import webdriver&lt;br /&gt;
 from selenium.webdriver.common.keys import Keys&lt;br /&gt;
 &lt;br /&gt;
 #from selenium.webdriver import Firefox&lt;br /&gt;
 from selenium.webdriver.firefox.options import Options&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 class PythonOrgSearch(unittest.TestCase):&lt;br /&gt;
 #    def __init__(self,asdf):&lt;br /&gt;
 #        self.driver = webdriver.Firefox() &lt;br /&gt;
 &lt;br /&gt;
     def setUp(self):&lt;br /&gt;
         print (&amp;quot;setUp&amp;quot;)&lt;br /&gt;
         # headless mode:&lt;br /&gt;
         # opts = Options()&lt;br /&gt;
         # opts.set_headless()&lt;br /&gt;
         # assert opts.headless  # Operating in headless mode&lt;br /&gt;
         # self.driver = webdriver.Firefox(options=opts)&lt;br /&gt;
 &lt;br /&gt;
         self.driver = webdriver.Firefox()&lt;br /&gt;
 &lt;br /&gt;
     def test_search_in_python_org(self):&lt;br /&gt;
         driver = self.driver&lt;br /&gt;
         driver.get(&amp;quot;http://www.python.org&amp;quot;)&lt;br /&gt;
         self.assertIn(&amp;quot;Python&amp;quot;, driver.title)&lt;br /&gt;
         elem = driver.find_element_by_name(&amp;quot;q&amp;quot;)&lt;br /&gt;
         elem.send_keys(&amp;quot;pycon&amp;quot;)&lt;br /&gt;
         elem.send_keys(Keys.RETURN)&lt;br /&gt;
         assert &amp;quot;No results found.&amp;quot; not in driver.page_source&lt;br /&gt;
         print (&amp;quot;fertig: python_org&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     def tearDown(self):&lt;br /&gt;
         print (&amp;quot;tearDown&amp;quot;)&lt;br /&gt;
         print (&amp;quot;close Firefox&amp;quot;)&lt;br /&gt;
         self.driver.close() # close tab&lt;br /&gt;
         self.driver.quit() # quit browser&lt;br /&gt;
         # os._exit(1) # exit unittest without Exception&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     try:&lt;br /&gt;
         unittest.main()&lt;br /&gt;
     except SystemExit as e:&lt;br /&gt;
         os._exit(1)&lt;br /&gt;
&lt;br /&gt;
==Cryptography and Hashing==&lt;br /&gt;
====Hashing via SHA256====&lt;br /&gt;
 def gen_SHA256_string(s: str) -&amp;gt; str:&lt;br /&gt;
     m = hashlib.sha256()&lt;br /&gt;
     m.update(s.encode(&amp;quot;ascii&amp;quot;))&lt;br /&gt;
     return m.hexdigest()&lt;br /&gt;
&lt;br /&gt;
====Hashing via MD5====&lt;br /&gt;
(MD5 is not secure, better use SHA256)&lt;br /&gt;
 def gen_MD5_string(s: str) -&amp;gt; str:&lt;br /&gt;
     m = hashlib.md5()&lt;br /&gt;
     m.update(s.encode(&amp;quot;ascii&amp;quot;))&lt;br /&gt;
     return m.hexdigest()&lt;br /&gt;
&lt;br /&gt;
====Password hashing via bcrypt====&lt;br /&gt;
 import bcrypt&lt;br /&gt;
 pwd = &#039;geheim&#039;&lt;br /&gt;
 pwd = pwd.encode(&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 # or &lt;br /&gt;
 pwd = b&#039;geheim&#039;&lt;br /&gt;
 &lt;br /&gt;
 hashed = bcrypt.hashpw(pwd, bcrypt.gensalt())&lt;br /&gt;
 if bcrypt.checkpw(pwd, hashed):&lt;br /&gt;
     print(&amp;quot;It Matches!&amp;quot;)&lt;br /&gt;
     print(hashed.decode(&amp;quot;utf-8&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
To use version 2a instead of 2b (default):&lt;br /&gt;
 bcrypt.gensalt(prefix=b&amp;quot;2a&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Multiprocessing, subprocesses and Threading==&lt;br /&gt;
see [[Python_-_Multithreading]] as well&lt;br /&gt;
&lt;br /&gt;
use processes for CPU limited work &amp;lt;br/&amp;gt;&lt;br /&gt;
use threads for I/O limited work&lt;br /&gt;
&lt;br /&gt;
===Simple single process===&lt;br /&gt;
 import subprocess&lt;br /&gt;
 process = subprocess.run([&amp;quot;sudo&amp;quot;, &amp;quot;du&amp;quot;, &amp;quot;--max-depth=1&amp;quot;, mydir], capture_output=True, text=True)&lt;br /&gt;
 print (process.stdout)&lt;br /&gt;
old, depricated way:&lt;br /&gt;
 os.system( &amp;quot;gnuplot &amp;quot; + gpfile)&lt;br /&gt;
&lt;br /&gt;
===Multiprocessing===&lt;br /&gt;
see [[Python - Multithreading]] as well&lt;br /&gt;
&lt;br /&gt;
V2 using pool and starmap&lt;br /&gt;
 import multiprocessing&lt;br /&gt;
 import os&lt;br /&gt;
 &lt;br /&gt;
 def worker(i: int, s: str) -&amp;gt; list:&lt;br /&gt;
     result = (i, s, os.getpid())&lt;br /&gt;
     return result&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     # gen. pile of work&lt;br /&gt;
     l_pile_of_work = []&lt;br /&gt;
     for i in range(1_000):&lt;br /&gt;
         tup = (i, &amp;quot;n&amp;quot; + str(i))&lt;br /&gt;
         l_pile_of_work.append((tup))&lt;br /&gt;
     # gen pool of processes&lt;br /&gt;
     num_processes = min(multiprocessing.cpu_count(), len(l_pile_of_work))&lt;br /&gt;
     pool = multiprocessing.Pool(processes=num_processes)&lt;br /&gt;
     # start processes on pile of work&lt;br /&gt;
     l_results_unsorted = pool.starmap(&lt;br /&gt;
         func=worker, iterable=l_pile_of_work  # each item is a list of 2 parameters&lt;br /&gt;
     )&lt;br /&gt;
     &lt;br /&gt;
     # or if only one parameter:&lt;br /&gt;
     # l_results_unsorted = pool.map(doit_de_district, l_pile_of_work)&lt;br /&gt;
     &lt;br /&gt;
     l_results = sorted(l_results_unsorted)  # sort by i&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
V1&lt;br /&gt;
 import subprocess&lt;br /&gt;
 l_subprocesses = []  # queue list of subprocesses&lt;br /&gt;
 max_processes = 4&lt;br /&gt;
 &lt;br /&gt;
 def process_enqueue(new_process_parameters):&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     # wait for free slot&lt;br /&gt;
     while len(l_subprocesses) &amp;gt;= max_processes:&lt;br /&gt;
         process_remove_finished_from_queue()&lt;br /&gt;
         time.sleep(0.1)  # sleep 0.1s&lt;br /&gt;
     process = subprocess.Popen(new_process_parameters,&lt;br /&gt;
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE,&lt;br /&gt;
                                universal_newlines=True)&lt;br /&gt;
     l_subprocesses.append(process)&lt;br /&gt;
 &lt;br /&gt;
 def process_remove_finished_from_queue():&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     i = 0&lt;br /&gt;
     while i &amp;lt;= len(l_subprocesses) - 1:&lt;br /&gt;
         process = l_subprocesses[i]&lt;br /&gt;
         if process.poll != None:  # has already finished&lt;br /&gt;
             process_print_output(process)&lt;br /&gt;
             l_subprocesses.pop(i)&lt;br /&gt;
         else:  # still running&lt;br /&gt;
             i += 1&lt;br /&gt;
 &lt;br /&gt;
 def process_print_output(process):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;waits for process to finish and prints process output&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     stdout, stderr = process.communicate()&lt;br /&gt;
     if stdout != &#039;&#039;:&lt;br /&gt;
         print(f&#039;Out: {stdout}&#039;)&lt;br /&gt;
     if stderr != &#039;&#039;:&lt;br /&gt;
         print(f&#039;ERROR: {stderr}&#039;)&lt;br /&gt;
 &lt;br /&gt;
 def process_wait_for_all_finished():&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     for process in l_subprocesses:&lt;br /&gt;
         process_print_output(process)&lt;br /&gt;
     l_subprocesses = []  # empty list of done subprocesses&lt;br /&gt;
 &lt;br /&gt;
 process_enqueue(l_parameters1)&lt;br /&gt;
 ...&lt;br /&gt;
 process_enqueue(l_parameters999)&lt;br /&gt;
 process_wait_for_all_finished()&lt;br /&gt;
&lt;br /&gt;
===Threading===&lt;br /&gt;
 import threading&lt;br /&gt;
 import queue&lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 def worker(q_work: queue.Queue, results: dict):&lt;br /&gt;
     while not q_work.empty():&lt;br /&gt;
         i, s = q_work.get()&lt;br /&gt;
         time.sleep(.1)&lt;br /&gt;
         result = (i, s, os.getpid())&lt;br /&gt;
         results[i] = result&lt;br /&gt;
         q_work.task_done()&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &#039;__main__&#039;:&lt;br /&gt;
     d_results = {}  # threads can write into dict&lt;br /&gt;
     # gen. pile of work&lt;br /&gt;
     l_pile_of_work = []&lt;br /&gt;
     for i in range(1_000):&lt;br /&gt;
         tup = (i, &amp;quot;n&amp;quot;+str(i))&lt;br /&gt;
         l_pile_of_work.append((tup))&lt;br /&gt;
     # convert list of work to queue&lt;br /&gt;
     q_pile_of_work = queue.Queue(&lt;br /&gt;
         maxsize=len(l_pile_of_work))  # maxsize=0 -&amp;gt; unlimited&lt;br /&gt;
     for params in l_pile_of_work:&lt;br /&gt;
         q_pile_of_work.put(params)&lt;br /&gt;
     # gen threads&lt;br /&gt;
     num_threads = 100&lt;br /&gt;
     l_threads = []  # List of threads, not used here&lt;br /&gt;
     for i in range(num_threads):&lt;br /&gt;
         t = threading.Thread(name=&#039;myThread-&#039;+str(i),&lt;br /&gt;
                              target=worker,&lt;br /&gt;
                              args=(q_pile_of_work, d_results),&lt;br /&gt;
                              daemon=True)&lt;br /&gt;
         l_threads.append(t)&lt;br /&gt;
         t.start()&lt;br /&gt;
     q_pile_of_work.join()  # wait for all threas to complete&lt;br /&gt;
     l_results_unsorted = d_results.values()&lt;br /&gt;
     l_results = sorted(l_results_unsorted)  # sort by i&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===asyncio — Asynchronous I/O===&lt;br /&gt;
see https://docs.python.org/3/library/asyncio-task.html&lt;br /&gt;
 import asyncio&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 # basics&lt;br /&gt;
 # task = asyncio.create_task(coro())&lt;br /&gt;
 # Wrap the coro coroutine into a Task and schedule its execution. Return the Task object.&lt;br /&gt;
 &lt;br /&gt;
 # sleep&lt;br /&gt;
 # await asyncio.sleep(1)&lt;br /&gt;
 &lt;br /&gt;
 # Running Tasks Concurrently and gathers the return values in list L&lt;br /&gt;
 # L = await asyncio.gather(coro(x1,y1), coro(x2,y2), coro(x3,y3))&lt;br /&gt;
 &lt;br /&gt;
 async def say_after(delay, what):&lt;br /&gt;
     # Coroutines declared with the async/await syntax&lt;br /&gt;
     await asyncio.sleep(delay)&lt;br /&gt;
     print(what)&lt;br /&gt;
 &lt;br /&gt;
 async def main():&lt;br /&gt;
     # The asyncio.create_task() function to run coroutines concurrently as asyncio Tasks.&lt;br /&gt;
     task1 = asyncio.create_task(&lt;br /&gt;
         say_after(1, &#039;hello&#039;))&lt;br /&gt;
 &lt;br /&gt;
     task2 = asyncio.create_task(&lt;br /&gt;
         say_after(1, &#039;world&#039;))&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;started at {time.strftime(&#039;%X&#039;)}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     # Wait until both tasks are completed&lt;br /&gt;
     await task1&lt;br /&gt;
     await task2&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;finished at {time.strftime(&#039;%X&#039;)}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 asyncio.run(main())&lt;br /&gt;
&lt;br /&gt;
==Pandas==&lt;br /&gt;
see [[Pandas]]&lt;br /&gt;
&lt;br /&gt;
==Matplotlib==&lt;br /&gt;
see [[Matplotlib]]&lt;br /&gt;
&lt;br /&gt;
== GUI via tkinter==&lt;br /&gt;
 import tkinter as tk  # no need to install via pip&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 class App(tk.Tk):&lt;br /&gt;
     def __init__(self):&lt;br /&gt;
         super().__init__()&lt;br /&gt;
         self.title(&amp;quot;robot-CClicker&amp;quot;)&lt;br /&gt;
         self.geometry(&amp;quot;200x200+0+1080&amp;quot;)&lt;br /&gt;
         self.resizable(width=False, height=False)&lt;br /&gt;
 &lt;br /&gt;
         self.l_buttons = []&lt;br /&gt;
         self.__create_widgets()&lt;br /&gt;
 &lt;br /&gt;
     def __create_widgets(self):&lt;br /&gt;
         self.btn_click500 = tk.Button(&lt;br /&gt;
             master=self,&lt;br /&gt;
             text=&amp;quot;500 clicks&amp;quot;,&lt;br /&gt;
             width=20,&lt;br /&gt;
             command=lambda: self.clickBigCookie(500),&lt;br /&gt;
         )&lt;br /&gt;
         self.l_buttons.append(self.btn_click500)&lt;br /&gt;
 &lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button.pack(anchor=tk.W)&lt;br /&gt;
 &lt;br /&gt;
     def clickBigCookie(self, num):&lt;br /&gt;
         # TODO: the disabling of the buttons is not working&lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button[&amp;quot;state&amp;quot;] = tk.DISABLED&lt;br /&gt;
         helper.clickIt(self.posBigCockie[0], self.posBigCockie[1], num=num)&lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button[&amp;quot;state&amp;quot;] = tk.NORMAL&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     app = App()&lt;br /&gt;
     app.mainloop()&lt;br /&gt;
&lt;br /&gt;
==Protobuf==&lt;br /&gt;
===convert .proto file to python class===&lt;br /&gt;
see https://www.datascienceblog.net/post/programming/essential-protobuf-guide-python/&lt;br /&gt;
 protoc my_message.proto --python_out ./ &lt;br /&gt;
&lt;br /&gt;
===read/decode protobuf message===&lt;br /&gt;
parse message from file&lt;br /&gt;
 import machine_message_pb2&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.bin&amp;quot;, &amp;quot;rb&amp;quot;) as f:&lt;br /&gt;
     my_message = my_message_pb2.my_message()&lt;br /&gt;
     my_message.ParseFromString(f.read())&lt;br /&gt;
 print(machine_message)&lt;br /&gt;
&lt;br /&gt;
===create/encode protobuf message===&lt;br /&gt;
 import machine_message_pb2&lt;br /&gt;
 &lt;br /&gt;
 my_message = my_message_pb2.my_message()&lt;br /&gt;
 my_message.data.field1 = 1&lt;br /&gt;
 my_message.data.field2 = &amp;quot;asdf&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.bin&amp;quot;, &amp;quot;wb&amp;quot;) as f:&lt;br /&gt;
     f.write(my_message.data.SerializeToString())&lt;br /&gt;
&lt;br /&gt;
==venv / virtual environments==&lt;br /&gt;
 pip install --upgrade pip&lt;br /&gt;
 python -m venv .venv --prompt $(basename $(pwd))&lt;br /&gt;
 source .venv/bin/activate&lt;br /&gt;
 pip install -r requirements.txt&lt;br /&gt;
 ...&lt;br /&gt;
 deactivate&lt;br /&gt;
&lt;br /&gt;
==Pydantic data validation==&lt;br /&gt;
===Function input parameter validation===&lt;br /&gt;
see https://docs.pydantic.dev/usage/validation_decorator/&lt;br /&gt;
 @validate_arguments&lt;br /&gt;
 def sum(x: int, y: float) -&amp;gt; float:&lt;br /&gt;
     print(x, y)&lt;br /&gt;
     return x + y&lt;br /&gt;
  &lt;br /&gt;
 print(sum(1.1, 1.1))&lt;br /&gt;
 # -&amp;gt; 2.1&lt;br /&gt;
&lt;br /&gt;
==Read config file==&lt;br /&gt;
===TOML===&lt;br /&gt;
see https://learnxinyminutes.com/docs/toml/&lt;br /&gt;
&lt;br /&gt;
minimal example:&lt;br /&gt;
config.toml&lt;br /&gt;
 # SAP API endpoint&lt;br /&gt;
 [general]&lt;br /&gt;
 sleep_time = 60&lt;br /&gt;
 use_proxy = true&lt;br /&gt;
tool.py&lt;br /&gt;
 try:&lt;br /&gt;
     import tomllib  # comes with python3.11&lt;br /&gt;
 except ModuleNotFoundError:&lt;br /&gt;
     import tomli as tomllib  # pip install tomli&lt;br /&gt;
 with open(&amp;quot;config.toml&amp;quot;, &amp;quot;rb&amp;quot;) as f:&lt;br /&gt;
    o = tomllib.load(f)  # type: ignore&lt;br /&gt;
 if o[&amp;quot;settings&amp;quot;][&amp;quot;use_proxy&amp;quot;]:&lt;br /&gt;
 ...&lt;br /&gt;
for validation, see https://realpython.com/python-toml/&lt;br /&gt;
&lt;br /&gt;
====TOML Write====&lt;br /&gt;
In for deployments I sometime want to modify a TOML config file for production, based on the dev version.&lt;br /&gt;
 import tomli_w  # pip install tomli-w&lt;br /&gt;
 p_in = Path(__file__).parent.parent / &amp;quot;.streamlit/config.toml&amp;quot;&lt;br /&gt;
 p_out = p_in.parent / &amp;quot;config-prod.toml&amp;quot; &lt;br /&gt;
 with p_in.open(&amp;quot;rb&amp;quot;) as fh:&lt;br /&gt;
     o = tomllib.load(fh)&lt;br /&gt;
 o[&amp;quot;server&amp;quot;][&amp;quot;fileWatcherType&amp;quot;] = &amp;quot;none&amp;quot;&lt;br /&gt;
 del o[&amp;quot;server&amp;quot;][&amp;quot;address&amp;quot;]&lt;br /&gt;
  with p_out.open(&amp;quot;wb&amp;quot;) as fh:&lt;br /&gt;
     tomli_w.dump(o, fh)&lt;br /&gt;
&lt;br /&gt;
===ConfigParser: INI Config File Reading===&lt;br /&gt;
better use TOML&lt;br /&gt;
&lt;br /&gt;
Config.ini&lt;br /&gt;
 [Section1]&lt;br /&gt;
 Cursor         = 205E18&lt;br /&gt;
 Grandma        =  18E18&lt;br /&gt;
 Farm           =  11E18&lt;br /&gt;
 Mine           = 514E18&lt;br /&gt;
 Factory        = 155E18&lt;br /&gt;
test.py&lt;br /&gt;
 from configparser import ConfigParser&lt;br /&gt;
 &lt;br /&gt;
 config = ConfigParser(&lt;br /&gt;
     interpolation=None&lt;br /&gt;
 )  # interpolation=None -&amp;gt; treats % in values as char % instead of interpreting it&lt;br /&gt;
 config.read(&amp;quot;Config.ini&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(config.getint(&amp;quot;Section1&amp;quot;, &amp;quot;key1&amp;quot;))&lt;br /&gt;
 print(config.getfloat(&amp;quot;Section1&amp;quot;, &amp;quot;key2&amp;quot;))&lt;br /&gt;
 print(config.get(&amp;quot;Section1&amp;quot;, &amp;quot;key3&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 for sec in config.sections():&lt;br /&gt;
     d_settings = {}&lt;br /&gt;
     for key in config.options(sec):&lt;br /&gt;
         value = config.get(sec, key)&lt;br /&gt;
         d_settings[key] = value&lt;br /&gt;
         print(&amp;quot;%15s : %s&amp;quot; % (key, value))&lt;br /&gt;
&lt;br /&gt;
==VCard / vcf==&lt;br /&gt;
 import codecs&lt;br /&gt;
 import vobject  # pip install vobject&lt;br /&gt;
 &lt;br /&gt;
 obj = vobject.readComponents(codecs.open(file_in, encoding=&amp;quot;utf-8&amp;quot;).read())  # type: ignore&lt;br /&gt;
 contacts: list[vobject.base.Component] = list(obj)  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 card = contacts[0]&lt;br /&gt;
 &lt;br /&gt;
 if &amp;quot;bday&amp;quot; not in card.contents:&lt;br /&gt;
     continue&lt;br /&gt;
 &lt;br /&gt;
 # bday: remove &#039;VALUE&#039;: [&#039;DATE&#039;]&lt;br /&gt;
 card.contents[&amp;quot;bday&amp;quot;][0].params = {}  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 # remove all fields but &amp;quot;bday&amp;quot;, &amp;quot;n&amp;quot;&lt;br /&gt;
 for key in card.contents.copy():  # loop over copy, to allow for deleting keys&lt;br /&gt;
     if key not in (&amp;quot;n&amp;quot;, &amp;quot;bday&amp;quot;):&lt;br /&gt;
         del card.contents[key]&lt;br /&gt;
 &lt;br /&gt;
 # recreate fn based on n&lt;br /&gt;
 card.add(&amp;quot;fn&amp;quot;)&lt;br /&gt;
 n = card.contents[&amp;quot;n&amp;quot;][0]&lt;br /&gt;
 fn = f&amp;quot;{n.value.given} {n.value.additional} {n.value.family}&amp;quot;  # type: ignore&lt;br /&gt;
 fn = re.sub(r&amp;quot;\s+&amp;quot;, &amp;quot; &amp;quot;, fn)&lt;br /&gt;
 card.fn.value = fn  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.vcf&amp;quot;, mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fhOut:&lt;br /&gt;
     fhOut.write(card.serialize())&lt;br /&gt;
&lt;br /&gt;
==Sentry Exception monitoring==&lt;br /&gt;
see [[Sentry]]&lt;br /&gt;
==MQTT==&lt;br /&gt;
 #!/usr/bin/env python3.12&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Read power data from Tasmota device via MQTT.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 import json&lt;br /&gt;
 from typing import Any&lt;br /&gt;
 &lt;br /&gt;
 import paho.mqtt.client as mqtt&lt;br /&gt;
 from mqtt_credentials import hostname, password, port, username&lt;br /&gt;
 from paho.mqtt.reasoncodes import ReasonCode&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_connect(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     flags: dict[str, int],&lt;br /&gt;
     reason_code: ReasonCode,&lt;br /&gt;
     properties: Any,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when the client connects MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     if reason_code == 0:&lt;br /&gt;
         print(&amp;quot;Connected to MQTT&amp;quot;)&lt;br /&gt;
         # print(f&amp;quot;MQTT protocol version: {mqtt_client._protocol}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         # Subscribing in on_connect() means that if we lose the connection and&lt;br /&gt;
         # reconnect then subscriptions will be renewed.&lt;br /&gt;
         mqtt_client.message_callback_add(&amp;quot;tele/tasmota_MT681/SENSOR&amp;quot;, on_message)&lt;br /&gt;
         mqtt_client.subscribe([(&amp;quot;tele/tasmota_MT681/SENSOR&amp;quot;, 0)])&lt;br /&gt;
 &lt;br /&gt;
     else:&lt;br /&gt;
         print(f&amp;quot;Connection to MQTT broker failed. Error: {reason_code}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_disconnect(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     reason_code: ReasonCode,&lt;br /&gt;
     properties: Any,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when the client is disconnected from the MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     if reason_code &amp;gt; 0:&lt;br /&gt;
         print(f&amp;quot;Unexpected disconnection. Error: {reason_code}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_message(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     message: mqtt.MQTTMessage,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when a Tasmota message is received from the MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     s = message.payload.decode()&lt;br /&gt;
     data = json.loads(s)&lt;br /&gt;
     # {&#039;Time&#039;: &#039;2024-03-16T06:44:08&#039;, &#039;MT681&#039;: {&#039;Total_in&#039;: 16.4396, &#039;Power_cur&#039;: 80, &#039;Total_out&#039;: 14.6403}}  # noqa: E501&lt;br /&gt;
 &lt;br /&gt;
     total_i = data[&amp;quot;MT681&amp;quot;][&amp;quot;Total_in&amp;quot;]&lt;br /&gt;
     total_o = data[&amp;quot;MT681&amp;quot;][&amp;quot;Total_out&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;In:\t{total_i}\nOut:\t{total_o}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     mqtt_client.disconnect()&lt;br /&gt;
     mqtt_client.loop_stop()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 try:&lt;br /&gt;
     # Create an MQTT client instance&lt;br /&gt;
     mqtt_client: mqtt.Client = mqtt.Client(&lt;br /&gt;
         mqtt.CallbackAPIVersion.VERSION2, &amp;quot;Python-Client-1&amp;quot;&lt;br /&gt;
     )  # type: ignore&lt;br /&gt;
     mqtt_client.username_pw_set(username, password)&lt;br /&gt;
 &lt;br /&gt;
     mqtt_client.on_connect = on_connect&lt;br /&gt;
 &lt;br /&gt;
     # Connect to the MQTT broker&lt;br /&gt;
     mqtt_client.connect(hostname, port, keepalive=60)&lt;br /&gt;
 &lt;br /&gt;
     # NOT: while True, as that eats the CPU !!!&lt;br /&gt;
     mqtt_client.loop_forever()&lt;br /&gt;
 except KeyboardInterrupt:&lt;br /&gt;
     print(&amp;quot;KeyboardInterrupt, disconnecting from MQTT broker.&amp;quot;)&lt;br /&gt;
     mqtt_client.disconnect()&lt;br /&gt;
     mqtt_client.loop_stop()&lt;br /&gt;
&lt;br /&gt;
==Caching Functions==&lt;br /&gt;
 from functools import lru_cache&lt;br /&gt;
 &lt;br /&gt;
 @lru_cache(maxsize=128)  # None equals to @cache declarator&lt;br /&gt;
 def fnc(n:int):&lt;br /&gt;
&lt;br /&gt;
==Performance/Resources==&lt;br /&gt;
===RAM/Memory consumption/bottleneck===&lt;br /&gt;
 import tracemalloc&lt;br /&gt;
 tracemalloc.start()&lt;br /&gt;
 &lt;br /&gt;
 # ...&lt;br /&gt;
 # Code&lt;br /&gt;
 # example:&lt;br /&gt;
 lst = list(range(1_000_000))&lt;br /&gt;
 # ...&lt;br /&gt;
 &lt;br /&gt;
 max_bytes = tracemalloc.get_traced_memory()[0]&lt;br /&gt;
 print(f&amp;quot;{round(max_bytes / 10**6)}MB&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 tracemalloc.stop()&lt;br /&gt;
&lt;br /&gt;
===Profiling / Count and Runtime per Function===&lt;br /&gt;
 call_stats = {}&lt;br /&gt;
 &lt;br /&gt;
 def track_function_usage(func: Callable) -&amp;gt; Callable:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Annotation for gathering runtime statistics.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     @wraps(func)&lt;br /&gt;
     def wrapper(*args: tuple, **kwargs: dict):  # noqa: ANN202&lt;br /&gt;
         start_time = time.time()&lt;br /&gt;
         result = func(*args, **kwargs)  # Call the original function&lt;br /&gt;
         end_time = time.time()&lt;br /&gt;
         call_stats[func.__name__][&amp;quot;calls&amp;quot;] += 1&lt;br /&gt;
         call_stats[func.__name__][&amp;quot;total_time&amp;quot;] += end_time - start_time&lt;br /&gt;
         return result&lt;br /&gt;
     return wrapper&lt;br /&gt;
&lt;br /&gt;
==JWT Token==&lt;br /&gt;
  try:&lt;br /&gt;
      decoded_token = jwt.decode(token, options={&amp;quot;verify_signature&amp;quot;: False})&lt;br /&gt;
  except jwt.ExpiredSignatureError:&lt;br /&gt;
      msg = &amp;quot;Token has expired.&amp;quot;&lt;br /&gt;
      logger.exception(msg)&lt;br /&gt;
  except jwt.InvalidTokenError:&lt;br /&gt;
      msg = &amp;quot;Invalid token.&amp;quot;&lt;br /&gt;
      logger.exception(msg)&lt;br /&gt;
&lt;br /&gt;
==Streamlit==&lt;br /&gt;
see https://github.com/entorb/streamlit-examples for a collection of my examples&lt;br /&gt;
&lt;br /&gt;
===Minimum Example===&lt;br /&gt;
 # src/app.py&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import streamlit as st&lt;br /&gt;
 from streamlit.logger import get_logger&lt;br /&gt;
 logger = get_logger(Path(__file__).stem)&lt;br /&gt;
 logger.info(&amp;quot;Start&amp;quot;)&lt;br /&gt;
 st.set_page_config(page_title=&amp;quot;AppTitle&amp;quot;, page_icon=None, layout=&amp;quot;wide&amp;quot;)&lt;br /&gt;
 st.title(&amp;quot;MyPageTitle&amp;quot;)&lt;br /&gt;
 st.header(&amp;quot;MyHeader&amp;quot;)&lt;br /&gt;
 st.subheader(&amp;quot;MySubHeader&amp;quot;)&lt;br /&gt;
 sel_param = st.selectbox(&amp;quot;Parameter&amp;quot;, (&amp;quot;count&amp;quot;, &amp;quot;sum&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
 # .streamlit/config.toml&lt;br /&gt;
 [browser]&lt;br /&gt;
 gatherUsageStats = false&lt;br /&gt;
 &lt;br /&gt;
 [server]&lt;br /&gt;
 port = 8501&lt;br /&gt;
&lt;br /&gt;
 # run it&lt;br /&gt;
 streamlit run src/app.py&lt;br /&gt;
&lt;br /&gt;
====Reduce Logging for K8s Deployed Docker Containers====&lt;br /&gt;
 # reduce log output&lt;br /&gt;
 for logger_name in [&lt;br /&gt;
     &amp;quot;streamlit.runtime.caching.cache_utils&amp;quot;,&lt;br /&gt;
     &amp;quot;streamlit.runtime.caching.storage.in_memory_cache_storage_wrapper&amp;quot;,&lt;br /&gt;
     &amp;quot;streamlit.runtime.runtime&amp;quot;,&lt;br /&gt;
     &amp;quot;urllib3.connectionpool&amp;quot;,&lt;br /&gt;
 ]:&lt;br /&gt;
     get_logger(logger_name).setLevel(logging.WARNING)&lt;br /&gt;
&lt;br /&gt;
===Chart via Altair===&lt;br /&gt;
====Line Chart====&lt;br /&gt;
 chart = st.line_chart(df[&amp;quot;value&amp;quot;])&lt;br /&gt;
 # corresponds to&lt;br /&gt;
 import altair as alt&lt;br /&gt;
 c = (&lt;br /&gt;
    alt.Chart(df)&lt;br /&gt;
    .mark_line()&lt;br /&gt;
    .encode(&lt;br /&gt;
        x=alt.X(&amp;quot;created&amp;quot;, title=None),&lt;br /&gt;
        y=alt.Y(&amp;quot;value&amp;quot;, title=None),&lt;br /&gt;
    )&lt;br /&gt;
 )&lt;br /&gt;
 st.altair_chart(c, use_container_width=True)&lt;br /&gt;
&lt;br /&gt;
====Bar Chart====&lt;br /&gt;
 chart = (&lt;br /&gt;
     alt.Chart(df)&lt;br /&gt;
     .mark_bar()&lt;br /&gt;
     .encode(&lt;br /&gt;
         x=alt.X(&amp;quot;yearmonth(date):T&amp;quot;, title=&amp;quot;Month&amp;quot;),&lt;br /&gt;
         y=alt.Y(f&amp;quot;count:Q&amp;quot;, title=None),&lt;br /&gt;
         color=&amp;quot;status:N&amp;quot;,&lt;br /&gt;
         tooltip=[&amp;quot;yearmonth(date):T&amp;quot;, &amp;quot;status:N&amp;quot;, &amp;quot;cnt:Q&amp;quot;],&lt;br /&gt;
     )&lt;br /&gt;
     # .properties(width=600, height=400)&lt;br /&gt;
 )&lt;br /&gt;
 st.altair_chart(chart, use_container_width=True)&lt;br /&gt;
&lt;br /&gt;
 :T stands for Temporal. It indicates that the field contains date or time values.&lt;br /&gt;
 :N stands for Nominal. It indicates that the field contains categorical data, which represents discrete categories or labels.&lt;br /&gt;
 :Q stands for Quantitative. It indicates that the field contains numerical data that can be measured and compared.&lt;br /&gt;
&lt;br /&gt;
===Excel Download===&lt;br /&gt;
 def excel_download_buttons(df: pd.DataFrame, file_name: str = &amp;quot;export.xlsx&amp;quot;) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Show prepare data and download buttons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if st.button(label=&amp;quot;Click to prepare download&amp;quot;):&lt;br /&gt;
         buffer = io.BytesIO()&lt;br /&gt;
         with pd.ExcelWriter(buffer, engine=&amp;quot;xlsxwriter&amp;quot;) as writer:&lt;br /&gt;
             df.to_excel(writer, sheet_name=&amp;quot;Sheet1&amp;quot;, index=False)&lt;br /&gt;
             writer.close()&lt;br /&gt;
 &lt;br /&gt;
             st.download_button(&lt;br /&gt;
                 label=&amp;quot;Download data as Excel&amp;quot;,&lt;br /&gt;
                 data=buffer,&lt;br /&gt;
                 file_name=file_name,&lt;br /&gt;
                 mime=&amp;quot;application/vnd.ms-excel&amp;quot;,&lt;br /&gt;
             )&lt;br /&gt;
&lt;br /&gt;
==UV Package Manager==&lt;br /&gt;
 # create/update lockfile&lt;br /&gt;
 uv lock --upgrade&lt;br /&gt;
 # sync venv&lt;br /&gt;
 uv sync --upgrade&lt;br /&gt;
 # extract package info to requirements.txt&lt;br /&gt;
 uv export --format requirements.txt --no-dev --no-hashes -o requirements.txt&lt;br /&gt;
&lt;br /&gt;
Update Python version for my current project&lt;br /&gt;
 uv venv --python=3.13.7&lt;br /&gt;
&lt;br /&gt;
===Update UV===&lt;br /&gt;
 uv self update&lt;br /&gt;
 # or&lt;br /&gt;
 brew update &amp;amp;&amp;amp; brew upgrade uv&lt;br /&gt;
&lt;br /&gt;
===Update Python Versions===&lt;br /&gt;
 uv python upgrade&lt;br /&gt;
&lt;br /&gt;
===UV + Docker on readOnlyRootFilesystem===&lt;br /&gt;
Example 1: https://github.com/SWE-Group-23/CM22007---Source/blob/10d02004f3a4208f7b30d37a0a87e89532a23575/create-service.sh#L117&lt;br /&gt;
&lt;br /&gt;
Dockerfile&lt;br /&gt;
 WORKDIR /app&lt;br /&gt;
 COPY . .&lt;br /&gt;
 &lt;br /&gt;
 ENV CASS_DRIVER_NO_EXTENSIONS=1&lt;br /&gt;
 &lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache/uv \\&lt;br /&gt;
     --mount=type=bind,source=uv.lock,target=uv.lock \\&lt;br /&gt;
     --mount=type=bind,source=pyproject.toml,target=pyproject.toml \\&lt;br /&gt;
     uv sync --frozen --no-install-project&lt;br /&gt;
     &lt;br /&gt;
 RUN uv pip install -e shared/&lt;br /&gt;
 &lt;br /&gt;
 RUN addgroup -S group1 &amp;amp;&amp;amp; adduser -S user1 -G group1 -u 1000&lt;br /&gt;
 RUN chown -R user1:group1 /app&lt;br /&gt;
 USER user1:group1&lt;br /&gt;
 &lt;br /&gt;
 CMD [&amp;quot;uv&amp;quot;, &amp;quot;run&amp;quot;, &amp;quot;main.py&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
Deployment&lt;br /&gt;
 securityContext:&lt;br /&gt;
   readOnlyRootFilesystem: true&lt;br /&gt;
   allowPrivilegeEscalation: false&lt;br /&gt;
   runAsNonRoot: true&lt;br /&gt;
   runAsUser: 1000&lt;br /&gt;
   capabilities:&lt;br /&gt;
     drop:&lt;br /&gt;
       - ALL&lt;br /&gt;
   seccompProfile:&lt;br /&gt;
     type: RuntimeDefault&lt;br /&gt;
 volumeMounts:&lt;br /&gt;
   - mountPath: /home/user1/.cache/uv&lt;br /&gt;
     name: uv&lt;br /&gt;
   - mountPath: /tmp&lt;br /&gt;
     name: temp&lt;br /&gt;
&lt;br /&gt;
Example 2: [https://hynek.me/articles/docker-uv/ Production-ready Python Docker Containers with uv]&lt;br /&gt;
&lt;br /&gt;
Dockerfile (without the comments)&lt;br /&gt;
 ENV UV_LINK_MODE=copy \&lt;br /&gt;
     UV_COMPILE_BYTECODE=1 \&lt;br /&gt;
     UV_PYTHON_DOWNLOADS=never \&lt;br /&gt;
     UV_PYTHON=python3.12 \&lt;br /&gt;
     UV_PROJECT_ENVIRONMENT=/app&lt;br /&gt;
 &lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache \&lt;br /&gt;
     --mount=type=bind,source=uv.lock,target=uv.lock \&lt;br /&gt;
     --mount=type=bind,source=pyproject.toml,target=pyproject.toml \&lt;br /&gt;
     uv sync \&lt;br /&gt;
         --locked \&lt;br /&gt;
         --no-dev \&lt;br /&gt;
         --no-install-project&lt;br /&gt;
 &lt;br /&gt;
 COPY . /src&lt;br /&gt;
 WORKDIR /src&lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache \&lt;br /&gt;
     uv sync \&lt;br /&gt;
         --locked \&lt;br /&gt;
         --no-dev \&lt;br /&gt;
         --no-editable&lt;br /&gt;
&lt;br /&gt;
== Vulture: Search for dead code ==&lt;br /&gt;
see [https://github.com/jendrikseipp/vulture GitHub]&lt;br /&gt;
&lt;br /&gt;
pyproject.toml&lt;br /&gt;
 [tool.vulture]&lt;br /&gt;
 paths = [&amp;quot;src&amp;quot;]&lt;br /&gt;
 # exclude = [&amp;quot;src/models.py&amp;quot;]&lt;br /&gt;
 ignore_names = [&lt;br /&gt;
     &amp;quot;LOGGER&amp;quot;,&lt;br /&gt;
 ]&lt;br /&gt;
&lt;br /&gt;
.pre-commit-config.yaml&lt;br /&gt;
   # Vulture: find dead code&lt;br /&gt;
   - repo: https://github.com/jendrikseipp/vulture&lt;br /&gt;
     rev: v2.16&lt;br /&gt;
     hooks:&lt;br /&gt;
       - id: vulture&lt;br /&gt;
         pass_filenames: false&lt;br /&gt;
&lt;br /&gt;
run&lt;br /&gt;
 pip install vulture&lt;br /&gt;
 vulture src/&lt;br /&gt;
 &lt;br /&gt;
 uv add --dev vulture&lt;br /&gt;
 uv run vulture src/&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Python&amp;diff=5407</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5407"/>
		<updated>2026-04-12T12:50:51Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Vulture: Search for dead code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Coding]][[Category:Python]]&lt;br /&gt;
&lt;br /&gt;
==Getting Started==&lt;br /&gt;
===Install===&lt;br /&gt;
====Python====&lt;br /&gt;
Best use [https://docs.astral.sh/uv/ UV] for managing python and package versions.&lt;br /&gt;
&lt;br /&gt;
* for Windows: get and install Python from https://www.python.org&lt;br /&gt;
* for MacOS: use [https://github.com/pyenv/pyenv?tab=readme-ov-file#a-getting-pyenv pyenv]&lt;br /&gt;
 brew install xz &lt;br /&gt;
 brew install pyenv&lt;br /&gt;
 &lt;br /&gt;
 echo &#039;export PYENV_ROOT=&amp;quot;$HOME/.pyenv&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 echo &#039;[ [ -d $PYENV_ROOT/bin ] ] &amp;amp;&amp;amp; export PATH=&amp;quot;$PYENV_ROOT/bin:$PATH&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 echo &#039;eval &amp;quot;$(pyenv init - zsh)&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 exec &amp;quot;$SHELL&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 pyenv install --list&lt;br /&gt;
 pyenv install 3.13.5&lt;br /&gt;
 pyenv global 3.13.5&lt;br /&gt;
 &lt;br /&gt;
 vim ~/.zshrc &lt;br /&gt;
 # add &lt;br /&gt;
 if command -v pyenv 1&amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then&lt;br /&gt;
   eval &amp;quot;$(pyenv init -)&amp;quot;&lt;br /&gt;
 fi&lt;br /&gt;
&lt;br /&gt;
====Editor: Visual Studio Code====&lt;br /&gt;
excellent and free source-code editor that supports many languages.&lt;br /&gt;
* get and install from https://code.visualstudio.com&lt;br /&gt;
&lt;br /&gt;
See Wickie page [[Visual_Studio_Code]] for general setup, extension, config...&lt;br /&gt;
&lt;br /&gt;
Python Extensions&lt;br /&gt;
* Python&lt;br /&gt;
* Pylance&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff Ruff Formater and Linter]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items/?itemName=tamasfe.even-better-toml Even Better TOML]&lt;br /&gt;
Settings (CTRL + ,)&lt;br /&gt;
* Extensions -&amp;gt; Python -&amp;gt; Formatting: Provider = black&lt;br /&gt;
* Text Editor -&amp;gt; Editor: Format On Save&lt;br /&gt;
* Text Editor -&amp;gt; Files:Eol -&amp;gt; \n&lt;br /&gt;
* Linter: Ruff&lt;br /&gt;
Settings in settings.json&lt;br /&gt;
 &amp;quot;python.analysis.completeFunctionParens&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.autoImportCompletions&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.inlayHints.functionReturnTypes&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.typeCheckingMode&amp;quot;: &amp;quot;strict&amp;quot;,&lt;br /&gt;
 // Ruff&lt;br /&gt;
 &amp;quot;[python]&amp;quot;: {&lt;br /&gt;
   &amp;quot;editor.defaultFormatter&amp;quot;: &amp;quot;charliermarsh.ruff&amp;quot;,&lt;br /&gt;
   &amp;quot;editor.formatOnSave&amp;quot;: true,&lt;br /&gt;
   &amp;quot;editor.codeActionsOnSave&amp;quot;: {&lt;br /&gt;
     &amp;quot;source.fixAll&amp;quot;: &amp;quot;explicit&amp;quot;,&lt;br /&gt;
     &amp;quot;source.organizeImports.ruff&amp;quot;: &amp;quot;explicit&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 },&lt;br /&gt;
&lt;br /&gt;
Run your code&lt;br /&gt;
 CTRL + F5 : run&lt;br /&gt;
 F5        : run in debugger&lt;br /&gt;
&lt;br /&gt;
====Code Formatter Ruff====&lt;br /&gt;
use software for handling the code formatting like &amp;quot;ruff&amp;quot; or &amp;quot;black&amp;quot;, where Ruff is much faster and additionally provides linting.&lt;br /&gt;
 pip install ruff&lt;br /&gt;
than activate in editor like vs code&lt;br /&gt;
&lt;br /&gt;
===My Template===&lt;br /&gt;
see latest version at https://github.com/entorb/template-python&lt;br /&gt;
&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Main file.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 # by Torben Menke https://entorb.net &lt;br /&gt;
 import logging&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 def init_logging() -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Initialize logging.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     logging.addLevelName(logging.DEBUG, &amp;quot;D&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.INFO, &amp;quot;I&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.WARNING, &amp;quot;W&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.ERROR, &amp;quot;E&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.CRITICAL, &amp;quot;C&amp;quot;)&lt;br /&gt;
     logging.basicConfig(&lt;br /&gt;
         level=logging.INFO, format=&amp;quot;%(levelname)s: %(name)s: %(message)s&amp;quot;&lt;br /&gt;
     )&lt;br /&gt;
     logging.getLogger(&amp;quot;httpx&amp;quot;).setLevel(logging.WARNING)&lt;br /&gt;
 &lt;br /&gt;
 init_logging()&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
 &lt;br /&gt;
 def main():&lt;br /&gt;
     LOGGER.info(&amp;quot;Moin Moin&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     main()&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Other file&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 import logging&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
&lt;br /&gt;
===Compile to .exe===&lt;br /&gt;
 pip install pyinstaller&lt;br /&gt;
 pyinstaller --onefile --console your.py&lt;br /&gt;
 # for Excel and Matplotlib these options are required&lt;br /&gt;
 --hidden-import=openpyxl --hidden-import=matplotlib --hidden-import pandas.plotting._matplotlib &lt;br /&gt;
([[Python - py2exe]] is deprecated)&lt;br /&gt;
&lt;br /&gt;
==Basics==&lt;br /&gt;
=== Naming Conventions===&lt;br /&gt;
[https://google.github.io/styleguide/pyguide.html Google Python Style Guide]:&lt;br /&gt;
 module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_CONSTANT_NAME, global_var_name, instance_var_name, function_parameter_name, local_var_name&lt;br /&gt;
&lt;br /&gt;
====Doc Strings====&lt;br /&gt;
It is best practice to start a file with a docstring:&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;My Script&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
This can be accessed linke this:&lt;br /&gt;
 title = __doc__[:-1]  # type: ignore&lt;br /&gt;
&lt;br /&gt;
===Installing packages===&lt;br /&gt;
 python -m pip install --upgrade pip&lt;br /&gt;
 &lt;br /&gt;
 pip install somemodule&lt;br /&gt;
 # or &lt;br /&gt;
 pip3 install somemodule&lt;br /&gt;
 # or read from file&lt;br /&gt;
 pip install -r requirements.txt&lt;br /&gt;
 &lt;br /&gt;
 # uninstall&lt;br /&gt;
 pip uninstall somemodule&lt;br /&gt;
 &lt;br /&gt;
 # using a web proxy&lt;br /&gt;
 # set proxy for windows cmd session&lt;br /&gt;
 SET HTTPS_PROXY=http://myProxy:8080&lt;br /&gt;
 (afterwards --proxy setting below no longer required&lt;br /&gt;
 or&lt;br /&gt;
 pip install --proxy http://myProxy:8080 somemodule&lt;br /&gt;
 &lt;br /&gt;
 # list outdated packages&lt;br /&gt;
 pip list --outdated&lt;br /&gt;
 &lt;br /&gt;
 # update package&lt;br /&gt;
 pip install --upgrade pyinstaller&lt;br /&gt;
 &lt;br /&gt;
 # updating all via Windows Powershell (from [https://thecesrom.dev/2021/05/26/the-one-liner-for-updating-pip-and-all-outdated-packages/])&lt;br /&gt;
 pip freeze | %{$_.split(&#039;==&#039;)[0]} | %{pip install --upgrade $_}&lt;br /&gt;
 &lt;br /&gt;
 # updating all via Bash (from [https://thecesrom.dev/2021/05/26/the-one-liner-for-updating-pip-and-all-outdated-packages/])&lt;br /&gt;
 pip freeze | grep -v &#039;^\-e&#039; | cut -d = -f 1  | xargs -n1 pip install --upgrade&lt;br /&gt;
 &lt;br /&gt;
 # downgrade&lt;br /&gt;
 pip install --upgrade pandas==1.2.4&lt;br /&gt;
&lt;br /&gt;
====Oneline Updater for requirements.txt====&lt;br /&gt;
https://pkgui.com/pip&lt;br /&gt;
&lt;br /&gt;
====update packages====&lt;br /&gt;
 pip install pip-review&lt;br /&gt;
 pip-review --auto&lt;br /&gt;
&lt;br /&gt;
====generate requirements.txt====&lt;br /&gt;
 pip install pipreqs&lt;br /&gt;
 pipreqs ./&lt;br /&gt;
&lt;br /&gt;
===Loops===&lt;br /&gt;
ATTENTION: The loops do not create a new variable scope. Only functions and modules introduce a new scope!&lt;br /&gt;
 for p in all_files:&lt;br /&gt;
     print(p)&lt;br /&gt;
 &lt;br /&gt;
 for i, p in enumerate(all_files):&lt;br /&gt;
     print(i, p)&lt;br /&gt;
 &lt;br /&gt;
 for i in range(10):&lt;br /&gt;
     print(i)&lt;br /&gt;
 # del i &lt;br /&gt;
 &lt;br /&gt;
 while i &amp;lt;= 100:&lt;br /&gt;
     i += 1&lt;br /&gt;
     ...&lt;br /&gt;
     if sth1:&lt;br /&gt;
         continue # start next loop&lt;br /&gt;
     if sth2:&lt;br /&gt;
         break # exit loop&lt;br /&gt;
 &lt;br /&gt;
 # inline if (requires a dummy else):&lt;br /&gt;
 print(&amp;quot;something&amp;quot;) if sth else 0&lt;br /&gt;
&lt;br /&gt;
===Math===&lt;br /&gt;
see [[Python - Math]] for linear regression&lt;br /&gt;
&lt;br /&gt;
Modulo&lt;br /&gt;
 15 % 4&lt;br /&gt;
 # &amp;gt; 3&lt;br /&gt;
&lt;br /&gt;
Integer Division&lt;br /&gt;
 17 // 4&lt;br /&gt;
 # &amp;gt; 4&lt;br /&gt;
&lt;br /&gt;
====Random====&lt;br /&gt;
 import random&lt;br /&gt;
 random.randint(1000000, 9999999)&lt;br /&gt;
&lt;br /&gt;
==Basic Objects==&lt;br /&gt;
===Variables===&lt;br /&gt;
 del var  # delete / undef a variable&lt;br /&gt;
 var = None  # sets to null&lt;br /&gt;
 &lt;br /&gt;
 # check if variable is defined&lt;br /&gt;
 if &amp;quot;var&amp;quot; in locals():&lt;br /&gt;
     pass&lt;br /&gt;
 # for object oriented projects:&lt;br /&gt;
 if &amp;quot;var&amp;quot; in self.__dict__:&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # Access global variables in functions&lt;br /&gt;
 var = 123&lt;br /&gt;
 &lt;br /&gt;
 def test() -&amp;gt; None:&lt;br /&gt;
     global var  # point to global instead of creation of local var&lt;br /&gt;
     var = 321&lt;br /&gt;
&lt;br /&gt;
===Strings===&lt;br /&gt;
 # num &amp;lt;-&amp;gt; str&lt;br /&gt;
 s = str(i)  # int to string&lt;br /&gt;
 f = float(s)  # str -&amp;gt; float&lt;br /&gt;
 i = int(s)&lt;br /&gt;
 str(round(f, 1))  # round first&lt;br /&gt;
 # tests&lt;br /&gt;
 s.isdigit()  # 0-9&lt;br /&gt;
 # note isdecimal() does also not match &#039;1.1&#039;&lt;br /&gt;
 &lt;br /&gt;
 # printf: 1 digit&lt;br /&gt;
 s = f&amp;quot;{value:0.1f}&amp;quot;&lt;br /&gt;
 s = &amp;quot;%0.1f&amp;quot; % value&lt;br /&gt;
&lt;br /&gt;
====Modify Strings==== &lt;br /&gt;
 # get string from prompt&lt;br /&gt;
 s = input(&amp;quot;Enter Text: &amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 s = s.strip()  # trim spaces from both sides, rstrip for right only&lt;br /&gt;
 s = s.lower()  # lower case&lt;br /&gt;
 s = s.upper()  # upper case&lt;br /&gt;
 s = s.title()  # upper case for first char of word&lt;br /&gt;
 &lt;br /&gt;
 # upper case first letter of each word and also removes multiple and trailing spaces&lt;br /&gt;
 import string&lt;br /&gt;
 s = string.capwords(s)&lt;br /&gt;
 &lt;br /&gt;
 # replace&lt;br /&gt;
 s.replace(x, y)&lt;br /&gt;
 &lt;br /&gt;
 # trim whitespaces from left and right&lt;br /&gt;
 s.strip()&lt;br /&gt;
 &lt;br /&gt;
 # replace all (multiple) whitespaces by single space &#039; &#039;&lt;br /&gt;
 s = &amp;quot; &amp;quot;.join(s.split())&lt;br /&gt;
 &lt;br /&gt;
 # generate key value pairs from dict&lt;br /&gt;
 # key1=value1&amp;amp;key2=value2&lt;br /&gt;
 param_str = &amp;quot;&amp;amp;&amp;quot;.join(&amp;quot;=&amp;quot;.join(tup) for tup in dict.items())&lt;br /&gt;
 &lt;br /&gt;
 # repeat string multiple times&lt;br /&gt;
 s * 5  # = s+s+s+s+s&lt;br /&gt;
&lt;br /&gt;
====Substrings====&lt;br /&gt;
 # find a substring:&lt;br /&gt;
 if x in s:  # True / False&lt;br /&gt;
     pass&lt;br /&gt;
 if len(s) &amp;gt; 0:&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # handling substrings&lt;br /&gt;
 a = &amp;quot;abcd&amp;quot;&lt;br /&gt;
 b = a[:1] + &amp;quot;o&amp;quot; + a[2:]&lt;br /&gt;
 # &amp;gt; &#039;aocd&#039;&lt;br /&gt;
 &lt;br /&gt;
 s = &amp;quot;Hello there !bob@&amp;quot;&lt;br /&gt;
 i1 = s.find(&amp;quot;!&amp;quot;) + 1&lt;br /&gt;
 i2 = s.find(&amp;quot;@&amp;quot;)&lt;br /&gt;
 substr = s[i1:i2]&lt;br /&gt;
 &lt;br /&gt;
 def substr_between(s: str, s1: str, s2: str) -&amp;gt; str:&lt;br /&gt;
     assert s1 in s, f&amp;quot;E: can&#039;t find &#039;{s1}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     assert s2 in s, f&amp;quot;E: can&#039;t find &#039;{s1}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     i1 = s.find(s1) + len(s1)&lt;br /&gt;
     i2 = s.find(s2)&lt;br /&gt;
     assert i1 &amp;lt; i2, f&amp;quot;E: &#039;{s1}&#039; not before &#039;{s2}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     return s[i1:i2]&lt;br /&gt;
&lt;br /&gt;
====Binary, raw strings, html encoding====&lt;br /&gt;
 # Binary Strings&lt;br /&gt;
 sb = b&amp;quot;asdf&amp;quot;&lt;br /&gt;
 # or&lt;br /&gt;
 sb = str.encode(&amp;quot;asdf&amp;quot;)&lt;br /&gt;
 s = sb.decode(&amp;quot;ascii&amp;quot;)  # decode binary strings&lt;br /&gt;
 sb = s.encode(&amp;quot;ascii&amp;quot;)  # encode string to binary&lt;br /&gt;
 &lt;br /&gt;
 # raw string&lt;br /&gt;
 s = r&amp;quot;c:\Windows&amp;quot;  # no escape of \  needed&lt;br /&gt;
 &lt;br /&gt;
 # convert utf-8 to html umlaute&lt;br /&gt;
 s = &amp;quot;Nürnberg&amp;quot;.encode(&amp;quot;ascii&amp;quot;, &amp;quot;xmlcharrefreplace&amp;quot;).decode()&lt;br /&gt;
 # -&amp;gt; N&amp;amp;#252;rnberg&lt;br /&gt;
&lt;br /&gt;
====Guess encoding via chardet====&lt;br /&gt;
 import chardet  # pip install chardet&lt;br /&gt;
 result = chardet.detect(raw_data)&lt;br /&gt;
 if result[&amp;quot;encoding&amp;quot;] and result[&amp;quot;confidence&amp;quot;] &amp;gt; 0.5:  # noqa: PLR2004&lt;br /&gt;
     encoding = result[&amp;quot;encoding&amp;quot;]&lt;br /&gt;
 else:&lt;br /&gt;
     encoding = &amp;quot;utf-8&amp;quot;&lt;br /&gt;
&lt;br /&gt;
there is a much faster alternative [https://github.com/PyYoshi/cChardet cchardet], but that requires Microsoft Visual C++ 14.0&lt;br /&gt;
&lt;br /&gt;
====Merge variables in string / sprintf====&lt;br /&gt;
 print(&amp;quot;Renner =&amp;quot;, i)&lt;br /&gt;
 print(&amp;quot;Renner = %3d&amp;quot; % i)  # leading 0&#039;s&lt;br /&gt;
 print(f&amp;quot;Renner = {i}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # place formatted numbers in a string / sprintf&lt;br /&gt;
 s = &amp;quot;The {:.1f}% {} cost {:05.2f} euros&amp;quot;.format( 5.1, &amp;quot;beer&amp;quot;, 3.50)&lt;br /&gt;
 print(s)&lt;br /&gt;
 # &amp;gt; The 5.1% beer cost 03.50 euros&lt;br /&gt;
 &lt;br /&gt;
 s = f&amp;quot;The length is {72.8958:.2f} meters&amp;quot;&lt;br /&gt;
 # &amp;gt; The length is 72.90 meters&lt;br /&gt;
&lt;br /&gt;
===Lists===&lt;br /&gt;
like @array in Perl&lt;br /&gt;
 lst = [1, 2, 3, 4, 5, 6]&lt;br /&gt;
 lst = [x / 2 for x in range(10)]&lt;br /&gt;
 lst = [None] * 10 # Initiate list of None elements:&lt;br /&gt;
 len(lst) # length&lt;br /&gt;
 lst2 = lst[0:10]  # get elements 0-10&lt;br /&gt;
 for i in lst:&lt;br /&gt;
     print(i)&lt;br /&gt;
&lt;br /&gt;
====clone list====&lt;br /&gt;
 # dangerous: creates a link to the original list&lt;br /&gt;
 list_2 = my_list  # M&#039;s elements are LINKS to L&#039;s&lt;br /&gt;
 # clones can be achieved via:&lt;br /&gt;
 list_2 = my_list.copy&lt;br /&gt;
 list_2 = my_list[:]&lt;br /&gt;
 list_2 = list(my_list)&lt;br /&gt;
&lt;br /&gt;
====list modifications====&lt;br /&gt;
 lst.pop()  # returns and removes the last item&lt;br /&gt;
 lst.pop(i)  # returns and removes the item at  position int i&lt;br /&gt;
 lst.insert(i, x)  # insert item x at position int i&lt;br /&gt;
 lst.remove(x)  # removes the first occurrence of  item x&lt;br /&gt;
 lst.append(x)  # append a single element&lt;br /&gt;
 lst.extend(m)  # append elements of another list&lt;br /&gt;
 &lt;br /&gt;
 lst.reverse()  # reverse the order of the list&lt;br /&gt;
 lst = sorted([&amp;quot;B&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;c&amp;quot;], key=str.casefold)  #  case insensitive / ignore case&lt;br /&gt;
 &lt;br /&gt;
 # list to string&lt;br /&gt;
 s = &amp;quot;&amp;quot;.join(lst)&lt;br /&gt;
 s = &amp;quot;\n&amp;quot;.join(lst)&lt;br /&gt;
 # string to list&lt;br /&gt;
 lst = s.split()  # default: split on space&lt;br /&gt;
 lst = s.split(&amp;quot;,&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # remove empty values from end&lt;br /&gt;
 while L[-1] == &amp;quot;&amp;quot;:&lt;br /&gt;
     L.pop()&lt;br /&gt;
 &lt;br /&gt;
 # check and remove items from list&lt;br /&gt;
  # from https://stackoverflow.com/a/6024599&lt;br /&gt;
  # iterates in-situ via reversed index&lt;br /&gt;
 for i in range(len(lst) - 1, -1, -1):&lt;br /&gt;
     element = lst[i]&lt;br /&gt;
     if check(element):&lt;br /&gt;
         del lst[i]&lt;br /&gt;
&lt;br /&gt;
====check if item is in list====&lt;br /&gt;
 x in lst&lt;br /&gt;
 x not in lst&lt;br /&gt;
 lst.count(x)  # how many items x are in the list&lt;br /&gt;
 i = lst.index(&amp;quot;word&amp;quot;)  # find in list / returns the  position of the first match in list&lt;br /&gt;
&lt;br /&gt;
====pair 2 lists====&lt;br /&gt;
 # zip: merge 2 lists to list of tuples&lt;br /&gt;
 data = list(zip(data_x, data_y, strict=True))&lt;br /&gt;
 &lt;br /&gt;
 # unzip: split list of pairs into 2 lists&lt;br /&gt;
 data_x, data_y = zip(*data, strict=True)&lt;br /&gt;
 &lt;br /&gt;
 # Cartesian product of lists / tuples&lt;br /&gt;
 import itertools&lt;br /&gt;
 for i in itertools.product(*list_of_lists):&lt;br /&gt;
     print(i)&lt;br /&gt;
&lt;br /&gt;
====sort multidim list====&lt;br /&gt;
 lst = sorted(lst, key=lambda x: x[0], reverse=False)&lt;br /&gt;
 lst = sorted(lst, key=lambda row: (row[&amp;quot;Wann&amp;quot;], row [&amp;quot;Wer&amp;quot;]), reverse=False)&lt;br /&gt;
&lt;br /&gt;
====filter list====&lt;br /&gt;
 lines = [x for x in lines if not x.startswith (&amp;quot;word&amp;quot;)]&lt;br /&gt;
 lst = [&amp;quot;asdf&amp;quot;, &amp;quot;asdf2&amp;quot;, &amp;quot;qwertz&amp;quot;]&lt;br /&gt;
 lst = [elem for elem in lst if &amp;quot;asdf&amp;quot; in elem]&lt;br /&gt;
&lt;br /&gt;
====for each element====&lt;br /&gt;
 # trim/strip spaces for each element&lt;br /&gt;
 lines = [s.strip() for s in lines]&lt;br /&gt;
 # modify each item in list by adding constant string&lt;br /&gt;
 l = [s + &#039;;&#039; + v for v in l]&lt;br /&gt;
  &lt;br /&gt;
 # modify item in list&lt;br /&gt;
 for idx, line in enumerate(cont):&lt;br /&gt;
     if &amp;quot;K1001/1&amp;quot; in line:&lt;br /&gt;
         line = &amp;quot;K1001/1 Test Nr &amp;quot; + str(i) + &amp;quot;\n&amp;quot;&lt;br /&gt;
         cont[idx] = line&lt;br /&gt;
         break&lt;br /&gt;
&lt;br /&gt;
===Tuples===&lt;br /&gt;
Ordered sequence, with no ability to replace or delete items&lt;br /&gt;
 tpl = (1,2,3,4,5,6)&lt;br /&gt;
&lt;br /&gt;
list -&amp;gt; tuple&lt;br /&gt;
 tpl = tuple(lst)&lt;br /&gt;
&lt;br /&gt;
combine 2 tuples&lt;br /&gt;
 tpl = tpl1 + tpl2&lt;br /&gt;
&lt;br /&gt;
===Dictionaries===&lt;br /&gt;
like %hash in Perl&lt;br /&gt;
 d = {&amp;quot;key1&amp;quot;: &amp;quot;value1&amp;quot;, &amp;quot;key2&amp;quot;: 123}&lt;br /&gt;
 d[&amp;quot;key3&amp;quot;] = (1, 2, 3)&lt;br /&gt;
 del d[&amp;quot;key3&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
 len(d)&lt;br /&gt;
 d.clear()&lt;br /&gt;
 d.copy()&lt;br /&gt;
 d.keys()&lt;br /&gt;
 d.values()&lt;br /&gt;
 d.items()  # returns a list of tuples (key, value)&lt;br /&gt;
 d.get(k)  # returns value of key k&lt;br /&gt;
 d.get(k, x)  # returns value of key k; if k is not  in d it returns x&lt;br /&gt;
 count = d.get(&amp;quot;key&amp;quot;, 0) + 1 # nice for counters&lt;br /&gt;
 d.pop(k)  # returns and removes item k&lt;br /&gt;
 d.pop(k, x)  # returns and removes item k; if k is  not in d it returns x&lt;br /&gt;
 # checks&lt;br /&gt;
 x in d&lt;br /&gt;
 x not in d&lt;br /&gt;
 &lt;br /&gt;
 # dict can have tuple as key&lt;br /&gt;
 tpl = (&amp;quot;key1&amp;quot;, &amp;quot;key2&amp;quot;, 123)&lt;br /&gt;
 d[tpl] = 123456&lt;br /&gt;
 &lt;br /&gt;
 # loop over all key-value pairs&lt;br /&gt;
 for key, value in d.items():&lt;br /&gt;
     print(f&amp;quot;{key} = {value}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # sorted keys:&lt;br /&gt;
 for key in sorted(dict.keys()):&lt;br /&gt;
     pass&lt;br /&gt;
 # sorted values, reversed&lt;br /&gt;
 for key, value in sorted(d.items(), key=lambda item:  item[1], reverse=True):&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # join / merge 2 dicts&lt;br /&gt;
 d.update(d2)&lt;br /&gt;
 &lt;br /&gt;
 # flatten dict of dict&lt;br /&gt;
 flat = d.copy()&lt;br /&gt;
 meta = d.pop(&amp;quot;metadata&amp;quot;)&lt;br /&gt;
 flat.update(meta)&lt;br /&gt;
&lt;br /&gt;
====MultiDim Dictionaries====&lt;br /&gt;
 d = {}&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;] = {}&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;][&amp;quot;value&amp;quot;] = 1.909e18&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;][&amp;quot;name&amp;quot;] = &amp;quot;Item 1&amp;quot;&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;] = {}&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;][&amp;quot;value&amp;quot;] = 1.725e18&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;][&amp;quot;name&amp;quot;] = &amp;quot;Item 2&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 for k in d.keys():&lt;br /&gt;
     d[k][&amp;quot;value&amp;quot;] = d[k][&amp;quot;value&amp;quot;] * 1.1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def find_common_strings(dict1: dict, dict2: dict) -&amp;gt; dict[Any, Any]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Find common strings in two dict, returning a new dict of those strings.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     def recursive_match(d1: dict, d2: dict) -&amp;gt; dict[Any, Any]:&lt;br /&gt;
         common_dict = {}&lt;br /&gt;
         for key in d1:  # noqa: PLC0206&lt;br /&gt;
             if key in d2:&lt;br /&gt;
                 if isinstance(d1[key], dict) and isinstance(d2[key], dict):&lt;br /&gt;
                     # Recurse if both values are dicts&lt;br /&gt;
                     nested_common = recursive_match(d1[key], d2[key])&lt;br /&gt;
                     if nested_common:  # Add to result if common values are found&lt;br /&gt;
                         common_dict[key] = nested_common&lt;br /&gt;
                 elif isinstance(d1[key], str) and isinstance(d2[key], str):&lt;br /&gt;
                     # Check if both are strings and identical&lt;br /&gt;
                     if d1[key] == d2[key]:&lt;br /&gt;
                         common_dict[key] = d1[key]&lt;br /&gt;
         return common_dict&lt;br /&gt;
 &lt;br /&gt;
     return recursive_match(dict1, dict2)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def update_dict_with_new_values(original_dict: dict, new_values: dict) -&amp;gt; dict:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Update strings in the original dict with new values from the new_values dict.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     def recursive_update(d1: dict, new_vals: dict) -&amp;gt; None:&lt;br /&gt;
         for key in new_vals:  # noqa: PLC0206&lt;br /&gt;
             if key in d1:&lt;br /&gt;
                 if isinstance(d1[key], dict) and isinstance(new_vals[key], dict):&lt;br /&gt;
                     # Recurse if both values are dicts&lt;br /&gt;
                     recursive_update(d1[key], new_vals[key])&lt;br /&gt;
                 elif isinstance(d1[key], str) and isinstance(new_vals[key], str):&lt;br /&gt;
                     # Update the string&lt;br /&gt;
                     d1[key] = new_vals[key]&lt;br /&gt;
 &lt;br /&gt;
     recursive_update(original_dict, new_values)&lt;br /&gt;
     return original_dict&lt;br /&gt;
&lt;br /&gt;
===Datetime, Date and Time===&lt;br /&gt;
 import datetime as dt&lt;br /&gt;
 from zoneinfo import ZoneInfo&lt;br /&gt;
 TZ_DE = ZoneInfo(&amp;quot;Europe/Berlin&amp;quot;)&lt;br /&gt;
 TZ_UTC = ZoneInfo(&amp;quot;UTC&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
====Create====&lt;br /&gt;
 # date&lt;br /&gt;
 date = dt.date(2023, 12, 31)&lt;br /&gt;
 date = dt.date.fromisocalendar(int(year), int(week),  int(daynum))  # daynum: 1..7&lt;br /&gt;
 date = dt.date.fromisoformat(&amp;quot;2020-03-10&amp;quot;)&lt;br /&gt;
 date = dt.datetime.strptime(datestr, &amp;quot;%y%m%d&amp;quot;).date()&lt;br /&gt;
 date_today = dt.datetime.now(tz=dt.UTC).date()&lt;br /&gt;
 date_yesterday = dt.datetime.now(tz=TZ_DE).date() -  dt.timedelta(days=1)&lt;br /&gt;
 days_overdue = (date_completed - date_due).days&lt;br /&gt;
 # to add one month (which is not supported by timedelta) &lt;br /&gt;
 from dateutil.relativedelta import relativedelta&lt;br /&gt;
 date_end = date_start + relativedelta(months=1)&lt;br /&gt;
 delta_days = 1 + (end - start).days&lt;br /&gt;
 &lt;br /&gt;
 # datetime&lt;br /&gt;
 dt_now = dt.datetime.now(tz=TZ_DE)&lt;br /&gt;
 my_dt = dt.datetime(2023, 12, 31, 14, 31, 56,  tzinfo=TZ_DE)  # 2023-12-31 14:31:56&lt;br /&gt;
 my_dt = dt.datetime.fromtimestamp(my_timestamp,  tz=TZ_DE)&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat (&amp;quot;2017-01-01T12:30:59.000000&amp;quot;)&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(&amp;quot;2020-03-10  06:01:01+00:00&amp;quot;)&lt;br /&gt;
 s = &amp;quot;2020-03-10T06:01:01Z&amp;quot;&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(s.replace(&amp;quot;Z&amp;quot;, &amp;quot; +00:00&amp;quot;))&lt;br /&gt;
 my_date_local = my_dt.astimezone(tz=TZ_DE).date()&lt;br /&gt;
 &lt;br /&gt;
 # datetime -&amp;gt; date&lt;br /&gt;
 my_date = my_dt.date()&lt;br /&gt;
 # date -&amp;gt; datetime&lt;br /&gt;
 my_date = dt.date(2023, 12, 31)&lt;br /&gt;
 my_dt = dt.datetime(my_date.year, my_date.month,  my_date.day, tzinfo=TZ_DE)&lt;br /&gt;
 &lt;br /&gt;
 # to string&lt;br /&gt;
 date_str = my_dt.strftime(&amp;quot;%y%m%d&amp;quot;)&lt;br /&gt;
 date_str = my_dt.strftime(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;)&lt;br /&gt;
 # alternative using time&lt;br /&gt;
 date_str = time.strftime(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;)&lt;br /&gt;
 # now in UTC without milliseconds&lt;br /&gt;
 date_str = dt.datetime.now(tz=dt.timezone.utc).replace(microsecond=0).isoformat() + &amp;quot;Z&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # remove timezone&lt;br /&gt;
 my_dt = my_dt.replace(tzinfo=None)&lt;br /&gt;
 &lt;br /&gt;
 # German format&lt;br /&gt;
 import locale&lt;br /&gt;
 locale.setlocale(locale.LC_ALL, &amp;quot;de_DE&amp;quot;)&lt;br /&gt;
 print(my_date.strftime(&amp;quot;%a %x&amp;quot;))  # Mo 25.12.2023&lt;br /&gt;
 &lt;br /&gt;
 # Calendar week&lt;br /&gt;
 week = my_date.isocalendar()[1]&lt;br /&gt;
 print(&amp;quot;KW%02d&amp;quot; % week)&lt;br /&gt;
 &lt;br /&gt;
 # first day of quarter&lt;br /&gt;
 def get_first_day_of_the_quarter(my_date: dt.date) -&amp;gt; dt.date:&lt;br /&gt;
     return dt.date(my_date.year, 1 + 3 * ((my_date.month - 1) // 3), 1)&lt;br /&gt;
&lt;br /&gt;
====rounding datetimes to minutes====&lt;br /&gt;
 def floor_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Floor (=round down) minutes to X min  resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     min_new = res * (my_dt.minute // res)&lt;br /&gt;
     return my_dt.replace(minute=min_new, second=0,  microsecond=0)&lt;br /&gt;
 &lt;br /&gt;
 def ceil_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Ceil (=round up) minutes to X min resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    min_new = res * (1 + my_dt.minute // res)&lt;br /&gt;
    return my_dt.replace(minute=0, second=0, microsecond=0) + dt.timedelta(&lt;br /&gt;
        minutes=min_new&lt;br /&gt;
    )&lt;br /&gt;
 &lt;br /&gt;
 def round_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Cound minutes to X min resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    min_old_dec = float(my_dt.minute) + float(my_dt.second * 60)&lt;br /&gt;
    min_new = res * round(min_old_dec / res)&lt;br /&gt;
    return my_dt.replace(minute=0, second=0, microsecond=0) + dt.timedelta(&lt;br /&gt;
        minutes=min_new&lt;br /&gt;
    )&lt;br /&gt;
 &lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(&amp;quot;2011-11-04  00:05:23+00:00&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;original: {my_dt}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;floored: {floor_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;ceileded: {ceil_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;rounded: {round_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
====Timing / Time elapsed====&lt;br /&gt;
 import time &lt;br /&gt;
 timestart = time.time()&lt;br /&gt;
 sec = time.time() - timestart&lt;br /&gt;
 print(f&amp;quot;Time elapsed: {sec:.2f} sec&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==OS, Argpars, etc.==&lt;br /&gt;
===Hostname===&lt;br /&gt;
 from socket import gethostname&lt;br /&gt;
 print(gethostname())&lt;br /&gt;
&lt;br /&gt;
===Checking Operating System===&lt;br /&gt;
 import os&lt;br /&gt;
 import sys&lt;br /&gt;
  &lt;br /&gt;
 if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
     print(&amp;quot;posix/Unix/Linux&amp;quot;)&lt;br /&gt;
 elif os.name == &amp;quot;nt&amp;quot;:&lt;br /&gt;
     print(&amp;quot;windows&amp;quot;)&lt;br /&gt;
 else:&lt;br /&gt;
     print(&amp;quot;unknown os&amp;quot;)&lt;br /&gt;
     sys.exit(1)  # throws exception, use quit() to   close / die silently&lt;br /&gt;
&lt;br /&gt;
===Get filename of python script===&lt;br /&gt;
 my_file_path = __file__&lt;br /&gt;
&lt;br /&gt;
alternative using sys package&lt;br /&gt;
 from sys import argv&lt;br /&gt;
 myFilename = argv[0]&lt;br /&gt;
&lt;br /&gt;
===accessing os envrionment variables===&lt;br /&gt;
 import os&lt;br /&gt;
 print(os.getenv(&amp;quot;tmp&amp;quot;))&lt;br /&gt;
 # better:&lt;br /&gt;
 try:&lt;br /&gt;
     s = os.environ[key] # throws error if unset&lt;br /&gt;
 except KeyError:&lt;br /&gt;
     error_message(f&amp;quot;Environment variable {key} not set.&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Command Line Arguments===&lt;br /&gt;
====ArgumentParser====&lt;br /&gt;
 import argparse&lt;br /&gt;
 &lt;br /&gt;
 parser = (&lt;br /&gt;
     argparse.ArgumentParser()&lt;br /&gt;
 )  # construct the argument parser and parse the  arguments&lt;br /&gt;
 # -h comes automatically&lt;br /&gt;
 &lt;br /&gt;
 # Boolean Parameter&lt;br /&gt;
 parser.add_argument(&lt;br /&gt;
     &amp;quot;-v&amp;quot;, &amp;quot;--verbose&amp;quot;, help=&amp;quot;increase output  verbosity&amp;quot;, action=&amp;quot;store_true&amp;quot;&lt;br /&gt;
 )  # store_true -&amp;gt; Boolean Value&lt;br /&gt;
 &lt;br /&gt;
 # Choice Parameter&lt;br /&gt;
 # restrict to a list of possible values / choices&lt;br /&gt;
 # parser.add_argument(&amp;quot;--choice&amp;quot;, type=int, choices= [0, 1, 2], help=&amp;quot;Test choices&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Positional Parameter (like text.py 123)&lt;br /&gt;
 # parser.add_argument(&amp;quot;num&amp;quot;, type=int, help=&amp;quot;Number  of things&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Required Parameter&lt;br /&gt;
 # parser.add_argument(&amp;quot;-i&amp;quot;, &amp;quot;--input&amp;quot;, type=str,  required=True, help=&amp;quot;Path of file&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Optional Parameter&lt;br /&gt;
 parser.add_argument(&amp;quot;-n&amp;quot;, &amp;quot;--number&amp;quot;, type=int,  help=&amp;quot;Number of clicks&amp;quot;)&lt;br /&gt;
 # Optional Parameter with Default&lt;br /&gt;
 parser.add_argument(&lt;br /&gt;
     &amp;quot;-s&amp;quot;,&lt;br /&gt;
     &amp;quot;--seconds&amp;quot;,&lt;br /&gt;
     type=int,&lt;br /&gt;
     default=sec_default,&lt;br /&gt;
     help=&amp;quot;Duration of clicking, default = %i (sec)&amp;quot;  % sec_default,&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 args = vars(parser.parse_args())&lt;br /&gt;
 &lt;br /&gt;
 if args[&amp;quot;verbose&amp;quot;]:&lt;br /&gt;
     pass  # do nothing&lt;br /&gt;
 # print (&amp;quot;verbosity turned on&amp;quot;)&lt;br /&gt;
 if args[&amp;quot;number&amp;quot;]:&lt;br /&gt;
     print(&amp;quot;num=%i&amp;quot; % args[&amp;quot;number&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
==== match case statement ====&lt;br /&gt;
(new in python 3.10)&lt;br /&gt;
 match args:&lt;br /&gt;
   case {&amp;quot;sap&amp;quot;: &amp;quot;prod&amp;quot;, &amp;quot;version&amp;quot;: 1}:&lt;br /&gt;
     ...&lt;br /&gt;
   case {&amp;quot;sap&amp;quot;: &amp;quot;prod&amp;quot;, &amp;quot;version&amp;quot;: 2}:&lt;br /&gt;
     ...&lt;br /&gt;
   case _: # default case&lt;br /&gt;
&lt;br /&gt;
==File Access==&lt;br /&gt;
===Pathlib===&lt;br /&gt;
for migrating see table [https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module]&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 p = Path(&amp;quot;dir/test.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 log_file = Path(__file__).with_suffix(&amp;quot;.log&amp;quot;)  # __file__ is name of python script&lt;br /&gt;
 &lt;br /&gt;
 my_fname = p.name  #  alternative to basename&lt;br /&gt;
 my_ext = p.suffix&lt;br /&gt;
 (filepath_without_ext, file_ext) = (p.stem, p.suffix)&lt;br /&gt;
 my_dir = p.parent&lt;br /&gt;
 my_parent_dir = p.parents[1]&lt;br /&gt;
 p2 = p.with_suffix(&amp;quot;.json&amp;quot;)&lt;br /&gt;
 p3 = p.parent / (p.name + &amp;quot;-autofix.tex&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # checks&lt;br /&gt;
 Path(my_file_str).exists()&lt;br /&gt;
 Path(my_file_str).is_file()&lt;br /&gt;
 Path(my_file_str).is_dir()&lt;br /&gt;
 &lt;br /&gt;
 # loop over glob of files matching wildcard&lt;br /&gt;
 for file_out in Path(&amp;quot;mydir&amp;quot;).glob(&amp;quot;*-autofix.tex&amp;quot;):&lt;br /&gt;
 &lt;br /&gt;
 # loop over dirs&lt;br /&gt;
 p = Path() # cwd&lt;br /&gt;
 list_of_repos = [x for x in p.iterdir() if x.is_dir() and (x / &amp;quot;.git&amp;quot;).is_dir()]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
alternative using old os functions&lt;br /&gt;
Split path into folder, filename, ext&lt;br /&gt;
 import os&lt;br /&gt;
 (dirName, fileName) = os.path.split(f)&lt;br /&gt;
 (fileBaseName, fileExtension) = os.path.splitext(fileName)&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-out.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===File Manipulations: copy, move, delete, touch===&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 # copy&lt;br /&gt;
 from shutil import copyfile&lt;br /&gt;
 copyfile(Path(&amp;quot;file-source.txt&amp;quot;), Path(&amp;quot;dir&amp;quot;) / Path(&amp;quot;file-target.txt&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # move / rename&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).replace(Path(&amp;quot;file2.txt&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # delete&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).unlink()&lt;br /&gt;
 &lt;br /&gt;
 # touch&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).touch()&lt;br /&gt;
&lt;br /&gt;
===File size and timestamp===&lt;br /&gt;
 # size&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).stat().st_size&lt;br /&gt;
 &lt;br /&gt;
 # timestamp last modified&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).stat().st_mtime&lt;br /&gt;
&lt;br /&gt;
===Dir create/mkdir and delete===&lt;br /&gt;
 # create dir&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 Path(&amp;quot;myDir1/myDir2&amp;quot;).mkdir(parents=True, exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
 # delete dir&lt;br /&gt;
 import shutil&lt;br /&gt;
 shutil.rmtree(d)&lt;br /&gt;
&lt;br /&gt;
===Loop over Directories===&lt;br /&gt;
====Fetch Dir Contents / Loop over Files====&lt;br /&gt;
glob via pathlib&lt;br /&gt;
 for fileOut in Path(&amp;quot;mydir&amp;quot;).glob(&amp;quot;*-autofix.tex&amp;quot;):&lt;br /&gt;
&lt;br /&gt;
Get list of files in directory, filter dirs from list, filter by ext&lt;br /&gt;
 dir= &amp;quot;/path/to/some/dir&amp;quot;&lt;br /&gt;
 listoffiles = [ f for f in os.listdir(dir) if os.path.isfile(os.path.join(dir ,f)) and f.lower()[-4:] == &amp;quot;.gpx&amp;quot;]&lt;br /&gt;
 listoffiles.sort()&lt;br /&gt;
&lt;br /&gt;
====Traverse in Subdirs====&lt;br /&gt;
 # walk into path an fetch all files matching extension jpe?g&lt;br /&gt;
 files = []&lt;br /&gt;
 for (dirpath, dirnames, filenames) in os.walk(&amp;quot;.&amp;quot;):&lt;br /&gt;
     dirpath = dirpath.replace(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;)&lt;br /&gt;
     for file in filenames:&lt;br /&gt;
         if file.endswith(&amp;quot;.txt&amp;quot;):&lt;br /&gt;
             files.append(dirpath + &amp;quot;/&amp;quot; + file)&lt;br /&gt;
         elif re.search(r&amp;quot;\.jpe?g$&amp;quot;, file, re.IGNORECASE):&lt;br /&gt;
             files.append(dirpath + &amp;quot;/&amp;quot; + file)&lt;br /&gt;
&lt;br /&gt;
==File Parsing==&lt;br /&gt;
===File General===&lt;br /&gt;
====File Read====&lt;br /&gt;
 path_file_in = Path(&amp;quot;file.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # via pathlib&lt;br /&gt;
 cont = path_file_in.read_text(encoding=&amp;quot;utf-8&amp;quot;) # note: utf-8-sig for UTF-8-BOM&lt;br /&gt;
 &lt;br /&gt;
 # general&lt;br /&gt;
 with path_file_in.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
     cont = fh.read()&lt;br /&gt;
     # or&lt;br /&gt;
     lst = fh.readlines()&lt;br /&gt;
     # or&lt;br /&gt;
     line = fh.readline()&lt;br /&gt;
     # or&lt;br /&gt;
     for line in fh:&lt;br /&gt;
         print(line)&lt;br /&gt;
 &lt;br /&gt;
 fh = path_file_in.open(encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 ...&lt;br /&gt;
 fh.close()&lt;br /&gt;
&lt;br /&gt;
====File Write====&lt;br /&gt;
 path_file_out = Path(&amp;quot;out/1/out.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # write via pathlib&lt;br /&gt;
 path_file_out.write_text(&amp;quot;some text&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # write general&lt;br /&gt;
 with path_file_out.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     # w = overWrite file ; a = append to file&lt;br /&gt;
     # If running Python in Windows, &amp;quot;\n&amp;quot; is automatically replaced by &amp;quot;\r\n&amp;quot;.&lt;br /&gt;
     # To prevent this use newline=&#039;\n&#039;&lt;br /&gt;
     fh.writelines(lst)  # no linebreaks&lt;br /&gt;
     # or&lt;br /&gt;
     fh.write(&amp;quot;\n&amp;quot;.join(lst))&lt;br /&gt;
     # or&lt;br /&gt;
     for line in lst:&lt;br /&gt;
         fh.write(line)&lt;br /&gt;
 &lt;br /&gt;
     # Force update of file contents without closing it&lt;br /&gt;
     fh.flush()&lt;br /&gt;
 &lt;br /&gt;
 # alternative&lt;br /&gt;
 fh = path_file_out.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;)&lt;br /&gt;
 ...&lt;br /&gt;
 fh.close()&lt;br /&gt;
&lt;br /&gt;
===CSV===&lt;br /&gt;
====CSV Read====&lt;br /&gt;
 import csv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 with Path(&amp;quot;data.csv&amp;quot;).open(mode=&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as fh:  # for Excel use ANSI&lt;br /&gt;
     csv_reader = csv.DictReader(fh, dialect=&amp;quot;excel&amp;quot;, delimiter=&amp;quot;\t&amp;quot;)&lt;br /&gt;
     for row in csv_reader:&lt;br /&gt;
         print(f&#039;\t{row[&amp;quot;name&amp;quot;]} works in the {row[&amp;quot;department&amp;quot;]} department&#039;)&lt;br /&gt;
&lt;br /&gt;
====CSV Write====&lt;br /&gt;
 import csv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 # simple&lt;br /&gt;
 with Path(&amp;quot;data.tsv&amp;quot;).open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     csvwriter = csv.writer(fh, delimiter=&amp;quot;\t&amp;quot;)&lt;br /&gt;
     csvwriter.writerow((&amp;quot;Date&amp;quot;, &amp;quot;Confirmed&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # dictwriter&lt;br /&gt;
 with Path(&amp;quot;data.tsv&amp;quot;).open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     csvwriter = csv.DictWriter(&lt;br /&gt;
         fh,&lt;br /&gt;
         delimiter=&amp;quot;\t&amp;quot;,&lt;br /&gt;
         extrasaction=&amp;quot;ignore&amp;quot;,&lt;br /&gt;
         fieldnames=[&amp;quot;date&amp;quot;, &amp;quot;occupied_percent&amp;quot;, &amp;quot;occupied&amp;quot;, &amp;quot;total&amp;quot;],&lt;br /&gt;
     )&lt;br /&gt;
     csvwriter.writeheader()&lt;br /&gt;
     for d in my_list_of_dicts:&lt;br /&gt;
         d[&amp;quot;occupied_percent&amp;quot;] = round(100 * d[&amp;quot;occupied&amp;quot;] / d[&amp;quot;total&amp;quot;], 1)&lt;br /&gt;
         csvwriter.writerow(d)&lt;br /&gt;
&lt;br /&gt;
===JSON===&lt;br /&gt;
====JSON Read====&lt;br /&gt;
 import json&lt;br /&gt;
 # from file&lt;br /&gt;
 with path_to_download_file.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
     d_json = json.load(fh)&lt;br /&gt;
 # from string&lt;br /&gt;
 d_json = json.loads(response_text)&lt;br /&gt;
&lt;br /&gt;
 def json_read(file_path: Path) -&amp;gt; list[dict[str, str]]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Read JSON data from file.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     with file_path.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
         json_data = json.load(fh)&lt;br /&gt;
     return json_data&lt;br /&gt;
&lt;br /&gt;
====JSON Write====&lt;br /&gt;
Write dict to file in JSON format, keeping utf-8 encoding&lt;br /&gt;
 import json&lt;br /&gt;
 &lt;br /&gt;
 with path_to_download_file.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     json.dump(my_dict, fh, ensure_ascii=False, sort_keys=False, indent=2)&lt;br /&gt;
&lt;br /&gt;
 def json_write(file_path: Path, json_data: list[dict[str, str]]) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Write JSON data to file.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     with file_path.open(&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
         json.dump(json_data, fh, ensure_ascii=False, sort_keys=False, indent=2)&lt;br /&gt;
&lt;br /&gt;
===Excel===&lt;br /&gt;
====Excel Read====&lt;br /&gt;
 import openpyxl&lt;br /&gt;
 # or xlsxwriter for write-only&lt;br /&gt;
 &lt;br /&gt;
 workbook = openpyxl.load_workbook(&lt;br /&gt;
     pathToMyExcelFile,&lt;br /&gt;
     data_only=True,  # read values instead of formulas&lt;br /&gt;
     read_only=True,  # suppresses: &amp;quot;UserWarning: wmf image format is not supported so the image is being dropped&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 sheet = workbook[&amp;quot;mySheetName&amp;quot;]&lt;br /&gt;
 # or fetch active sheet&lt;br /&gt;
 sheet = workbook.active&lt;br /&gt;
 cell = sheet[&amp;quot;A34&amp;quot;]&lt;br /&gt;
 # or&lt;br /&gt;
 cell = sheet.cell(row=34, column=1)  # index start here with 1&lt;br /&gt;
 print(cell.value)&lt;br /&gt;
 # or&lt;br /&gt;
 print(sheet.cell(column=col, row=row).value)&lt;br /&gt;
&lt;br /&gt;
====Excel Write====&lt;br /&gt;
 import openpyxl&lt;br /&gt;
 &lt;br /&gt;
 workbook = openpyxl.Workbook()&lt;br /&gt;
 sheet = workbook.active&lt;br /&gt;
 cell = sheet[&amp;quot;A34&amp;quot;]&lt;br /&gt;
 # or&lt;br /&gt;
 cell = sheet.cell(row=i, column=j)  # index starts at 1&lt;br /&gt;
 cell.value = &amp;quot;asdf&amp;quot;&lt;br /&gt;
 workbook.save(&amp;quot;out.xlsx&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Word===&lt;br /&gt;
====Word Read====&lt;br /&gt;
 from io import BytesIO&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import pandas as pd&lt;br /&gt;
 from docx import Document&lt;br /&gt;
 from docx.document import Document as DocxDocument&lt;br /&gt;
 &lt;br /&gt;
 def read_doc_to_df(p_doc: Path | BytesIO) -&amp;gt; pd.DataFrame:  # noqa: C901, PLR0912&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Read Word document to DataFrame.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if isinstance(p_doc, Path):&lt;br /&gt;
         doc = Document(str(p_doc))&lt;br /&gt;
     elif isinstance(p_doc, BytesIO):&lt;br /&gt;
         doc = Document(p_doc)&lt;br /&gt;
 &lt;br /&gt;
     # list of multiple paragraphs per key&lt;br /&gt;
     data: dict[str, dict[str, list[str]]] = {}&lt;br /&gt;
 &lt;br /&gt;
     sections1: list[str] = []&lt;br /&gt;
     section1 = &amp;quot;&amp;quot;&lt;br /&gt;
     key = &amp;quot;&amp;quot;&lt;br /&gt;
     section2 = &amp;quot;&amp;quot;&lt;br /&gt;
     score = &amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     for paragraph in doc.paragraphs:&lt;br /&gt;
         assert paragraph.style is not None&lt;br /&gt;
         text = paragraph.text.strip()&lt;br /&gt;
         if paragraph.style.name == &amp;quot;Title&amp;quot;:  # doc title&lt;br /&gt;
             continue&lt;br /&gt;
         if paragraph.style.name == &amp;quot;Heading 1&amp;quot;:  # sheet name&lt;br /&gt;
             section1 = text&lt;br /&gt;
             if section1 not in sections1:&lt;br /&gt;
                 sections1.append(section1)&lt;br /&gt;
             key = &amp;quot;&amp;quot;&lt;br /&gt;
             section2 = &amp;quot;&amp;quot;&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Heading 2&amp;quot;:  # Key of the question&lt;br /&gt;
             key = text.split(&amp;quot; | &amp;quot;)[0]&lt;br /&gt;
             key = section1 + &amp;quot;|&amp;quot; + key&lt;br /&gt;
             section2 = &amp;quot;&amp;quot;&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Normal&amp;quot; and text.startswith(&amp;quot;[&amp;quot;) and &amp;quot;]&amp;quot; in text:&lt;br /&gt;
             section2, t2 = text[1:].split(&amp;quot;]&amp;quot;, maxsplit=1)&lt;br /&gt;
             if section2 == &amp;quot;Score&amp;quot;:&lt;br /&gt;
                 score = t2.strip()&lt;br /&gt;
                 data[key][section2] = [score]&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Normal&amp;quot;:&lt;br /&gt;
             text = paragraph.text.strip()&lt;br /&gt;
             # skip requirements leftovers and empty paragraphs&lt;br /&gt;
             if (section2 == &amp;quot;&amp;quot; and text.startswith(&amp;quot;...&amp;quot;)) or text == &amp;quot;&amp;quot;:&lt;br /&gt;
                 continue&lt;br /&gt;
             lst = data.setdefault(key, {}).setdefault(section2, [])&lt;br /&gt;
             lst.append(text)&lt;br /&gt;
             data[key][section2] = lst&lt;br /&gt;
         else:&lt;br /&gt;
             msg = f&amp;quot;Unexpected style: {paragraph.style.name} in paragraph: {text}&amp;quot;&lt;br /&gt;
             raise ValueError(msg)&lt;br /&gt;
 &lt;br /&gt;
     # flatten the text from list to str&lt;br /&gt;
     data2: dict[str, dict[str, str]] = {}&lt;br /&gt;
     for key, sections in data.items():&lt;br /&gt;
         for section2, lst in sections.items():&lt;br /&gt;
             s = &amp;quot;\n&amp;quot;.join(lst)&lt;br /&gt;
             if key not in data2:&lt;br /&gt;
                 data2[key] = {}&lt;br /&gt;
             data2[key][section2] = s.strip()&lt;br /&gt;
 &lt;br /&gt;
     df1 = pd.DataFrame.from_dict(data2, orient=&amp;quot;index&amp;quot;)&lt;br /&gt;
     df1.index.name = &amp;quot;Key&amp;quot;&lt;br /&gt;
     df1 = df1.reset_index()&lt;br /&gt;
     df1[ [ &amp;quot;Sheet&amp;quot;, &amp;quot;Key&amp;quot; ] ] = df1[&amp;quot;Key&amp;quot;].str.split(&amp;quot;|&amp;quot;, n=1, expand=True)&lt;br /&gt;
 &lt;br /&gt;
     return df1&lt;br /&gt;
&lt;br /&gt;
====Word Write====&lt;br /&gt;
 from io import BytesIO&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import pandas as pd from docx import Document&lt;br /&gt;
 from docx.document import Document as DocxDocument&lt;br /&gt;
 from docx.shared import Cm&lt;br /&gt;
 &lt;br /&gt;
 LAYOUT_SMALL_MARGINS = True&lt;br /&gt;
 if LAYOUT_SMALL_MARGINS:&lt;br /&gt;
     sections = doc.sections&lt;br /&gt;
     for section in sections:&lt;br /&gt;
         section.top_margin = Cm(1)&lt;br /&gt;
         section.bottom_margin = Cm(1)&lt;br /&gt;
         section.left_margin = Cm(1)&lt;br /&gt;
         section.right_margin = Cm(1)&lt;br /&gt;
 &lt;br /&gt;
 doc.add_heading(&amp;quot;My Title&amp;quot;, level=0)&lt;br /&gt;
 doc.add_heading(&amp;quot;My Section&amp;quot;, level=1)&lt;br /&gt;
 doc.add_paragraph(&amp;quot;Some Text)&lt;br /&gt;
 p = doc.add_paragraph()&lt;br /&gt;
 runner = p.add_run(bold text&amp;quot;)&lt;br /&gt;
 runner.bold = True  # runner.italic = True&lt;br /&gt;
&lt;br /&gt;
==Regular Expressions==&lt;br /&gt;
See [http://docs.python.org/library/re.html]&lt;br /&gt;
&lt;br /&gt;
See [https://pythex.org] for an online tester&lt;br /&gt;
&lt;br /&gt;
multiple flags are joined via pipe |&lt;br /&gt;
 s = re.sub(&amp;quot;asdf.*&amp;quot;, r&amp;quot;qwertz&amp;quot;, s, flags=re.DOTALL | re.IGNORECASE)&lt;br /&gt;
&lt;br /&gt;
====Lookahead and Lookbehind====&lt;br /&gt;
 pos lookahead: (?=...)&lt;br /&gt;
 neg lookahead: (?!...)&lt;br /&gt;
 pos lookbehind (?&amp;lt;=...)&lt;br /&gt;
 neg lookbehind (?&amp;lt;!...)&lt;br /&gt;
&lt;br /&gt;
====matching====&lt;br /&gt;
 import re&lt;br /&gt;
 &lt;br /&gt;
 # V0: simple 1&lt;br /&gt;
 myPattern = &amp;quot;(/\*\*\* 0097_210000_0192539580000_2898977_0050 \*\*\*/.*?)($|/\*\*\*)&amp;quot;&lt;br /&gt;
 myRegExp = re.compile(myPattern, re.DOTALL)&lt;br /&gt;
 myMatch = myRegExp.search(cont)&lt;br /&gt;
 assert myMatch != None, f&amp;quot;golden file not found in file {filename}&amp;quot;&lt;br /&gt;
 cont_golden = myMatch.group(1)&lt;br /&gt;
 &lt;br /&gt;
 # V1: simple 2&lt;br /&gt;
 assert (&lt;br /&gt;
     re.match(&amp;quot;^[a-z]{2}$&amp;quot;, d_settings[&amp;quot;country&amp;quot;]) != None&lt;br /&gt;
 ), f&#039;Error: county must be 2 digit lower case. We got: {d_settings[&amp;quot;country&amp;quot;]}&#039;&lt;br /&gt;
 &lt;br /&gt;
 # V2: find and count&lt;br /&gt;
 was = r&amp;quot;&amp;quot;&amp;quot;Part: ([^&amp;lt;+])&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 cnt_parts = len(re.findall(was, cont))&lt;br /&gt;
 cont = re.sub(was, r&amp;quot;\n\nTeil: \1\n\n&amp;quot;, cont)&lt;br /&gt;
 assert cnt_parts == 4, f&amp;quot;{cnt_parts} == 4&amp;quot;&lt;br /&gt;
 assert &amp;quot;Part:&amp;quot; not in cont&lt;br /&gt;
&lt;br /&gt;
=====Match email=====&lt;br /&gt;
 def checkValidEMail(email: str) -&amp;gt; bool:&lt;br /&gt;
     # from https://stackoverflow.com/posts/719543/timeline bottom edit&lt;br /&gt;
     if not re.fullmatch(r&amp;quot;^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$&amp;quot;, email):&lt;br /&gt;
         print(&amp;quot;Error: invalid email&amp;quot;)&lt;br /&gt;
         quit()&lt;br /&gt;
     return True&lt;br /&gt;
&lt;br /&gt;
====Find all====&lt;br /&gt;
 myMatches = re.findall(&#039;href=&amp;quot;([^&amp;quot;]+)&amp;quot;&#039;, cont)&lt;br /&gt;
 for myMatch in myMatches:&lt;br /&gt;
     print(myMatch)&lt;br /&gt;
&lt;br /&gt;
====substring====&lt;br /&gt;
 import re&lt;br /&gt;
 &lt;br /&gt;
 # simple via search&lt;br /&gt;
 lk_id = re.search(&#039;^.*timeseries\-(\d+)\.json$&#039;, f).group(1)&lt;br /&gt;
 &lt;br /&gt;
 # simple via sub&lt;br /&gt;
 myPattern = &amp;quot;^.*&amp;quot; + s1 + &amp;quot;(.*)&amp;quot; + s2 + &amp;quot;.*$&amp;quot;&lt;br /&gt;
 out = re.sub(myPattern, r&amp;quot;\1&amp;quot;, s)&lt;br /&gt;
 &lt;br /&gt;
 # more robust including an assert&lt;br /&gt;
 def substr_between(s: str, s1: str, s2: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     returns substring of s, found between strings s1 and s2&lt;br /&gt;
     s1 and s2 can be regular expressions&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     myPattern = s1 + &#039;(.*)&#039; + s2&lt;br /&gt;
     myRegExp = re.compile(myPattern)&lt;br /&gt;
     myMatches = myRegExp.search(s)&lt;br /&gt;
     assert myMatches != None, f&amp;quot;E: can&#039;t find &#039;{s1}&#039;...&#039;{s2}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     out = myMatches.group(1)&lt;br /&gt;
     return out&lt;br /&gt;
&lt;br /&gt;
 matchObj = re.search(r&amp;quot;(\d+\.\d+)&amp;quot;, text&lt;br /&gt;
 if matchObj:&lt;br /&gt;
   price = float( &#039;%s&#039; % (matchObj).group(0) )&lt;br /&gt;
&lt;br /&gt;
====Naming of match groups====&lt;br /&gt;
(?P&amp;lt;name&amp;gt;...), see [https://docs.python.org/3/library/re.html]&lt;br /&gt;
&lt;br /&gt;
====Search and Replace====&lt;br /&gt;
From [http://www.regular-expressions.info/python.html]&amp;lt;br&amp;gt;&lt;br /&gt;
re.sub(regex, replacement, str) performs a search-and-replace across subject, replacing all matches of regex in str with replacement. The result is returned by the sub() function. The str string you pass is not modified.&lt;br /&gt;
&lt;br /&gt;
 s = re.sub(&amp;quot;  +&amp;quot;, &amp;quot; &amp;quot;, s)&lt;br /&gt;
&lt;br /&gt;
====Splitting====&lt;br /&gt;
From [http://docs.python.org/library/re.html]&amp;lt;br&amp;gt;&lt;br /&gt;
split() splits a string into a list delimited by the passed pattern. The method is invaluable for converting textual data into data structures that can be easily read and modified by Python as demonstrated in the following example that creates a phonebook.&lt;br /&gt;
&lt;br /&gt;
First, here is the input. Normally it may come from a file, here we are using triple-quoted string syntax:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; input = &amp;quot;&amp;quot;&amp;quot;Ross McFluff: 834.345.1254 155 Elm Street&lt;br /&gt;
 ...&lt;br /&gt;
 ... Ronald Heathmore: 892.345.3428 436 Finley Avenue&lt;br /&gt;
 ... Frank Burger: 925.541.7625 662 South Dogwood Way&lt;br /&gt;
 ...&lt;br /&gt;
 ...&lt;br /&gt;
 ... Heather Albrecht: 548.326.4584 919 Park Place&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The entries are separated by one or more newlines. Now we convert the string into a list with each nonempty line having its own entry:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; entries = re.split(&amp;quot;\n+&amp;quot;, input)&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; entries&lt;br /&gt;
 [&#039;Ross McFluff: 834.345.1254 155 Elm Street&#039;,&lt;br /&gt;
 &#039;Ronald Heathmore: 892.345.3428 436 Finley Avenue&#039;,&lt;br /&gt;
 &#039;Frank Burger: 925.541.7625 662 South Dogwood Way&#039;,&lt;br /&gt;
 &#039;Heather Albrecht: 548.326.4584 919 Park Place&#039;]&lt;br /&gt;
&lt;br /&gt;
Finally, split each entry into a list with first name, last name, telephone number, and address. We use the maxsplit parameter of split() because the address has spaces, our splitting pattern, in it:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; [re.split(&amp;quot;:? &amp;quot;, entry, 3) for entry in entries]&lt;br /&gt;
 [[&#039;Ross&#039;, &#039;McFluff&#039;, &#039;834.345.1254&#039;, &#039;155 Elm Street&#039;],&lt;br /&gt;
 [&#039;Ronald&#039;, &#039;Heathmore&#039;, &#039;892.345.3428&#039;, &#039;436 Finley Avenue&#039;],&lt;br /&gt;
 [&#039;Frank&#039;, &#039;Burger&#039;, &#039;925.541.7625&#039;, &#039;662 South Dogwood Way&#039;],&lt;br /&gt;
 [&#039;Heather&#039;, &#039;Albrecht&#039;, &#039;548.326.4584&#039;, &#039;919 Park Place&#039;]]&lt;br /&gt;
&lt;br /&gt;
==== replace cont by linebreaks====&lt;br /&gt;
 def replace_cont_by_linebreaks(s: str, regex: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Replace regex in s by the number of linebreaks it originally contained.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     myMatches = re.findall(regex, s, flags=re.DOTALL)&lt;br /&gt;
     for match in myMatches:&lt;br /&gt;
         linebreaks = match.count(&amp;quot;\n&amp;quot;)&lt;br /&gt;
         s = s.replace(match, &amp;quot;\n&amp;quot; * linebreaks, 1)&lt;br /&gt;
     return s&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Image/Picture/Photo==&lt;br /&gt;
 from PIL import Image, ImageFilter  # pip install Pillow&lt;br /&gt;
 &lt;br /&gt;
 fileIn = &amp;quot;2018-02-09 13.56.25.jpg&amp;quot;&lt;br /&gt;
 # Read image&lt;br /&gt;
 img = Image.open(fileIn)&lt;br /&gt;
PROBLEM:&amp;lt;br/&amp;gt;&lt;br /&gt;
PIL Image.save() drops the IPTC data like tags, keywords, copywrite, ...&amp;lt;br/&amp;gt;&lt;br /&gt;
better using https://imagemagick.org instead when tags shall be kept&lt;br /&gt;
&lt;br /&gt;
====Resize====&lt;br /&gt;
 # Resize keeping aspect ration -&amp;gt; img.thumbnail&lt;br /&gt;
 # drops exif data, exif can be added from source file via exif= in save, see below&lt;br /&gt;
 size = 1920, 1920&lt;br /&gt;
 img.thumbnail(size, Image.ANTIALIAS)&lt;br /&gt;
&lt;br /&gt;
====Export file====&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-edit.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img = Image.open(fileIn)&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=&#039;keep&#039;)  # exif=dict_exif_bytes&lt;br /&gt;
     # JPEG Parameters&lt;br /&gt;
     # * qualitiy : &#039;keep&#039; or 1 (worst) to 95 (best), default = 75. Values above 95 should be avoided.&lt;br /&gt;
     # * dpi : tuple of integers representing the pixel density, (x,y)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     print(&amp;quot;cannot write file &#039;%s&#039;&amp;quot; % fileOut)&lt;br /&gt;
&lt;br /&gt;
====Export Progressive / web optimized JPEG====&lt;br /&gt;
 from PIL import ImageFile  # for MAXBLOCK for progressive export&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-progressive.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=80, optimize=True, progressive=True)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     ImageFile.MAXBLOCK = img.size[0] * img.size[1]&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=80, optimize=True, progressive=True)&lt;br /&gt;
&lt;br /&gt;
====JPEG Meta Data: EXIF and IPTC====&lt;br /&gt;
=====IPTC: Tags/Keywords=====&lt;br /&gt;
 from iptcinfo3 import IPTCInfo  # this works in pyhton 3!&lt;br /&gt;
 iptc = IPTCInfo(fileIn)&lt;br /&gt;
 if len(iptc[&#039;keywords&#039;]) &amp;gt; 0:  # or supplementalCategories or contacts&lt;br /&gt;
     print(&#039;====&amp;gt; Keywords&#039;)&lt;br /&gt;
     for key in sorted(iptc[&#039;keywords&#039;]):&lt;br /&gt;
         s = key.decode(&#039;ascii&#039;)  # decode binary strings&lt;br /&gt;
         print(s)&lt;br /&gt;
&lt;br /&gt;
=====EXIF via piexif=====&lt;br /&gt;
 import piexif  # pip install piexif&lt;br /&gt;
 exif_dict = piexif.load(img.info[&#039;exif&#039;])&lt;br /&gt;
 print(exif_dict[&#039;GPS&#039;][piexif.GPSIFD.GPSAltitude])&lt;br /&gt;
 # returns list of 2 integers: value and donator  -&amp;gt; v / d&lt;br /&gt;
 # (340000, 1000) =&amp;gt; 340m&lt;br /&gt;
 # (51, 2) =&amp;gt; 25.5m&lt;br /&gt;
 &lt;br /&gt;
 # Modify altitude&lt;br /&gt;
 exif_dict[&#039;GPS&#039;][piexif.GPSIFD.GPSAltitude] = (140, 1)  # 140m&lt;br /&gt;
 &lt;br /&gt;
 # write to file&lt;br /&gt;
 exif_bytes = piexif.dump(exif_dict)&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-modExif.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;jpeg&amp;quot;, exif=exif_bytes, quality=&#039;keep&#039;)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     print(&amp;quot;cannot write file &#039;%s&#039;&amp;quot; % fileOut)&lt;br /&gt;
or&lt;br /&gt;
 exif_dict = piexif.load(fileIn)&lt;br /&gt;
 for ifd in (&amp;quot;0th&amp;quot;, &amp;quot;Exif&amp;quot;, &amp;quot;GPS&amp;quot;, &amp;quot;1st&amp;quot;):&lt;br /&gt;
     print(&amp;quot;===&amp;quot; + ifd)&lt;br /&gt;
     for tag in exif_dict[ifd]:&lt;br /&gt;
         print(piexif.TAGS[ifd][tag][&amp;quot;name&amp;quot;], &amp;quot;\t&amp;quot;,&lt;br /&gt;
               tag, &amp;quot;\t&amp;quot;, exif_dict[ifd][tag])&lt;br /&gt;
 print(exif_dict[&#039;0th&#039;][306]) # 306 = DateTime&lt;br /&gt;
&lt;br /&gt;
=====EXIF via exifread=====&lt;br /&gt;
 # Open image file for reading (binary mode)&lt;br /&gt;
 fh = open(fileIn, &amp;quot;rb&amp;quot;)&lt;br /&gt;
 # Return Exif tags&lt;br /&gt;
 exif = exifread.process_file(fh)&lt;br /&gt;
 fh.close()&lt;br /&gt;
 # for tag in exif.keys():&lt;br /&gt;
 #     if tag not in (&#039;JPEGThumbnail&#039;, &#039;TIFFThumbnail&#039;, &#039;Filename&#039;, &#039;EXIF MakerNote&#039;):&lt;br /&gt;
 #         print(&amp;quot;%s\t%s&amp;quot; % (tag, exif[tag]))&lt;br /&gt;
 print(exif[&amp;quot;Image DateTime&amp;quot;])&lt;br /&gt;
 print(exif[&amp;quot;GPS GPSLatitude&amp;quot;])&lt;br /&gt;
 print(exif[&amp;quot;GPS GPSLongitude&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
=====EXIF GPS via PIL=====&lt;br /&gt;
 # from https://developer.here.com/blog/getting-started-with-geocoding-exif-image-metadata-in-python3&lt;br /&gt;
 def get_exif(filename):&lt;br /&gt;
     image = Image.open(filename)&lt;br /&gt;
     image.verify()&lt;br /&gt;
     image.close()&lt;br /&gt;
     return image._getexif()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_labeled_exif(exif):&lt;br /&gt;
     labeled = {}&lt;br /&gt;
     for (key, val) in exif.items():&lt;br /&gt;
         labeled[TAGS.get(key)] = val&lt;br /&gt;
     return labeled&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_geotagging(exif):&lt;br /&gt;
     if not exif:&lt;br /&gt;
         raise ValueError(&amp;quot;No EXIF metadata found&amp;quot;)&lt;br /&gt;
     geotagging = {}&lt;br /&gt;
     for (idx, tag) in TAGS.items():&lt;br /&gt;
         if tag == &amp;quot;GPSInfo&amp;quot;:&lt;br /&gt;
             if idx not in exif:&lt;br /&gt;
                 raise ValueError(&amp;quot;No EXIF geotagging found&amp;quot;)&lt;br /&gt;
             for (key, val) in GPSTAGS.items():&lt;br /&gt;
                 if key in exif[idx]:&lt;br /&gt;
                     geotagging[val] = exif[idx][key]&lt;br /&gt;
     return geotagging&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_decimal_from_dms(dms, ref):&lt;br /&gt;
     degrees = dms[0][0] / dms[0][1]&lt;br /&gt;
     minutes = dms[1][0] / dms[1][1] / 60.0&lt;br /&gt;
     seconds = dms[2][0] / dms[2][1] / 3600.0&lt;br /&gt;
     if ref in [&amp;quot;S&amp;quot;, &amp;quot;W&amp;quot;]:&lt;br /&gt;
         degrees = -degrees&lt;br /&gt;
         minutes = -minutes&lt;br /&gt;
         seconds = -seconds&lt;br /&gt;
     return round(degrees + minutes + seconds, 5)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_coordinates(geotags):&lt;br /&gt;
     lat = get_decimal_from_dms(geotags[&amp;quot;GPSLatitude&amp;quot;], geotags[&amp;quot;GPSLatitudeRef&amp;quot;])&lt;br /&gt;
     lon = get_decimal_from_dms(geotags[&amp;quot;GPSLongitude&amp;quot;], geotags[&amp;quot;GPSLongitudeRef&amp;quot;])&lt;br /&gt;
     return (lat, lon)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 exif = get_exif(fileIn)&lt;br /&gt;
 exif_labeled = get_labeled_exif(exif)&lt;br /&gt;
 print(exif_labeled[&amp;quot;DateTime&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
 geotags = get_geotagging(exif)&lt;br /&gt;
 print(get_coordinates(geotags))&lt;br /&gt;
&lt;br /&gt;
====Template Matching / Image Regocnition Using CV2 / OpenCV====&lt;br /&gt;
see [[Python - CV2]]&lt;br /&gt;
&lt;br /&gt;
====Optical Character Recognition (OCR) ====&lt;br /&gt;
see [[Python - OCR]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Templates/Snippets==&lt;br /&gt;
===Retry on Exception===&lt;br /&gt;
 for retry_no in range(MAX_RETRIES):&lt;br /&gt;
     try:&lt;br /&gt;
         if retry_no &amp;gt; 0:&lt;br /&gt;
             logger.warning(&amp;quot;retrying %d/%d...&amp;quot;, retry_no + 1, MAX_RETRIES)&lt;br /&gt;
         doit()&lt;br /&gt;
     except json.JSONDecodeError:&lt;br /&gt;
         logger.exception(&amp;quot;JSONDecodeError&amp;quot;)&lt;br /&gt;
         if retry_no == MAX_RETRIES - 1:&lt;br /&gt;
             # end of retries&lt;br /&gt;
             raise&lt;br /&gt;
&lt;br /&gt;
===Diff of 2 files===&lt;br /&gt;
 import difflib&lt;br /&gt;
 &lt;br /&gt;
 fh1 = p_file1.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 fh2 = p_file1.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 diff = difflib.ndiff(fh1.readlines(), fh2.readlines())&lt;br /&gt;
 delta = &amp;quot;&amp;quot;.join(line for line in diff if line.startswith((&amp;quot;+ &amp;quot;, &amp;quot;- &amp;quot;)))&lt;br /&gt;
 print(delta)&lt;br /&gt;
&lt;br /&gt;
===GPX parsing===&lt;br /&gt;
 import gpxpy&lt;br /&gt;
 import gpxpy.gpx&lt;br /&gt;
 # Elevation data by NASA: see lib at https://github.com/tkrajina/srtm.py&lt;br /&gt;
 fh_gpx_file = open(gpx_file_path, &#039;r&#039;)&lt;br /&gt;
 gpx = gpxpy.parse(fh_gpx_file)&lt;br /&gt;
 #  Loops for accessing the data&lt;br /&gt;
 for track in gpx.tracks:&lt;br /&gt;
     for segment in track.segments:&lt;br /&gt;
         for point in segment.points:&lt;br /&gt;
 for waypoint in gpx.waypoints:&lt;br /&gt;
 for route in gpx.routes:&lt;br /&gt;
     for point in route.points: &lt;br /&gt;
 # interesting properties of point / waypoint objects:&lt;br /&gt;
 point.time&lt;br /&gt;
 point.latitude&lt;br /&gt;
 point.longitude&lt;br /&gt;
 point.source&lt;br /&gt;
 waypoint.name&lt;br /&gt;
&lt;br /&gt;
===TypeHints===&lt;br /&gt;
 from typing import Any, Dict, cast&lt;br /&gt;
 creds = cast(dict[str, str], tomllib.load(f))  # type: ignore&lt;br /&gt;
 o = cast(Dict[str, Any], tomllib.load(f))  # type: ignore&lt;br /&gt;
 o[&amp;quot;sap&amp;quot;] = cast(Dict[str, str], o[&amp;quot;sap&amp;quot;])&lt;br /&gt;
 o[&amp;quot;settings&amp;quot;] = cast(Dict[str, str | int | bool], o[&amp;quot;settings&amp;quot;])&lt;br /&gt;
 o[&amp;quot;settings&amp;quot;][&amp;quot;sleep_time&amp;quot;] = cast(int, o[&amp;quot;settings&amp;quot;][&amp;quot;sleep_time&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
===Pylance/Pyright===&lt;br /&gt;
 import tomllib&lt;br /&gt;
 # shows warning: Import &amp;quot;xyz&amp;quot; could not be resolved&lt;br /&gt;
 # fix by &lt;br /&gt;
 import tomllib # pyright: ignore&lt;br /&gt;
&lt;br /&gt;
===asserts function argument validation===&lt;br /&gt;
aus [https://bildungsportal.sachsen.de/opal/auth/RepositoryEntry/12518195218/CourseNode/94506982489539?0 Python Kurs von Carsten Knoll]&lt;br /&gt;
 def eine_funktion(satz, ganzzahl, zahl2, liste):&lt;br /&gt;
   if not type(satz) == str:&lt;br /&gt;
     print &amp;quot;Datentpyfehler: satz&amp;quot;&lt;br /&gt;
     return -1&lt;br /&gt;
   if not isinstance(ganzzahl, int):&lt;br /&gt;
     print &amp;quot;Datentpyfehler: ganzzahl&amp;quot;&lt;br /&gt;
     return -2&lt;br /&gt;
   if not isinstance(liste, (tuple, list)):&lt;br /&gt;
     print &amp;quot;Datentpyfehler: liste&amp;quot;&lt;br /&gt;
     return -3&lt;br /&gt;
   # Kompakteste Variante (empfohlen): &lt;br /&gt;
   assert zahl2 &amp;gt; 0, &amp;quot;Error: zahl2 ist nicht &amp;gt; 0&amp;quot; # Assertation-Error bei Nichterfuellung&lt;br /&gt;
&lt;br /&gt;
 def F(x):&lt;br /&gt;
   if not isinstance(x, (float, int)):&lt;br /&gt;
     msg = &amp;quot;Zahl erwartet, %s bekommen&amp;quot; % type(x)&lt;br /&gt;
     raise ValueError(msg)&lt;br /&gt;
   return x**2&lt;br /&gt;
better:&lt;br /&gt;
 def F(x):&lt;br /&gt;
   assert isinstance(x, (float, int)), &amp;quot;Error: x is not of type float or int&amp;quot;&lt;br /&gt;
   return x**2&lt;br /&gt;
&lt;br /&gt;
 assert variant in [&lt;br /&gt;
     &amp;quot;normal&amp;quot;,&lt;br /&gt;
     &amp;quot;gray&amp;quot;,&lt;br /&gt;
     &amp;quot;cannyedge&amp;quot;,&lt;br /&gt;
 ], &amp;quot;Error: variant is not in &#039;normal&#039;, &#039;gray&#039;, &#039;cannyedge&#039;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===Exceptions===&lt;br /&gt;
see [https://medium.com/@saadjamilakhtar/5-best-practices-for-python-exception-handling-5e54b876a20]&lt;br /&gt;
&lt;br /&gt;
====Catching====&lt;br /&gt;
Catch keyboard interrupt and do a &amp;quot;save exit&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     i = 0&lt;br /&gt;
     while 1:&lt;br /&gt;
         i += 1&lt;br /&gt;
         print(i)&lt;br /&gt;
 except KeyboardInterrupt:&lt;br /&gt;
     print(&amp;quot;stopped&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
Catch &#039;&#039;&#039;all&#039;&#039;&#039; exceptions&lt;br /&gt;
 try:&lt;br /&gt;
   [...]&lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     print(&amp;quot;Exception: &amp;quot;, e)&lt;br /&gt;
&lt;br /&gt;
====Raising Exceptions====&lt;br /&gt;
 if resp.status_code != 200:  # noqa: PLR2004&lt;br /&gt;
     msg = f&amp;quot;Bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
     raise ValueError(msg) from None&lt;br /&gt;
&lt;br /&gt;
Custom Exceptions&lt;br /&gt;
 try: &lt;br /&gt;
   raise Exception(&amp;quot;HiHo&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
 raise ValueError¶&lt;br /&gt;
&lt;br /&gt;
===perl grep and map===&lt;br /&gt;
from [https://stackoverflow.com/questions/12845288/grep-on-elements-of-a-list]&lt;br /&gt;
 def grep(list, pattern):&lt;br /&gt;
     expr = re.compile(pattern)&lt;br /&gt;
     return [elem for elem in list if expr.match(elem)]&lt;br /&gt;
 or&lt;br /&gt;
 filteredList = filter(lambda x: x &amp;lt; 7 and x &amp;gt; 2, unfilteredList)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def map(list, was, womit):&lt;br /&gt;
     return list(map(lambda i: re.sub(was, womit, i), list))&lt;br /&gt;
     # was = &#039;.*&amp;quot;(\d+)&amp;quot;.*&#039;&lt;br /&gt;
     # womit = r&amp;quot;\1&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===unit testing using pytest===&lt;br /&gt;
install via&lt;br /&gt;
 pip install pytest&lt;br /&gt;
activate in vscode, see [https://code.visualstudio.com/docs/python/testing#_enable-a-test-framework]: To enable testing, use the Python: Configure Tests command on the Command Palette.&lt;br /&gt;
&lt;br /&gt;
see https://docs.pytest.org/en/6.2.x/assert.html#assert&lt;br /&gt;
&lt;br /&gt;
test_1.py:&lt;br /&gt;
 import myLib # my custom lib to test&lt;br /&gt;
 &lt;br /&gt;
 class TestClass:&lt;br /&gt;
     def test_one(self):&lt;br /&gt;
         x = &amp;quot;this&amp;quot;&lt;br /&gt;
         assert &amp;quot;h&amp;quot; in x&lt;br /&gt;
 &lt;br /&gt;
     def test_two(self):&lt;br /&gt;
         assert myLib.multiply(1, 2) == 2&lt;br /&gt;
 &lt;br /&gt;
     def test_three(self):&lt;br /&gt;
         assert myLib.multiply(2, 2) == 2&lt;br /&gt;
&lt;br /&gt;
====project structure====&lt;br /&gt;
=====simplest structore: test next to script=====&lt;br /&gt;
Alternative without tests dir:&lt;br /&gt;
 ./main.py&lt;br /&gt;
 ./helper.py &lt;br /&gt;
 ./helper_test.py&lt;br /&gt;
with helper_test.py:&lt;br /&gt;
 from helper import fnc1&lt;br /&gt;
&lt;br /&gt;
=====standalone model with src and tests dirs=====&lt;br /&gt;
for this dir structure&lt;br /&gt;
 src/helper.py &lt;br /&gt;
 tests/helper_test.py&lt;br /&gt;
helper_test.py needs&lt;br /&gt;
 import sys&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 sys.path.insert(0, (Path(__file__).parent.parent / &amp;quot;src&amp;quot;).as_posix())&lt;br /&gt;
&lt;br /&gt;
=====standalone model with tests dir=====&lt;br /&gt;
if the test files are placed inside a ./tests/ dir&lt;br /&gt;
 ./main.py&lt;br /&gt;
 ./helper.py &lt;br /&gt;
 tests/helper_test.py&lt;br /&gt;
helper_test.py needs &lt;br /&gt;
 sys.path.insert(0, Path(__file__).parent.parent.as_posix())&lt;br /&gt;
 from helper import fnc1&lt;br /&gt;
&lt;br /&gt;
====parametrize====&lt;br /&gt;
 import pytest&lt;br /&gt;
 @pytest.mark.parametrize(&lt;br /&gt;
     (&amp;quot;test_input&amp;quot;, &amp;quot;expected&amp;quot;),&lt;br /&gt;
     [(&amp;quot;&amp;quot;, None), (&amp;quot;PT30M&amp;quot;, 30), (&amp;quot;PT3H&amp;quot;, 3 * 60), (&amp;quot;PT2H30M&amp;quot;, 2 * 60 + 30)],&lt;br /&gt;
 )&lt;br /&gt;
 def test_task_est_to_minutes(test_input: str, expected: int) -&amp;gt; None:&lt;br /&gt;
     assert task_est_to_minutes(test_input) == expected&lt;br /&gt;
&lt;br /&gt;
=====fixures: prepare and cleanup test_data=====&lt;br /&gt;
to automatically run before and after each testcase&lt;br /&gt;
 @pytest.fixture(autouse=True)&lt;br /&gt;
 def _setup_tests():  # noqa: ANN202&lt;br /&gt;
     cache_prepare_lists()&lt;br /&gt;
     cache_prepare_tasks()&lt;br /&gt;
 &lt;br /&gt;
     yield&lt;br /&gt;
 &lt;br /&gt;
     cache_cleanup_test_data()&lt;br /&gt;
&lt;br /&gt;
====Test coverage report====&lt;br /&gt;
 pip install pytest-cov&lt;br /&gt;
 # console output&lt;br /&gt;
 pytest --cov&lt;br /&gt;
 &lt;br /&gt;
 # html report in coverage_report/index.html with details per file&lt;br /&gt;
 pytest --cov --cov-report=html:coverage_report&lt;br /&gt;
to exclude a code block from coverage report, add&lt;br /&gt;
 # pragma: no cover&lt;br /&gt;
&lt;br /&gt;
=== Sleep / Wait for input ===&lt;br /&gt;
sleep for a while&lt;br /&gt;
 import time&lt;br /&gt;
 time.sleep(60)&lt;br /&gt;
&lt;br /&gt;
wait for user input&lt;br /&gt;
 input(&amp;quot;press Enter to close&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Suppress Warnings===&lt;br /&gt;
see [https://docs.python.org/3/library/warnings.html#temporarily-suppressing-warnings]&lt;br /&gt;
 import warnings&lt;br /&gt;
 warnings.filterwarnings(&amp;quot;ignore&amp;quot;, message=&amp;quot;.*native_field_num.*not found in message.*&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Logging===&lt;br /&gt;
====V4 minimal stdout logging====&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.addLevelName(logging.DEBUG, &amp;quot;D&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.INFO, &amp;quot;I&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.WARNING, &amp;quot;W&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.ERROR, &amp;quot;E&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.CRITICAL, &amp;quot;C&amp;quot;)&lt;br /&gt;
 logging.basicConfig(level=logging.INFO, format=&amp;quot;%(levelname)s: %(name)s: %(message)s&amp;quot;)&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
 LOGGER.setLevel(logging.INFO)&lt;br /&gt;
 logging.getLogger(&amp;quot;httpx&amp;quot;).setLevel(logging.WARNING)&lt;br /&gt;
 &lt;br /&gt;
 # usage of variables and rounding of numbers. (%d also rounds floats)&lt;br /&gt;
 LOGGER.info(&amp;quot;file %s has %d lines with avg %.1f char/line&amp;quot;, filename, lines, c_p_l)&lt;br /&gt;
&lt;br /&gt;
====V3 simple logging====&lt;br /&gt;
 # main file:&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.basicConfig(&lt;br /&gt;
     level=logging.INFO,&lt;br /&gt;
     format=&amp;quot;%(asctime)s\t%(levelname)s\t%(name)s\t%(message)s&amp;quot;,&lt;br /&gt;
     handlers=[&lt;br /&gt;
         RotatingFileHandler(&lt;br /&gt;
             Path(__file__).with_suffix(&amp;quot;.log&amp;quot;),&lt;br /&gt;
             maxBytes=10485760,  # 10 MB = 10*1024*1024,&lt;br /&gt;
             backupCount=1,&lt;br /&gt;
             encoding=&amp;quot;utf-8&amp;quot;,&lt;br /&gt;
         )&lt;br /&gt;
     ],&lt;br /&gt;
     datefmt=&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
 logger = logging.getLogger(Path(__file__).stem)&lt;br /&gt;
 logger.info(&amp;quot;Start&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # other files:&lt;br /&gt;
 import logging&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
&lt;br /&gt;
====V2: rotating file and STDOUT====&lt;br /&gt;
 # 1. setup&lt;br /&gt;
 import logging&lt;br /&gt;
 from logging.handlers import RotatingFileHandler&lt;br /&gt;
 &lt;br /&gt;
 logfile = &amp;quot;myApp.log&amp;quot;&lt;br /&gt;
 maxBytes = 20 * 1024 * 1024&lt;br /&gt;
 backupCount = 5&lt;br /&gt;
 loglevel_console = logging.INFO&lt;br /&gt;
 loglevel_file = logging.DEBUG&lt;br /&gt;
 &lt;br /&gt;
 # create logger&lt;br /&gt;
 logger = logging.getLogger(&amp;quot;root&amp;quot;)&lt;br /&gt;
 logger.setLevel(loglevel_file)&lt;br /&gt;
 &lt;br /&gt;
 # console handler&lt;br /&gt;
 ch = logging.StreamHandler()&lt;br /&gt;
 ch.setLevel(loglevel_console)&lt;br /&gt;
 &lt;br /&gt;
 # rotating file handler&lt;br /&gt;
 # fh = logging.FileHandler(logfile)&lt;br /&gt;
 fh = RotatingFileHandler(logfile, maxBytes=maxBytes, backupCount=backupCount)&lt;br /&gt;
 fh.setLevel(loglevel_file)&lt;br /&gt;
 &lt;br /&gt;
 # create formatter and add it to the handlers&lt;br /&gt;
 # %(name)s = LoggerName, %(threadName)s = TreadName&lt;br /&gt;
 formatter = logging.Formatter(&lt;br /&gt;
     &amp;quot;%(asctime)s - %(levelname)s - %(name)s - %(threadName)s - %(message)s &amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 fh.setFormatter(formatter)&lt;br /&gt;
 ch.setFormatter(formatter)&lt;br /&gt;
 &lt;br /&gt;
 # add the handlers to the logger&lt;br /&gt;
 logger.addHandler(ch)&lt;br /&gt;
 logger.addHandler(fh)&lt;br /&gt;
 &lt;br /&gt;
 logger.debug(&amp;quot;DebugMe&amp;quot;)&lt;br /&gt;
 logger.info(&amp;quot;Starting&amp;quot;)&lt;br /&gt;
 logger.warning(&amp;quot;Attention&amp;quot;)&lt;br /&gt;
 logger.error(&amp;quot;Something went wrong&amp;quot;)&lt;br /&gt;
 logger.critical(&amp;quot;Something seriously went wrong &amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # 2. in other files/modules now use&lt;br /&gt;
 import logging&lt;br /&gt;
 &lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 logger.info(&amp;quot;text&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 ...&lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     logger.exception(&amp;quot;Unhandeled exception&amp;quot;)&lt;br /&gt;
     quit()&lt;br /&gt;
&lt;br /&gt;
====V1: Simple====&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.basicConfig(&lt;br /&gt;
     level=logging.INFO,&lt;br /&gt;
     format=&amp;quot;%(asctime)s\t%(levelname)s\t%(message)s&amp;quot;,  # \t%(name)s&lt;br /&gt;
     filename=Path(__file__).with_suffix(&amp;quot;.log&amp;quot;), # uncomment to switch to stdout&lt;br /&gt;
     filemode=&amp;quot;a&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 logger.debug(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.info(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.warning(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.error(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.critical(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 try:&lt;br /&gt;
 ...&lt;br /&gt;
 except psycopg2.DatabaseError:&lt;br /&gt;
     logger.exception(&amp;quot;Database connection error: %s&amp;quot;)&lt;br /&gt;
     raise&lt;br /&gt;
&lt;br /&gt;
===Process Bar===&lt;br /&gt;
see [https://tqdm.github.io tqdm]&lt;br /&gt;
 from tqdm import tqdm&lt;br /&gt;
 for i in tqdm(range(10000)):&lt;br /&gt;
     ....&lt;br /&gt;
or&lt;br /&gt;
 with tqdm(total=total_iterations, desc=&amp;quot;Processing&amp;quot;) as progress_bar:&lt;br /&gt;
     ...&lt;br /&gt;
     progress_bar.update(1)&lt;br /&gt;
&lt;br /&gt;
===CGI Web development===&lt;br /&gt;
 # Print necessary headers.&lt;br /&gt;
 print(&amp;quot;Content-Type: text/html&amp;quot;)&lt;br /&gt;
 print()&lt;br /&gt;
 &lt;br /&gt;
 # errors and debugging info to browser&lt;br /&gt;
 import cgitb&lt;br /&gt;
 cgitb.enable()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Access URL or Form Parameters&lt;br /&gt;
 # V2 from https://www.tutorialspoint.com/python/python_cgi_programming.htm&lt;br /&gt;
 import cgi&lt;br /&gt;
 form = cgi.FieldStorage()&lt;br /&gt;
 username = form.getvalue(&#039;username&#039;)&lt;br /&gt;
 print(username)&lt;br /&gt;
&lt;br /&gt;
 # V1&lt;br /&gt;
 import sys&lt;br /&gt;
 import urllib.parse&lt;br /&gt;
 query = os.environ.get(&#039;QUERY_STRING&#039;)&lt;br /&gt;
 query = urllib.parse.unquote(query, errors=&amp;quot;surrogateescape&amp;quot;)&lt;br /&gt;
 d = dict(qc.split(&amp;quot;=&amp;quot;) for qc in query.split(&amp;quot;&amp;amp;&amp;quot;))&lt;br /&gt;
 print(d)&lt;br /&gt;
&lt;br /&gt;
====CGI Backend Returning JSONs====&lt;br /&gt;
 #!/usr/local/bin/python3.6&lt;br /&gt;
 # -*- coding: utf-8 -*-&lt;br /&gt;
 &lt;br /&gt;
 import cgi&lt;br /&gt;
 import json&lt;br /&gt;
 &lt;br /&gt;
 # Print necessary headers.&lt;br /&gt;
 print(&amp;quot;Content-type: application/json&amp;quot;)&lt;br /&gt;
 print()&lt;br /&gt;
 &lt;br /&gt;
 def get_form_parameter(para: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;asserts that a given parameter is set and returns its value&amp;quot;&lt;br /&gt;
     value = form.getvalue(para)&lt;br /&gt;
     assert value, f&amp;quot;Error: parameter {para} missing&amp;quot;&lt;br /&gt;
     assert value != &amp;quot;&amp;quot;, f&amp;quot;Error: parameter {para} missing&amp;quot;&lt;br /&gt;
     return value&lt;br /&gt;
  &lt;br /&gt;
 response = {}&lt;br /&gt;
 response[&#039;status&#039;] = &amp;quot;ok&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 try:&lt;br /&gt;
     action = get_form_parameter(&amp;quot;action&amp;quot;)&lt;br /&gt;
     response[&#039;action&#039;] = action&lt;br /&gt;
     if action == &amp;quot;myAction&amp;quot;:&lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     response[&#039;status&#039;] = &amp;quot;error&amp;quot;&lt;br /&gt;
     d = {&amp;quot;type&amp;quot;: str(type(e)), &amp;quot;text&amp;quot;: str(e)}&lt;br /&gt;
     response[&amp;quot;exception&amp;quot;] = d&lt;br /&gt;
 &lt;br /&gt;
 finally:&lt;br /&gt;
     print(json.dumps(response))&lt;br /&gt;
&lt;br /&gt;
==Databases==&lt;br /&gt;
===PostgreSQL===&lt;br /&gt;
====Improved helper function====&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Helper functions for DB access.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 import datetime as dt&lt;br /&gt;
 import logging&lt;br /&gt;
 &lt;br /&gt;
 import psycopg2&lt;br /&gt;
 import psycopg2.extras&lt;br /&gt;
 from creds_db import credentials as creds_db&lt;br /&gt;
 &lt;br /&gt;
 # Configure logging&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 &lt;br /&gt;
 connection: psycopg2.extensions.connection = None  # type: ignore&lt;br /&gt;
 cursor_query: psycopg2.extensions.cursor = None  # type: ignore&lt;br /&gt;
 cursor_write: psycopg2.extensions.cursor = None  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def connect() -&amp;gt; (&lt;br /&gt;
     tuple[&lt;br /&gt;
         psycopg2.extensions.connection,&lt;br /&gt;
         psycopg2.extensions.cursor,&lt;br /&gt;
         psycopg2.extensions.cursor,&lt;br /&gt;
     ]&lt;br /&gt;
 ):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Connect to the database.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     try:&lt;br /&gt;
         connection = psycopg2.connect(**creds_db)&lt;br /&gt;
         cursor_query = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)&lt;br /&gt;
         cursor_write = connection.cursor()&lt;br /&gt;
         logger.info(&lt;br /&gt;
             &amp;quot;Connected to database %s on host %s&amp;quot;,&lt;br /&gt;
             creds_db[&amp;quot;database&amp;quot;],&lt;br /&gt;
             creds_db[&amp;quot;host&amp;quot;],&lt;br /&gt;
         )&lt;br /&gt;
     except psycopg2.DatabaseError:&lt;br /&gt;
         logger.exception(&amp;quot;Database connection error: %s&amp;quot;)&lt;br /&gt;
         raise&lt;br /&gt;
     else:&lt;br /&gt;
         return connection, cursor_query, cursor_write&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def disconnect() -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Disconnect from the database.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     try:&lt;br /&gt;
         if cursor_query:&lt;br /&gt;
             cursor_query.close()&lt;br /&gt;
         if cursor_write:&lt;br /&gt;
             cursor_write.close()&lt;br /&gt;
         if connection:&lt;br /&gt;
             connection.close()&lt;br /&gt;
         logger.info(&amp;quot;Disconnected from the database&amp;quot;)&lt;br /&gt;
     except psycopg2.DatabaseError:&lt;br /&gt;
         logger.exception(&amp;quot;Error during disconnection: %s&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def exists(url: str, valid_hours: float) -&amp;gt; bool | None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Check if a record exists.&lt;br /&gt;
 &lt;br /&gt;
     Returns&lt;br /&gt;
     -------&lt;br /&gt;
     None for missing record&lt;br /&gt;
     True for valid record&lt;br /&gt;
     False for expired record&lt;br /&gt;
 &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=valid_hours)&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT delete_at &amp;gt; %s AS &amp;quot;valid&amp;quot;&lt;br /&gt;
 FROM my_table&lt;br /&gt;
 WHERE url = %s LIMIT 1;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_query.execute(sql, (delete_at, url))&lt;br /&gt;
     res = cursor_query.fetchone()&lt;br /&gt;
     return res[0] if res else None&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def query1(url: str) -&amp;gt; dict | None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Execute a query.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT *&lt;br /&gt;
 FROM my_table&lt;br /&gt;
 WHERE url = %s LIMIT 1&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_query.execute(sql, (url,))&lt;br /&gt;
     return cursor_query.fetchone()  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def insert(url: str, response: str, delete_in_hours: float) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Insert a record.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=delete_in_hours)&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 INSERT INTO my_table (url, response, delete_at)&lt;br /&gt;
 VALUES (%s, %s, %s);&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (url, response, delete_at))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def delete(url: str) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Delete a record.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 DELETE FROM my_table&lt;br /&gt;
 WHERE url = %s;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (url,))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def delete_expired(in_hours: float) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Delete records that expire in less than in_hours from now.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=in_hours)&lt;br /&gt;
 &lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 DELETE FROM my_table&lt;br /&gt;
 WHERE delete_at &amp;lt; %s;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (delete_at,))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 connection, cursor_query, cursor_write = connect()&lt;br /&gt;
&lt;br /&gt;
====Basics====&lt;br /&gt;
 import psycopg2&lt;br /&gt;
 import psycopg2.extras&lt;br /&gt;
 &lt;br /&gt;
 credentials = {&lt;br /&gt;
     &amp;quot;host&amp;quot;: &amp;quot;localhost&amp;quot;,&lt;br /&gt;
     &amp;quot;port&amp;quot;: 5432,&lt;br /&gt;
     &amp;quot;database&amp;quot;: &amp;quot;myDB&amp;quot;,&lt;br /&gt;
     &amp;quot;user&amp;quot;: &amp;quot;myUser&amp;quot;,&lt;br /&gt;
     &amp;quot;password&amp;quot;: &amp;quot;myPwd&amp;quot;,&lt;br /&gt;
 }&lt;br /&gt;
 connection = psycopg2.connect(**credentials)&lt;br /&gt;
 cursor = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)&lt;br /&gt;
 &lt;br /&gt;
 l_bind_vars = [[&amp;quot;A1&amp;quot;, &amp;quot;A2&amp;quot;], [&amp;quot;B1&amp;quot;, &amp;quot;B2&amp;quot;]]&lt;br /&gt;
 &lt;br /&gt;
 sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT * FROM myTable&lt;br /&gt;
 WHERE 1=1 &lt;br /&gt;
 AND status NOT IN (&#039;CLOSED&#039;) &lt;br /&gt;
 AND ColA = %s &lt;br /&gt;
 AND ColB = %s &lt;br /&gt;
 ORDER BY created DESC&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 cursor.execute(sql, l_bind_vars)&lt;br /&gt;
 d_data = dict(cursor.fetchone())&lt;br /&gt;
&lt;br /&gt;
====export result to csv file====&lt;br /&gt;
 sql1 = &amp;quot;SELECT * FROM table&amp;quot;&lt;br /&gt;
 sql2 = &amp;quot;COPY (&amp;quot; + sql1 + &amp;quot;) TO STDOUT WITH CSV HEADER DELIMITER &#039;\t&#039;&amp;quot;&lt;br /&gt;
         with open(&amp;quot;out.csv&amp;quot;, &amp;quot;w&amp;quot;) as file:&lt;br /&gt;
             cursor.copy_expert(sql2, file)&lt;br /&gt;
&lt;br /&gt;
====ReadConfigFile to connect to PostgreSQL====&lt;br /&gt;
from [http://www.postgresqltutorial.com/postgresql-python/connect/]&lt;br /&gt;
database.ini&lt;br /&gt;
 [postgresql]&lt;br /&gt;
 host=dbhost&lt;br /&gt;
 port=5432&lt;br /&gt;
 database=dbname&lt;br /&gt;
 user=dbuser&lt;br /&gt;
 password=dbpass&lt;br /&gt;
&lt;br /&gt;
 from configparser import ConfigParser&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def config(filename=&amp;quot;database.ini&amp;quot;, section=&amp;quot;postgresql&amp;quot;):&lt;br /&gt;
     parser = ConfigParser()&lt;br /&gt;
     parser.read(filename)&lt;br /&gt;
     # get section, default to postgresql&lt;br /&gt;
     db = {}&lt;br /&gt;
     if parser.has_section(section):&lt;br /&gt;
         params = parser.items(section)&lt;br /&gt;
         for param in params:&lt;br /&gt;
             db[param[0]] = param[1]&lt;br /&gt;
     else:&lt;br /&gt;
         raise Exception(&lt;br /&gt;
             &amp;quot;Section {0} not found in the {1} file&amp;quot;.format(section, filename)&lt;br /&gt;
         )&lt;br /&gt;
     return db&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
main.py&lt;br /&gt;
 import psycopg2&lt;br /&gt;
 from config import config&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def connect():&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Connect to the PostgreSQL database server&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     conn = None&lt;br /&gt;
     try:&lt;br /&gt;
         params = config()  # read connection parameters&lt;br /&gt;
         print(&amp;quot;Connecting to the PostgreSQL database...&amp;quot;)&lt;br /&gt;
         conn = psycopg2.connect(**params)&lt;br /&gt;
         cur = conn.cursor()  # create a cursor&lt;br /&gt;
         print(&amp;quot;PostgreSQL database version:&amp;quot;)&lt;br /&gt;
         cur.execute(&amp;quot;SELECT version()&amp;quot;)  # execute a statement&lt;br /&gt;
         db_version = cur.fetchone()&lt;br /&gt;
         print(db_version)&lt;br /&gt;
         cur.close()&lt;br /&gt;
     except (Exception, psycopg2.DatabaseError) as error:&lt;br /&gt;
         print(error)&lt;br /&gt;
     finally:&lt;br /&gt;
         if conn is not None:&lt;br /&gt;
             conn.close()&lt;br /&gt;
             print(&amp;quot;Database connection closed.&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     connect()&lt;br /&gt;
&lt;br /&gt;
===SQL Lite / SQLite===&lt;br /&gt;
see page [[SQLite]]&lt;br /&gt;
&lt;br /&gt;
===InfluxDB===&lt;br /&gt;
see [[InfluxDB]]&lt;br /&gt;
&lt;br /&gt;
==Internet Access==&lt;br /&gt;
===Send E-Mails===&lt;br /&gt;
see [[Python - eMail]]&lt;br /&gt;
&lt;br /&gt;
===Download data using browser UA / REST===&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 headers = {&lt;br /&gt;
     &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0&amp;quot;,&lt;br /&gt;
 }&lt;br /&gt;
 resp = requests.get(&lt;br /&gt;
     url,&lt;br /&gt;
     headers=headers,&lt;br /&gt;
     timeout=3,  # timeout in sec, requests should always have a timeout!&lt;br /&gt;
     # timeout=(1,3), # 1s connect-timout, 3s read-timeout&lt;br /&gt;
 )&lt;br /&gt;
 if resp.status_code != 200:  # noqa: PLR2004&lt;br /&gt;
     msg = f&amp;quot;E: bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
     raise Exception(msg)  # noqa: TRY002&lt;br /&gt;
&lt;br /&gt;
====Download only if cache is too old====&lt;br /&gt;
 import time&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 def fetch_url_or_cache(file_cache: Path, url) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Fetch URL and cache in a file.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if check_cache_file_available_and_recent(&lt;br /&gt;
         file_path=file_cache, max_age=3600, verbose=False&lt;br /&gt;
     ):&lt;br /&gt;
         with file_cache.open(mode=&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
             s = fh.read()&lt;br /&gt;
     else:&lt;br /&gt;
         s = fetch(url=url)&lt;br /&gt;
         with file_cache.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
             fh.writelines(s)&lt;br /&gt;
     return s&lt;br /&gt;
 &lt;br /&gt;
 def check_cache_file_available_and_recent(&lt;br /&gt;
     file_path: Path,&lt;br /&gt;
     max_age: int = 3500,&lt;br /&gt;
 ) -&amp;gt; bool:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Check if cache file exists and is recent.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cache_good = False&lt;br /&gt;
     if file_path.exists() and (time.time() - file_path.stat().st_mtime &amp;lt; max_age):&lt;br /&gt;
         cache_good = True&lt;br /&gt;
     return cache_good&lt;br /&gt;
 &lt;br /&gt;
 # or&lt;br /&gt;
 def check_cache_file_available_and_recent_verbose(&lt;br /&gt;
     file_path: Path, max_age: int = 3600, *, verbose: bool = False&lt;br /&gt;
 ) -&amp;gt; bool:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Check if cache file exists and is recent.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cache_good = True&lt;br /&gt;
     if not file_path.exists():&lt;br /&gt;
         if verbose:&lt;br /&gt;
             print(f&amp;quot;No Cache available: {file_path}&amp;quot;)&lt;br /&gt;
         cache_good = False&lt;br /&gt;
     if cache_good and time.time() - (time.time() - file_path.stat().st_mtime &amp;lt; max_age):&lt;br /&gt;
         if verbose:&lt;br /&gt;
             print(f&amp;quot;Cache too old: {file_path}&amp;quot;)&lt;br /&gt;
         cache_good = False&lt;br /&gt;
     return cache_good&lt;br /&gt;
 &lt;br /&gt;
 def fetch(url) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Fetch URL.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     headers = {&lt;br /&gt;
         &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0&amp;quot;,  # noqa: E501&lt;br /&gt;
     }&lt;br /&gt;
     resp = requests.get(url, headers=headers, timeout=5)&lt;br /&gt;
     if resp.status_code == 200:  # noqa: PLR2004&lt;br /&gt;
         return resp.content.decode(&amp;quot;ascii&amp;quot;)&lt;br /&gt;
     else:  # noqa: RET505&lt;br /&gt;
         msg = f&amp;quot;E: bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
         raise Exception(msg)  # noqa: TRY002&lt;br /&gt;
&lt;br /&gt;
====Download using urllib.request====&lt;br /&gt;
 from urllib.request import urlopen&lt;br /&gt;
 &lt;br /&gt;
 url = &amp;quot;https://pomber.github.io/covid19/timeseries.json&amp;quot;&lt;br /&gt;
 with urlopen(url) as response:  # noqa: S310&lt;br /&gt;
     response_text = response.read()&lt;br /&gt;
&lt;br /&gt;
===HTML parsing and extracting of elements===&lt;br /&gt;
V2: via BeautifulSoup&lt;br /&gt;
 from bs4 import BeautifulSoup  # pip install beautifulsoup4&lt;br /&gt;
 &lt;br /&gt;
 soup = BeautifulSoup(cont, features=&amp;quot;html.parser&amp;quot;)&lt;br /&gt;
 my_element = soup.find(&amp;quot;div&amp;quot;, {&amp;quot;class&amp;quot;: &amp;quot;user-formatted-inner&amp;quot;})&lt;br /&gt;
 my_body = my_element.prettify()&lt;br /&gt;
 # my_body = my_element.encode()&lt;br /&gt;
 # my_body = str(my_element)&lt;br /&gt;
&lt;br /&gt;
V1: via lxml and xpath&lt;br /&gt;
 from lxml import html&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 page = requests.get(url)&lt;br /&gt;
 tree = html.fromstring(page.content)&lt;br /&gt;
 tbody_trs = tree.xpath(&amp;quot;//*/tbody/tr&amp;quot;)&lt;br /&gt;
 l_rows = []&lt;br /&gt;
 for tr in tbody_trs:&lt;br /&gt;
     l_columns = []&lt;br /&gt;
     if len(tr) != 15:&lt;br /&gt;
         continue&lt;br /&gt;
     for td in tr:&lt;br /&gt;
         l_columns.append(td.text_content())&lt;br /&gt;
         l_rows.append(list(l_columns))&lt;br /&gt;
&lt;br /&gt;
===HTML entities to unicode===&lt;br /&gt;
 import html&lt;br /&gt;
 cont = html.unescape(cont)&lt;br /&gt;
&lt;br /&gt;
==GUI Interactions==&lt;br /&gt;
===Take Screenshot===&lt;br /&gt;
 import pyautogui # (c:\Python\Scripts\)pip install pyautogui&lt;br /&gt;
 # pyautogui does only support screenshots on monitor #1&lt;br /&gt;
 ...&lt;br /&gt;
 screenshot = pyautogui.screenshot()&lt;br /&gt;
 # screenshot = pyautogui.screenshot(region=(screenshotX,screenshotY, screenshotW, screenshotH))&lt;br /&gt;
 screenshot = np.array(screenshot) &lt;br /&gt;
 # Convert RGB to BGR &lt;br /&gt;
 screenshot = screenshot[:, :, ::-1].copy()&lt;br /&gt;
&lt;br /&gt;
===Mouse Actions===&lt;br /&gt;
 def clickIt(x,y,key=&amp;quot;&amp;quot;) :&lt;br /&gt;
   x0, y0 = pyautogui.position()&lt;br /&gt;
   if key != &amp;quot;&amp;quot;: # crtl, shift&lt;br /&gt;
     pyautogui.keyDown(key)&lt;br /&gt;
   pyautogui.moveTo(x, y, duration=0.2)&lt;br /&gt;
   pyautogui.click(x=x , y=y, button=&#039;left&#039;, clicks=1, interval=0.1)&lt;br /&gt;
   if key != &amp;quot;&amp;quot;: # crtl, shift&lt;br /&gt;
     pyautogui.keyUp(key)&lt;br /&gt;
   pyautogui.moveTo(x0, y0)&lt;br /&gt;
&lt;br /&gt;
===Web Automation===&lt;br /&gt;
 from selenium import webdriver&lt;br /&gt;
 from selenium.webdriver.common.keys import Keys&lt;br /&gt;
 &lt;br /&gt;
 # from selenium.webdriver import Firefox&lt;br /&gt;
 from selenium.webdriver.firefox.options import Options&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 import glob&lt;br /&gt;
 &lt;br /&gt;
 class StravaUserMapDL():&lt;br /&gt;
     def __init__(self):&lt;br /&gt;
         self.driver = webdriver.Firefox()&lt;br /&gt;
 &lt;br /&gt;
     def login(self):&lt;br /&gt;
         driver = self.driver&lt;br /&gt;
         url = &amp;quot;https://www.somewebpage.com&amp;quot;&lt;br /&gt;
         email = &amp;quot;myemail&amp;quot;&lt;br /&gt;
         password = &amp;quot;mypassword&amp;quot;&lt;br /&gt;
         driver.get(url)&lt;br /&gt;
 &lt;br /&gt;
         title = driver.title&lt;br /&gt;
         urlIs = driver.current_url&lt;br /&gt;
         cont = driver.page_source #  as string&lt;br /&gt;
         FILE = open(filename,&amp;quot;w&amp;quot;) # w = overWrite file ; a = append to file&lt;br /&gt;
         FILE.write(cont)&lt;br /&gt;
         FILE.close()         &lt;br /&gt;
 &lt;br /&gt;
         # handle login if urlIs != url&lt;br /&gt;
         if (urlIs != url): &lt;br /&gt;
             # activate checkbox &#039;remember_me&#039;&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;remember_me&#039;)&lt;br /&gt;
             if (elem.is_selected() == False):&lt;br /&gt;
                 elem.click()&lt;br /&gt;
             assert elem.is_selected() == True&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;email&#039;)&lt;br /&gt;
             elem.send_keys(email)&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;password&#039;)&lt;br /&gt;
             elem.send_keys(password)&lt;br /&gt;
             elem.send_keys(Keys.RETURN)&lt;br /&gt;
             # Wait until login pages is replaced by real page&lt;br /&gt;
             urlIs = driver.current_url&lt;br /&gt;
             while (urlIs != url):&lt;br /&gt;
                 time.sleep(1)&lt;br /&gt;
                 urlIs = driver.current_url&lt;br /&gt;
             print (urlIs)&lt;br /&gt;
 &lt;br /&gt;
             # results = driver.find_elements_by_class_name(&#039;following&#039;)&lt;br /&gt;
             # results = driver.find_elements_by_tag_name(&#039;li&#039;)&lt;br /&gt;
 &lt;br /&gt;
             # print(results[0].text)&lt;br /&gt;
         assert (urlIs == url)&lt;br /&gt;
&lt;br /&gt;
===Unit Tests using Web Automation===&lt;br /&gt;
 import unittest&lt;br /&gt;
 from selenium import webdriver&lt;br /&gt;
 from selenium.webdriver.common.keys import Keys&lt;br /&gt;
 &lt;br /&gt;
 #from selenium.webdriver import Firefox&lt;br /&gt;
 from selenium.webdriver.firefox.options import Options&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 class PythonOrgSearch(unittest.TestCase):&lt;br /&gt;
 #    def __init__(self,asdf):&lt;br /&gt;
 #        self.driver = webdriver.Firefox() &lt;br /&gt;
 &lt;br /&gt;
     def setUp(self):&lt;br /&gt;
         print (&amp;quot;setUp&amp;quot;)&lt;br /&gt;
         # headless mode:&lt;br /&gt;
         # opts = Options()&lt;br /&gt;
         # opts.set_headless()&lt;br /&gt;
         # assert opts.headless  # Operating in headless mode&lt;br /&gt;
         # self.driver = webdriver.Firefox(options=opts)&lt;br /&gt;
 &lt;br /&gt;
         self.driver = webdriver.Firefox()&lt;br /&gt;
 &lt;br /&gt;
     def test_search_in_python_org(self):&lt;br /&gt;
         driver = self.driver&lt;br /&gt;
         driver.get(&amp;quot;http://www.python.org&amp;quot;)&lt;br /&gt;
         self.assertIn(&amp;quot;Python&amp;quot;, driver.title)&lt;br /&gt;
         elem = driver.find_element_by_name(&amp;quot;q&amp;quot;)&lt;br /&gt;
         elem.send_keys(&amp;quot;pycon&amp;quot;)&lt;br /&gt;
         elem.send_keys(Keys.RETURN)&lt;br /&gt;
         assert &amp;quot;No results found.&amp;quot; not in driver.page_source&lt;br /&gt;
         print (&amp;quot;fertig: python_org&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     def tearDown(self):&lt;br /&gt;
         print (&amp;quot;tearDown&amp;quot;)&lt;br /&gt;
         print (&amp;quot;close Firefox&amp;quot;)&lt;br /&gt;
         self.driver.close() # close tab&lt;br /&gt;
         self.driver.quit() # quit browser&lt;br /&gt;
         # os._exit(1) # exit unittest without Exception&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     try:&lt;br /&gt;
         unittest.main()&lt;br /&gt;
     except SystemExit as e:&lt;br /&gt;
         os._exit(1)&lt;br /&gt;
&lt;br /&gt;
==Cryptography and Hashing==&lt;br /&gt;
====Hashing via SHA256====&lt;br /&gt;
 def gen_SHA256_string(s: str) -&amp;gt; str:&lt;br /&gt;
     m = hashlib.sha256()&lt;br /&gt;
     m.update(s.encode(&amp;quot;ascii&amp;quot;))&lt;br /&gt;
     return m.hexdigest()&lt;br /&gt;
&lt;br /&gt;
====Hashing via MD5====&lt;br /&gt;
(MD5 is not secure, better use SHA256)&lt;br /&gt;
 def gen_MD5_string(s: str) -&amp;gt; str:&lt;br /&gt;
     m = hashlib.md5()&lt;br /&gt;
     m.update(s.encode(&amp;quot;ascii&amp;quot;))&lt;br /&gt;
     return m.hexdigest()&lt;br /&gt;
&lt;br /&gt;
====Password hashing via bcrypt====&lt;br /&gt;
 import bcrypt&lt;br /&gt;
 pwd = &#039;geheim&#039;&lt;br /&gt;
 pwd = pwd.encode(&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 # or &lt;br /&gt;
 pwd = b&#039;geheim&#039;&lt;br /&gt;
 &lt;br /&gt;
 hashed = bcrypt.hashpw(pwd, bcrypt.gensalt())&lt;br /&gt;
 if bcrypt.checkpw(pwd, hashed):&lt;br /&gt;
     print(&amp;quot;It Matches!&amp;quot;)&lt;br /&gt;
     print(hashed.decode(&amp;quot;utf-8&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
To use version 2a instead of 2b (default):&lt;br /&gt;
 bcrypt.gensalt(prefix=b&amp;quot;2a&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Multiprocessing, subprocesses and Threading==&lt;br /&gt;
see [[Python_-_Multithreading]] as well&lt;br /&gt;
&lt;br /&gt;
use processes for CPU limited work &amp;lt;br/&amp;gt;&lt;br /&gt;
use threads for I/O limited work&lt;br /&gt;
&lt;br /&gt;
===Simple single process===&lt;br /&gt;
 import subprocess&lt;br /&gt;
 process = subprocess.run([&amp;quot;sudo&amp;quot;, &amp;quot;du&amp;quot;, &amp;quot;--max-depth=1&amp;quot;, mydir], capture_output=True, text=True)&lt;br /&gt;
 print (process.stdout)&lt;br /&gt;
old, depricated way:&lt;br /&gt;
 os.system( &amp;quot;gnuplot &amp;quot; + gpfile)&lt;br /&gt;
&lt;br /&gt;
===Multiprocessing===&lt;br /&gt;
see [[Python - Multithreading]] as well&lt;br /&gt;
&lt;br /&gt;
V2 using pool and starmap&lt;br /&gt;
 import multiprocessing&lt;br /&gt;
 import os&lt;br /&gt;
 &lt;br /&gt;
 def worker(i: int, s: str) -&amp;gt; list:&lt;br /&gt;
     result = (i, s, os.getpid())&lt;br /&gt;
     return result&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     # gen. pile of work&lt;br /&gt;
     l_pile_of_work = []&lt;br /&gt;
     for i in range(1_000):&lt;br /&gt;
         tup = (i, &amp;quot;n&amp;quot; + str(i))&lt;br /&gt;
         l_pile_of_work.append((tup))&lt;br /&gt;
     # gen pool of processes&lt;br /&gt;
     num_processes = min(multiprocessing.cpu_count(), len(l_pile_of_work))&lt;br /&gt;
     pool = multiprocessing.Pool(processes=num_processes)&lt;br /&gt;
     # start processes on pile of work&lt;br /&gt;
     l_results_unsorted = pool.starmap(&lt;br /&gt;
         func=worker, iterable=l_pile_of_work  # each item is a list of 2 parameters&lt;br /&gt;
     )&lt;br /&gt;
     &lt;br /&gt;
     # or if only one parameter:&lt;br /&gt;
     # l_results_unsorted = pool.map(doit_de_district, l_pile_of_work)&lt;br /&gt;
     &lt;br /&gt;
     l_results = sorted(l_results_unsorted)  # sort by i&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
V1&lt;br /&gt;
 import subprocess&lt;br /&gt;
 l_subprocesses = []  # queue list of subprocesses&lt;br /&gt;
 max_processes = 4&lt;br /&gt;
 &lt;br /&gt;
 def process_enqueue(new_process_parameters):&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     # wait for free slot&lt;br /&gt;
     while len(l_subprocesses) &amp;gt;= max_processes:&lt;br /&gt;
         process_remove_finished_from_queue()&lt;br /&gt;
         time.sleep(0.1)  # sleep 0.1s&lt;br /&gt;
     process = subprocess.Popen(new_process_parameters,&lt;br /&gt;
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE,&lt;br /&gt;
                                universal_newlines=True)&lt;br /&gt;
     l_subprocesses.append(process)&lt;br /&gt;
 &lt;br /&gt;
 def process_remove_finished_from_queue():&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     i = 0&lt;br /&gt;
     while i &amp;lt;= len(l_subprocesses) - 1:&lt;br /&gt;
         process = l_subprocesses[i]&lt;br /&gt;
         if process.poll != None:  # has already finished&lt;br /&gt;
             process_print_output(process)&lt;br /&gt;
             l_subprocesses.pop(i)&lt;br /&gt;
         else:  # still running&lt;br /&gt;
             i += 1&lt;br /&gt;
 &lt;br /&gt;
 def process_print_output(process):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;waits for process to finish and prints process output&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     stdout, stderr = process.communicate()&lt;br /&gt;
     if stdout != &#039;&#039;:&lt;br /&gt;
         print(f&#039;Out: {stdout}&#039;)&lt;br /&gt;
     if stderr != &#039;&#039;:&lt;br /&gt;
         print(f&#039;ERROR: {stderr}&#039;)&lt;br /&gt;
 &lt;br /&gt;
 def process_wait_for_all_finished():&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     for process in l_subprocesses:&lt;br /&gt;
         process_print_output(process)&lt;br /&gt;
     l_subprocesses = []  # empty list of done subprocesses&lt;br /&gt;
 &lt;br /&gt;
 process_enqueue(l_parameters1)&lt;br /&gt;
 ...&lt;br /&gt;
 process_enqueue(l_parameters999)&lt;br /&gt;
 process_wait_for_all_finished()&lt;br /&gt;
&lt;br /&gt;
===Threading===&lt;br /&gt;
 import threading&lt;br /&gt;
 import queue&lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 def worker(q_work: queue.Queue, results: dict):&lt;br /&gt;
     while not q_work.empty():&lt;br /&gt;
         i, s = q_work.get()&lt;br /&gt;
         time.sleep(.1)&lt;br /&gt;
         result = (i, s, os.getpid())&lt;br /&gt;
         results[i] = result&lt;br /&gt;
         q_work.task_done()&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &#039;__main__&#039;:&lt;br /&gt;
     d_results = {}  # threads can write into dict&lt;br /&gt;
     # gen. pile of work&lt;br /&gt;
     l_pile_of_work = []&lt;br /&gt;
     for i in range(1_000):&lt;br /&gt;
         tup = (i, &amp;quot;n&amp;quot;+str(i))&lt;br /&gt;
         l_pile_of_work.append((tup))&lt;br /&gt;
     # convert list of work to queue&lt;br /&gt;
     q_pile_of_work = queue.Queue(&lt;br /&gt;
         maxsize=len(l_pile_of_work))  # maxsize=0 -&amp;gt; unlimited&lt;br /&gt;
     for params in l_pile_of_work:&lt;br /&gt;
         q_pile_of_work.put(params)&lt;br /&gt;
     # gen threads&lt;br /&gt;
     num_threads = 100&lt;br /&gt;
     l_threads = []  # List of threads, not used here&lt;br /&gt;
     for i in range(num_threads):&lt;br /&gt;
         t = threading.Thread(name=&#039;myThread-&#039;+str(i),&lt;br /&gt;
                              target=worker,&lt;br /&gt;
                              args=(q_pile_of_work, d_results),&lt;br /&gt;
                              daemon=True)&lt;br /&gt;
         l_threads.append(t)&lt;br /&gt;
         t.start()&lt;br /&gt;
     q_pile_of_work.join()  # wait for all threas to complete&lt;br /&gt;
     l_results_unsorted = d_results.values()&lt;br /&gt;
     l_results = sorted(l_results_unsorted)  # sort by i&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===asyncio — Asynchronous I/O===&lt;br /&gt;
see https://docs.python.org/3/library/asyncio-task.html&lt;br /&gt;
 import asyncio&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 # basics&lt;br /&gt;
 # task = asyncio.create_task(coro())&lt;br /&gt;
 # Wrap the coro coroutine into a Task and schedule its execution. Return the Task object.&lt;br /&gt;
 &lt;br /&gt;
 # sleep&lt;br /&gt;
 # await asyncio.sleep(1)&lt;br /&gt;
 &lt;br /&gt;
 # Running Tasks Concurrently and gathers the return values in list L&lt;br /&gt;
 # L = await asyncio.gather(coro(x1,y1), coro(x2,y2), coro(x3,y3))&lt;br /&gt;
 &lt;br /&gt;
 async def say_after(delay, what):&lt;br /&gt;
     # Coroutines declared with the async/await syntax&lt;br /&gt;
     await asyncio.sleep(delay)&lt;br /&gt;
     print(what)&lt;br /&gt;
 &lt;br /&gt;
 async def main():&lt;br /&gt;
     # The asyncio.create_task() function to run coroutines concurrently as asyncio Tasks.&lt;br /&gt;
     task1 = asyncio.create_task(&lt;br /&gt;
         say_after(1, &#039;hello&#039;))&lt;br /&gt;
 &lt;br /&gt;
     task2 = asyncio.create_task(&lt;br /&gt;
         say_after(1, &#039;world&#039;))&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;started at {time.strftime(&#039;%X&#039;)}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     # Wait until both tasks are completed&lt;br /&gt;
     await task1&lt;br /&gt;
     await task2&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;finished at {time.strftime(&#039;%X&#039;)}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 asyncio.run(main())&lt;br /&gt;
&lt;br /&gt;
==Pandas==&lt;br /&gt;
see [[Pandas]]&lt;br /&gt;
&lt;br /&gt;
==Matplotlib==&lt;br /&gt;
see [[Matplotlib]]&lt;br /&gt;
&lt;br /&gt;
== GUI via tkinter==&lt;br /&gt;
 import tkinter as tk  # no need to install via pip&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 class App(tk.Tk):&lt;br /&gt;
     def __init__(self):&lt;br /&gt;
         super().__init__()&lt;br /&gt;
         self.title(&amp;quot;robot-CClicker&amp;quot;)&lt;br /&gt;
         self.geometry(&amp;quot;200x200+0+1080&amp;quot;)&lt;br /&gt;
         self.resizable(width=False, height=False)&lt;br /&gt;
 &lt;br /&gt;
         self.l_buttons = []&lt;br /&gt;
         self.__create_widgets()&lt;br /&gt;
 &lt;br /&gt;
     def __create_widgets(self):&lt;br /&gt;
         self.btn_click500 = tk.Button(&lt;br /&gt;
             master=self,&lt;br /&gt;
             text=&amp;quot;500 clicks&amp;quot;,&lt;br /&gt;
             width=20,&lt;br /&gt;
             command=lambda: self.clickBigCookie(500),&lt;br /&gt;
         )&lt;br /&gt;
         self.l_buttons.append(self.btn_click500)&lt;br /&gt;
 &lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button.pack(anchor=tk.W)&lt;br /&gt;
 &lt;br /&gt;
     def clickBigCookie(self, num):&lt;br /&gt;
         # TODO: the disabling of the buttons is not working&lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button[&amp;quot;state&amp;quot;] = tk.DISABLED&lt;br /&gt;
         helper.clickIt(self.posBigCockie[0], self.posBigCockie[1], num=num)&lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button[&amp;quot;state&amp;quot;] = tk.NORMAL&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     app = App()&lt;br /&gt;
     app.mainloop()&lt;br /&gt;
&lt;br /&gt;
==Protobuf==&lt;br /&gt;
===convert .proto file to python class===&lt;br /&gt;
see https://www.datascienceblog.net/post/programming/essential-protobuf-guide-python/&lt;br /&gt;
 protoc my_message.proto --python_out ./ &lt;br /&gt;
&lt;br /&gt;
===read/decode protobuf message===&lt;br /&gt;
parse message from file&lt;br /&gt;
 import machine_message_pb2&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.bin&amp;quot;, &amp;quot;rb&amp;quot;) as f:&lt;br /&gt;
     my_message = my_message_pb2.my_message()&lt;br /&gt;
     my_message.ParseFromString(f.read())&lt;br /&gt;
 print(machine_message)&lt;br /&gt;
&lt;br /&gt;
===create/encode protobuf message===&lt;br /&gt;
 import machine_message_pb2&lt;br /&gt;
 &lt;br /&gt;
 my_message = my_message_pb2.my_message()&lt;br /&gt;
 my_message.data.field1 = 1&lt;br /&gt;
 my_message.data.field2 = &amp;quot;asdf&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.bin&amp;quot;, &amp;quot;wb&amp;quot;) as f:&lt;br /&gt;
     f.write(my_message.data.SerializeToString())&lt;br /&gt;
&lt;br /&gt;
==venv / virtual environments==&lt;br /&gt;
 pip install --upgrade pip&lt;br /&gt;
 python -m venv .venv --prompt $(basename $(pwd))&lt;br /&gt;
 source .venv/bin/activate&lt;br /&gt;
 pip install -r requirements.txt&lt;br /&gt;
 ...&lt;br /&gt;
 deactivate&lt;br /&gt;
&lt;br /&gt;
==Pydantic data validation==&lt;br /&gt;
===Function input parameter validation===&lt;br /&gt;
see https://docs.pydantic.dev/usage/validation_decorator/&lt;br /&gt;
 @validate_arguments&lt;br /&gt;
 def sum(x: int, y: float) -&amp;gt; float:&lt;br /&gt;
     print(x, y)&lt;br /&gt;
     return x + y&lt;br /&gt;
  &lt;br /&gt;
 print(sum(1.1, 1.1))&lt;br /&gt;
 # -&amp;gt; 2.1&lt;br /&gt;
&lt;br /&gt;
==Read config file==&lt;br /&gt;
===TOML===&lt;br /&gt;
see https://learnxinyminutes.com/docs/toml/&lt;br /&gt;
&lt;br /&gt;
minimal example:&lt;br /&gt;
config.toml&lt;br /&gt;
 # SAP API endpoint&lt;br /&gt;
 [general]&lt;br /&gt;
 sleep_time = 60&lt;br /&gt;
 use_proxy = true&lt;br /&gt;
tool.py&lt;br /&gt;
 try:&lt;br /&gt;
     import tomllib  # comes with python3.11&lt;br /&gt;
 except ModuleNotFoundError:&lt;br /&gt;
     import tomli as tomllib  # pip install tomli&lt;br /&gt;
 with open(&amp;quot;config.toml&amp;quot;, &amp;quot;rb&amp;quot;) as f:&lt;br /&gt;
    o = tomllib.load(f)  # type: ignore&lt;br /&gt;
 if o[&amp;quot;settings&amp;quot;][&amp;quot;use_proxy&amp;quot;]:&lt;br /&gt;
 ...&lt;br /&gt;
for validation, see https://realpython.com/python-toml/&lt;br /&gt;
&lt;br /&gt;
====TOML Write====&lt;br /&gt;
In for deployments I sometime want to modify a TOML config file for production, based on the dev version.&lt;br /&gt;
 import tomli_w  # pip install tomli-w&lt;br /&gt;
 p_in = Path(__file__).parent.parent / &amp;quot;.streamlit/config.toml&amp;quot;&lt;br /&gt;
 p_out = p_in.parent / &amp;quot;config-prod.toml&amp;quot; &lt;br /&gt;
 with p_in.open(&amp;quot;rb&amp;quot;) as fh:&lt;br /&gt;
     o = tomllib.load(fh)&lt;br /&gt;
 o[&amp;quot;server&amp;quot;][&amp;quot;fileWatcherType&amp;quot;] = &amp;quot;none&amp;quot;&lt;br /&gt;
 del o[&amp;quot;server&amp;quot;][&amp;quot;address&amp;quot;]&lt;br /&gt;
  with p_out.open(&amp;quot;wb&amp;quot;) as fh:&lt;br /&gt;
     tomli_w.dump(o, fh)&lt;br /&gt;
&lt;br /&gt;
===ConfigParser: INI Config File Reading===&lt;br /&gt;
better use TOML&lt;br /&gt;
&lt;br /&gt;
Config.ini&lt;br /&gt;
 [Section1]&lt;br /&gt;
 Cursor         = 205E18&lt;br /&gt;
 Grandma        =  18E18&lt;br /&gt;
 Farm           =  11E18&lt;br /&gt;
 Mine           = 514E18&lt;br /&gt;
 Factory        = 155E18&lt;br /&gt;
test.py&lt;br /&gt;
 from configparser import ConfigParser&lt;br /&gt;
 &lt;br /&gt;
 config = ConfigParser(&lt;br /&gt;
     interpolation=None&lt;br /&gt;
 )  # interpolation=None -&amp;gt; treats % in values as char % instead of interpreting it&lt;br /&gt;
 config.read(&amp;quot;Config.ini&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(config.getint(&amp;quot;Section1&amp;quot;, &amp;quot;key1&amp;quot;))&lt;br /&gt;
 print(config.getfloat(&amp;quot;Section1&amp;quot;, &amp;quot;key2&amp;quot;))&lt;br /&gt;
 print(config.get(&amp;quot;Section1&amp;quot;, &amp;quot;key3&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 for sec in config.sections():&lt;br /&gt;
     d_settings = {}&lt;br /&gt;
     for key in config.options(sec):&lt;br /&gt;
         value = config.get(sec, key)&lt;br /&gt;
         d_settings[key] = value&lt;br /&gt;
         print(&amp;quot;%15s : %s&amp;quot; % (key, value))&lt;br /&gt;
&lt;br /&gt;
==VCard / vcf==&lt;br /&gt;
 import codecs&lt;br /&gt;
 import vobject  # pip install vobject&lt;br /&gt;
 &lt;br /&gt;
 obj = vobject.readComponents(codecs.open(file_in, encoding=&amp;quot;utf-8&amp;quot;).read())  # type: ignore&lt;br /&gt;
 contacts: list[vobject.base.Component] = list(obj)  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 card = contacts[0]&lt;br /&gt;
 &lt;br /&gt;
 if &amp;quot;bday&amp;quot; not in card.contents:&lt;br /&gt;
     continue&lt;br /&gt;
 &lt;br /&gt;
 # bday: remove &#039;VALUE&#039;: [&#039;DATE&#039;]&lt;br /&gt;
 card.contents[&amp;quot;bday&amp;quot;][0].params = {}  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 # remove all fields but &amp;quot;bday&amp;quot;, &amp;quot;n&amp;quot;&lt;br /&gt;
 for key in card.contents.copy():  # loop over copy, to allow for deleting keys&lt;br /&gt;
     if key not in (&amp;quot;n&amp;quot;, &amp;quot;bday&amp;quot;):&lt;br /&gt;
         del card.contents[key]&lt;br /&gt;
 &lt;br /&gt;
 # recreate fn based on n&lt;br /&gt;
 card.add(&amp;quot;fn&amp;quot;)&lt;br /&gt;
 n = card.contents[&amp;quot;n&amp;quot;][0]&lt;br /&gt;
 fn = f&amp;quot;{n.value.given} {n.value.additional} {n.value.family}&amp;quot;  # type: ignore&lt;br /&gt;
 fn = re.sub(r&amp;quot;\s+&amp;quot;, &amp;quot; &amp;quot;, fn)&lt;br /&gt;
 card.fn.value = fn  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.vcf&amp;quot;, mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fhOut:&lt;br /&gt;
     fhOut.write(card.serialize())&lt;br /&gt;
&lt;br /&gt;
==Sentry Exception monitoring==&lt;br /&gt;
see [[Sentry]]&lt;br /&gt;
==MQTT==&lt;br /&gt;
 #!/usr/bin/env python3.12&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Read power data from Tasmota device via MQTT.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 import json&lt;br /&gt;
 from typing import Any&lt;br /&gt;
 &lt;br /&gt;
 import paho.mqtt.client as mqtt&lt;br /&gt;
 from mqtt_credentials import hostname, password, port, username&lt;br /&gt;
 from paho.mqtt.reasoncodes import ReasonCode&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_connect(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     flags: dict[str, int],&lt;br /&gt;
     reason_code: ReasonCode,&lt;br /&gt;
     properties: Any,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when the client connects MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     if reason_code == 0:&lt;br /&gt;
         print(&amp;quot;Connected to MQTT&amp;quot;)&lt;br /&gt;
         # print(f&amp;quot;MQTT protocol version: {mqtt_client._protocol}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         # Subscribing in on_connect() means that if we lose the connection and&lt;br /&gt;
         # reconnect then subscriptions will be renewed.&lt;br /&gt;
         mqtt_client.message_callback_add(&amp;quot;tele/tasmota_MT681/SENSOR&amp;quot;, on_message)&lt;br /&gt;
         mqtt_client.subscribe([(&amp;quot;tele/tasmota_MT681/SENSOR&amp;quot;, 0)])&lt;br /&gt;
 &lt;br /&gt;
     else:&lt;br /&gt;
         print(f&amp;quot;Connection to MQTT broker failed. Error: {reason_code}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_disconnect(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     reason_code: ReasonCode,&lt;br /&gt;
     properties: Any,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when the client is disconnected from the MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     if reason_code &amp;gt; 0:&lt;br /&gt;
         print(f&amp;quot;Unexpected disconnection. Error: {reason_code}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_message(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     message: mqtt.MQTTMessage,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when a Tasmota message is received from the MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     s = message.payload.decode()&lt;br /&gt;
     data = json.loads(s)&lt;br /&gt;
     # {&#039;Time&#039;: &#039;2024-03-16T06:44:08&#039;, &#039;MT681&#039;: {&#039;Total_in&#039;: 16.4396, &#039;Power_cur&#039;: 80, &#039;Total_out&#039;: 14.6403}}  # noqa: E501&lt;br /&gt;
 &lt;br /&gt;
     total_i = data[&amp;quot;MT681&amp;quot;][&amp;quot;Total_in&amp;quot;]&lt;br /&gt;
     total_o = data[&amp;quot;MT681&amp;quot;][&amp;quot;Total_out&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;In:\t{total_i}\nOut:\t{total_o}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     mqtt_client.disconnect()&lt;br /&gt;
     mqtt_client.loop_stop()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 try:&lt;br /&gt;
     # Create an MQTT client instance&lt;br /&gt;
     mqtt_client: mqtt.Client = mqtt.Client(&lt;br /&gt;
         mqtt.CallbackAPIVersion.VERSION2, &amp;quot;Python-Client-1&amp;quot;&lt;br /&gt;
     )  # type: ignore&lt;br /&gt;
     mqtt_client.username_pw_set(username, password)&lt;br /&gt;
 &lt;br /&gt;
     mqtt_client.on_connect = on_connect&lt;br /&gt;
 &lt;br /&gt;
     # Connect to the MQTT broker&lt;br /&gt;
     mqtt_client.connect(hostname, port, keepalive=60)&lt;br /&gt;
 &lt;br /&gt;
     # NOT: while True, as that eats the CPU !!!&lt;br /&gt;
     mqtt_client.loop_forever()&lt;br /&gt;
 except KeyboardInterrupt:&lt;br /&gt;
     print(&amp;quot;KeyboardInterrupt, disconnecting from MQTT broker.&amp;quot;)&lt;br /&gt;
     mqtt_client.disconnect()&lt;br /&gt;
     mqtt_client.loop_stop()&lt;br /&gt;
&lt;br /&gt;
==Caching Functions==&lt;br /&gt;
 from functools import lru_cache&lt;br /&gt;
 &lt;br /&gt;
 @lru_cache(maxsize=128)  # None equals to @cache declarator&lt;br /&gt;
 def fnc(n:int):&lt;br /&gt;
&lt;br /&gt;
==Performance/Resources==&lt;br /&gt;
===RAM/Memory consumption/bottleneck===&lt;br /&gt;
 import tracemalloc&lt;br /&gt;
 tracemalloc.start()&lt;br /&gt;
 &lt;br /&gt;
 # ...&lt;br /&gt;
 # Code&lt;br /&gt;
 # example:&lt;br /&gt;
 lst = list(range(1_000_000))&lt;br /&gt;
 # ...&lt;br /&gt;
 &lt;br /&gt;
 max_bytes = tracemalloc.get_traced_memory()[0]&lt;br /&gt;
 print(f&amp;quot;{round(max_bytes / 10**6)}MB&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 tracemalloc.stop()&lt;br /&gt;
&lt;br /&gt;
===Profiling / Count and Runtime per Function===&lt;br /&gt;
 call_stats = {}&lt;br /&gt;
 &lt;br /&gt;
 def track_function_usage(func: Callable) -&amp;gt; Callable:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Annotation for gathering runtime statistics.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     @wraps(func)&lt;br /&gt;
     def wrapper(*args: tuple, **kwargs: dict):  # noqa: ANN202&lt;br /&gt;
         start_time = time.time()&lt;br /&gt;
         result = func(*args, **kwargs)  # Call the original function&lt;br /&gt;
         end_time = time.time()&lt;br /&gt;
         call_stats[func.__name__][&amp;quot;calls&amp;quot;] += 1&lt;br /&gt;
         call_stats[func.__name__][&amp;quot;total_time&amp;quot;] += end_time - start_time&lt;br /&gt;
         return result&lt;br /&gt;
     return wrapper&lt;br /&gt;
&lt;br /&gt;
==JWT Token==&lt;br /&gt;
  try:&lt;br /&gt;
      decoded_token = jwt.decode(token, options={&amp;quot;verify_signature&amp;quot;: False})&lt;br /&gt;
  except jwt.ExpiredSignatureError:&lt;br /&gt;
      msg = &amp;quot;Token has expired.&amp;quot;&lt;br /&gt;
      logger.exception(msg)&lt;br /&gt;
  except jwt.InvalidTokenError:&lt;br /&gt;
      msg = &amp;quot;Invalid token.&amp;quot;&lt;br /&gt;
      logger.exception(msg)&lt;br /&gt;
&lt;br /&gt;
==Streamlit==&lt;br /&gt;
see https://github.com/entorb/streamlit-examples for a collection of my examples&lt;br /&gt;
&lt;br /&gt;
===Minimum Example===&lt;br /&gt;
 # src/app.py&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import streamlit as st&lt;br /&gt;
 from streamlit.logger import get_logger&lt;br /&gt;
 logger = get_logger(Path(__file__).stem)&lt;br /&gt;
 logger.info(&amp;quot;Start&amp;quot;)&lt;br /&gt;
 st.set_page_config(page_title=&amp;quot;AppTitle&amp;quot;, page_icon=None, layout=&amp;quot;wide&amp;quot;)&lt;br /&gt;
 st.title(&amp;quot;MyPageTitle&amp;quot;)&lt;br /&gt;
 st.header(&amp;quot;MyHeader&amp;quot;)&lt;br /&gt;
 st.subheader(&amp;quot;MySubHeader&amp;quot;)&lt;br /&gt;
 sel_param = st.selectbox(&amp;quot;Parameter&amp;quot;, (&amp;quot;count&amp;quot;, &amp;quot;sum&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
 # .streamlit/config.toml&lt;br /&gt;
 [browser]&lt;br /&gt;
 gatherUsageStats = false&lt;br /&gt;
 &lt;br /&gt;
 [server]&lt;br /&gt;
 port = 8501&lt;br /&gt;
&lt;br /&gt;
 # run it&lt;br /&gt;
 streamlit run src/app.py&lt;br /&gt;
&lt;br /&gt;
====Reduce Logging for K8s Deployed Docker Containers====&lt;br /&gt;
 # reduce log output&lt;br /&gt;
 for logger_name in [&lt;br /&gt;
     &amp;quot;streamlit.runtime.caching.cache_utils&amp;quot;,&lt;br /&gt;
     &amp;quot;streamlit.runtime.caching.storage.in_memory_cache_storage_wrapper&amp;quot;,&lt;br /&gt;
     &amp;quot;streamlit.runtime.runtime&amp;quot;,&lt;br /&gt;
     &amp;quot;urllib3.connectionpool&amp;quot;,&lt;br /&gt;
 ]:&lt;br /&gt;
     get_logger(logger_name).setLevel(logging.WARNING)&lt;br /&gt;
&lt;br /&gt;
===Chart via Altair===&lt;br /&gt;
====Line Chart====&lt;br /&gt;
 chart = st.line_chart(df[&amp;quot;value&amp;quot;])&lt;br /&gt;
 # corresponds to&lt;br /&gt;
 import altair as alt&lt;br /&gt;
 c = (&lt;br /&gt;
    alt.Chart(df)&lt;br /&gt;
    .mark_line()&lt;br /&gt;
    .encode(&lt;br /&gt;
        x=alt.X(&amp;quot;created&amp;quot;, title=None),&lt;br /&gt;
        y=alt.Y(&amp;quot;value&amp;quot;, title=None),&lt;br /&gt;
    )&lt;br /&gt;
 )&lt;br /&gt;
 st.altair_chart(c, use_container_width=True)&lt;br /&gt;
&lt;br /&gt;
====Bar Chart====&lt;br /&gt;
 chart = (&lt;br /&gt;
     alt.Chart(df)&lt;br /&gt;
     .mark_bar()&lt;br /&gt;
     .encode(&lt;br /&gt;
         x=alt.X(&amp;quot;yearmonth(date):T&amp;quot;, title=&amp;quot;Month&amp;quot;),&lt;br /&gt;
         y=alt.Y(f&amp;quot;count:Q&amp;quot;, title=None),&lt;br /&gt;
         color=&amp;quot;status:N&amp;quot;,&lt;br /&gt;
         tooltip=[&amp;quot;yearmonth(date):T&amp;quot;, &amp;quot;status:N&amp;quot;, &amp;quot;cnt:Q&amp;quot;],&lt;br /&gt;
     )&lt;br /&gt;
     # .properties(width=600, height=400)&lt;br /&gt;
 )&lt;br /&gt;
 st.altair_chart(chart, use_container_width=True)&lt;br /&gt;
&lt;br /&gt;
 :T stands for Temporal. It indicates that the field contains date or time values.&lt;br /&gt;
 :N stands for Nominal. It indicates that the field contains categorical data, which represents discrete categories or labels.&lt;br /&gt;
 :Q stands for Quantitative. It indicates that the field contains numerical data that can be measured and compared.&lt;br /&gt;
&lt;br /&gt;
===Excel Download===&lt;br /&gt;
 def excel_download_buttons(df: pd.DataFrame, file_name: str = &amp;quot;export.xlsx&amp;quot;) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Show prepare data and download buttons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if st.button(label=&amp;quot;Click to prepare download&amp;quot;):&lt;br /&gt;
         buffer = io.BytesIO()&lt;br /&gt;
         with pd.ExcelWriter(buffer, engine=&amp;quot;xlsxwriter&amp;quot;) as writer:&lt;br /&gt;
             df.to_excel(writer, sheet_name=&amp;quot;Sheet1&amp;quot;, index=False)&lt;br /&gt;
             writer.close()&lt;br /&gt;
 &lt;br /&gt;
             st.download_button(&lt;br /&gt;
                 label=&amp;quot;Download data as Excel&amp;quot;,&lt;br /&gt;
                 data=buffer,&lt;br /&gt;
                 file_name=file_name,&lt;br /&gt;
                 mime=&amp;quot;application/vnd.ms-excel&amp;quot;,&lt;br /&gt;
             )&lt;br /&gt;
&lt;br /&gt;
==UV Package Manager==&lt;br /&gt;
 # create/update lockfile&lt;br /&gt;
 uv lock --upgrade&lt;br /&gt;
 # sync venv&lt;br /&gt;
 uv sync --upgrade&lt;br /&gt;
 # extract package info to requirements.txt&lt;br /&gt;
 uv export --format requirements.txt --no-dev --no-hashes -o requirements.txt&lt;br /&gt;
&lt;br /&gt;
Update Python version for my current project&lt;br /&gt;
 uv venv --python=3.13.7&lt;br /&gt;
&lt;br /&gt;
===Update UV===&lt;br /&gt;
 uv self update&lt;br /&gt;
 # or&lt;br /&gt;
 brew update &amp;amp;&amp;amp; brew upgrade uv&lt;br /&gt;
&lt;br /&gt;
===Update Python Versions===&lt;br /&gt;
 uv python upgrade&lt;br /&gt;
&lt;br /&gt;
===UV + Docker on readOnlyRootFilesystem===&lt;br /&gt;
Example 1: https://github.com/SWE-Group-23/CM22007---Source/blob/10d02004f3a4208f7b30d37a0a87e89532a23575/create-service.sh#L117&lt;br /&gt;
&lt;br /&gt;
Dockerfile&lt;br /&gt;
 WORKDIR /app&lt;br /&gt;
 COPY . .&lt;br /&gt;
 &lt;br /&gt;
 ENV CASS_DRIVER_NO_EXTENSIONS=1&lt;br /&gt;
 &lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache/uv \\&lt;br /&gt;
     --mount=type=bind,source=uv.lock,target=uv.lock \\&lt;br /&gt;
     --mount=type=bind,source=pyproject.toml,target=pyproject.toml \\&lt;br /&gt;
     uv sync --frozen --no-install-project&lt;br /&gt;
     &lt;br /&gt;
 RUN uv pip install -e shared/&lt;br /&gt;
 &lt;br /&gt;
 RUN addgroup -S group1 &amp;amp;&amp;amp; adduser -S user1 -G group1 -u 1000&lt;br /&gt;
 RUN chown -R user1:group1 /app&lt;br /&gt;
 USER user1:group1&lt;br /&gt;
 &lt;br /&gt;
 CMD [&amp;quot;uv&amp;quot;, &amp;quot;run&amp;quot;, &amp;quot;main.py&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
Deployment&lt;br /&gt;
 securityContext:&lt;br /&gt;
   readOnlyRootFilesystem: true&lt;br /&gt;
   allowPrivilegeEscalation: false&lt;br /&gt;
   runAsNonRoot: true&lt;br /&gt;
   runAsUser: 1000&lt;br /&gt;
   capabilities:&lt;br /&gt;
     drop:&lt;br /&gt;
       - ALL&lt;br /&gt;
   seccompProfile:&lt;br /&gt;
     type: RuntimeDefault&lt;br /&gt;
 volumeMounts:&lt;br /&gt;
   - mountPath: /home/user1/.cache/uv&lt;br /&gt;
     name: uv&lt;br /&gt;
   - mountPath: /tmp&lt;br /&gt;
     name: temp&lt;br /&gt;
&lt;br /&gt;
Example 2: [https://hynek.me/articles/docker-uv/ Production-ready Python Docker Containers with uv]&lt;br /&gt;
&lt;br /&gt;
Dockerfile (without the comments)&lt;br /&gt;
 ENV UV_LINK_MODE=copy \&lt;br /&gt;
     UV_COMPILE_BYTECODE=1 \&lt;br /&gt;
     UV_PYTHON_DOWNLOADS=never \&lt;br /&gt;
     UV_PYTHON=python3.12 \&lt;br /&gt;
     UV_PROJECT_ENVIRONMENT=/app&lt;br /&gt;
 &lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache \&lt;br /&gt;
     --mount=type=bind,source=uv.lock,target=uv.lock \&lt;br /&gt;
     --mount=type=bind,source=pyproject.toml,target=pyproject.toml \&lt;br /&gt;
     uv sync \&lt;br /&gt;
         --locked \&lt;br /&gt;
         --no-dev \&lt;br /&gt;
         --no-install-project&lt;br /&gt;
 &lt;br /&gt;
 COPY . /src&lt;br /&gt;
 WORKDIR /src&lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache \&lt;br /&gt;
     uv sync \&lt;br /&gt;
         --locked \&lt;br /&gt;
         --no-dev \&lt;br /&gt;
         --no-editable&lt;br /&gt;
&lt;br /&gt;
== Vulture: Search for dead code ==&lt;br /&gt;
see [https://github.com/jendrikseipp/vulture GitHub]&lt;br /&gt;
&lt;br /&gt;
pyproject.toml&lt;br /&gt;
 [tool.vulture]&lt;br /&gt;
 paths = [&amp;quot;src&amp;quot;]&lt;br /&gt;
 # exclude = [&amp;quot;src/models.py&amp;quot;]&lt;br /&gt;
 ignore_names = [&lt;br /&gt;
     &amp;quot;LOGGER&amp;quot;,&lt;br /&gt;
 ]&lt;br /&gt;
&lt;br /&gt;
.pre-commit-config.yaml&lt;br /&gt;
   # Vulture: find dead code&lt;br /&gt;
   - repo: https://github.com/jendrikseipp/vulture&lt;br /&gt;
     rev: v2.16&lt;br /&gt;
     hooks:&lt;br /&gt;
       - id: vulture&lt;br /&gt;
&lt;br /&gt;
run&lt;br /&gt;
 pip install vulture&lt;br /&gt;
 vulture src/&lt;br /&gt;
 &lt;br /&gt;
 uv add --dev vulture&lt;br /&gt;
 uv run vulture src/&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Python&amp;diff=5406</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5406"/>
		<updated>2026-04-12T12:31:06Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Vulture: Search for dead code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Coding]][[Category:Python]]&lt;br /&gt;
&lt;br /&gt;
==Getting Started==&lt;br /&gt;
===Install===&lt;br /&gt;
====Python====&lt;br /&gt;
Best use [https://docs.astral.sh/uv/ UV] for managing python and package versions.&lt;br /&gt;
&lt;br /&gt;
* for Windows: get and install Python from https://www.python.org&lt;br /&gt;
* for MacOS: use [https://github.com/pyenv/pyenv?tab=readme-ov-file#a-getting-pyenv pyenv]&lt;br /&gt;
 brew install xz &lt;br /&gt;
 brew install pyenv&lt;br /&gt;
 &lt;br /&gt;
 echo &#039;export PYENV_ROOT=&amp;quot;$HOME/.pyenv&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 echo &#039;[ [ -d $PYENV_ROOT/bin ] ] &amp;amp;&amp;amp; export PATH=&amp;quot;$PYENV_ROOT/bin:$PATH&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 echo &#039;eval &amp;quot;$(pyenv init - zsh)&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 exec &amp;quot;$SHELL&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 pyenv install --list&lt;br /&gt;
 pyenv install 3.13.5&lt;br /&gt;
 pyenv global 3.13.5&lt;br /&gt;
 &lt;br /&gt;
 vim ~/.zshrc &lt;br /&gt;
 # add &lt;br /&gt;
 if command -v pyenv 1&amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then&lt;br /&gt;
   eval &amp;quot;$(pyenv init -)&amp;quot;&lt;br /&gt;
 fi&lt;br /&gt;
&lt;br /&gt;
====Editor: Visual Studio Code====&lt;br /&gt;
excellent and free source-code editor that supports many languages.&lt;br /&gt;
* get and install from https://code.visualstudio.com&lt;br /&gt;
&lt;br /&gt;
See Wickie page [[Visual_Studio_Code]] for general setup, extension, config...&lt;br /&gt;
&lt;br /&gt;
Python Extensions&lt;br /&gt;
* Python&lt;br /&gt;
* Pylance&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff Ruff Formater and Linter]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items/?itemName=tamasfe.even-better-toml Even Better TOML]&lt;br /&gt;
Settings (CTRL + ,)&lt;br /&gt;
* Extensions -&amp;gt; Python -&amp;gt; Formatting: Provider = black&lt;br /&gt;
* Text Editor -&amp;gt; Editor: Format On Save&lt;br /&gt;
* Text Editor -&amp;gt; Files:Eol -&amp;gt; \n&lt;br /&gt;
* Linter: Ruff&lt;br /&gt;
Settings in settings.json&lt;br /&gt;
 &amp;quot;python.analysis.completeFunctionParens&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.autoImportCompletions&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.inlayHints.functionReturnTypes&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.typeCheckingMode&amp;quot;: &amp;quot;strict&amp;quot;,&lt;br /&gt;
 // Ruff&lt;br /&gt;
 &amp;quot;[python]&amp;quot;: {&lt;br /&gt;
   &amp;quot;editor.defaultFormatter&amp;quot;: &amp;quot;charliermarsh.ruff&amp;quot;,&lt;br /&gt;
   &amp;quot;editor.formatOnSave&amp;quot;: true,&lt;br /&gt;
   &amp;quot;editor.codeActionsOnSave&amp;quot;: {&lt;br /&gt;
     &amp;quot;source.fixAll&amp;quot;: &amp;quot;explicit&amp;quot;,&lt;br /&gt;
     &amp;quot;source.organizeImports.ruff&amp;quot;: &amp;quot;explicit&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 },&lt;br /&gt;
&lt;br /&gt;
Run your code&lt;br /&gt;
 CTRL + F5 : run&lt;br /&gt;
 F5        : run in debugger&lt;br /&gt;
&lt;br /&gt;
====Code Formatter Ruff====&lt;br /&gt;
use software for handling the code formatting like &amp;quot;ruff&amp;quot; or &amp;quot;black&amp;quot;, where Ruff is much faster and additionally provides linting.&lt;br /&gt;
 pip install ruff&lt;br /&gt;
than activate in editor like vs code&lt;br /&gt;
&lt;br /&gt;
===My Template===&lt;br /&gt;
see latest version at https://github.com/entorb/template-python&lt;br /&gt;
&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Main file.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 # by Torben Menke https://entorb.net &lt;br /&gt;
 import logging&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 def init_logging() -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Initialize logging.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     logging.addLevelName(logging.DEBUG, &amp;quot;D&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.INFO, &amp;quot;I&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.WARNING, &amp;quot;W&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.ERROR, &amp;quot;E&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.CRITICAL, &amp;quot;C&amp;quot;)&lt;br /&gt;
     logging.basicConfig(&lt;br /&gt;
         level=logging.INFO, format=&amp;quot;%(levelname)s: %(name)s: %(message)s&amp;quot;&lt;br /&gt;
     )&lt;br /&gt;
     logging.getLogger(&amp;quot;httpx&amp;quot;).setLevel(logging.WARNING)&lt;br /&gt;
 &lt;br /&gt;
 init_logging()&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
 &lt;br /&gt;
 def main():&lt;br /&gt;
     LOGGER.info(&amp;quot;Moin Moin&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     main()&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Other file&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 import logging&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
&lt;br /&gt;
===Compile to .exe===&lt;br /&gt;
 pip install pyinstaller&lt;br /&gt;
 pyinstaller --onefile --console your.py&lt;br /&gt;
 # for Excel and Matplotlib these options are required&lt;br /&gt;
 --hidden-import=openpyxl --hidden-import=matplotlib --hidden-import pandas.plotting._matplotlib &lt;br /&gt;
([[Python - py2exe]] is deprecated)&lt;br /&gt;
&lt;br /&gt;
==Basics==&lt;br /&gt;
=== Naming Conventions===&lt;br /&gt;
[https://google.github.io/styleguide/pyguide.html Google Python Style Guide]:&lt;br /&gt;
 module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_CONSTANT_NAME, global_var_name, instance_var_name, function_parameter_name, local_var_name&lt;br /&gt;
&lt;br /&gt;
====Doc Strings====&lt;br /&gt;
It is best practice to start a file with a docstring:&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;My Script&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
This can be accessed linke this:&lt;br /&gt;
 title = __doc__[:-1]  # type: ignore&lt;br /&gt;
&lt;br /&gt;
===Installing packages===&lt;br /&gt;
 python -m pip install --upgrade pip&lt;br /&gt;
 &lt;br /&gt;
 pip install somemodule&lt;br /&gt;
 # or &lt;br /&gt;
 pip3 install somemodule&lt;br /&gt;
 # or read from file&lt;br /&gt;
 pip install -r requirements.txt&lt;br /&gt;
 &lt;br /&gt;
 # uninstall&lt;br /&gt;
 pip uninstall somemodule&lt;br /&gt;
 &lt;br /&gt;
 # using a web proxy&lt;br /&gt;
 # set proxy for windows cmd session&lt;br /&gt;
 SET HTTPS_PROXY=http://myProxy:8080&lt;br /&gt;
 (afterwards --proxy setting below no longer required&lt;br /&gt;
 or&lt;br /&gt;
 pip install --proxy http://myProxy:8080 somemodule&lt;br /&gt;
 &lt;br /&gt;
 # list outdated packages&lt;br /&gt;
 pip list --outdated&lt;br /&gt;
 &lt;br /&gt;
 # update package&lt;br /&gt;
 pip install --upgrade pyinstaller&lt;br /&gt;
 &lt;br /&gt;
 # updating all via Windows Powershell (from [https://thecesrom.dev/2021/05/26/the-one-liner-for-updating-pip-and-all-outdated-packages/])&lt;br /&gt;
 pip freeze | %{$_.split(&#039;==&#039;)[0]} | %{pip install --upgrade $_}&lt;br /&gt;
 &lt;br /&gt;
 # updating all via Bash (from [https://thecesrom.dev/2021/05/26/the-one-liner-for-updating-pip-and-all-outdated-packages/])&lt;br /&gt;
 pip freeze | grep -v &#039;^\-e&#039; | cut -d = -f 1  | xargs -n1 pip install --upgrade&lt;br /&gt;
 &lt;br /&gt;
 # downgrade&lt;br /&gt;
 pip install --upgrade pandas==1.2.4&lt;br /&gt;
&lt;br /&gt;
====Oneline Updater for requirements.txt====&lt;br /&gt;
https://pkgui.com/pip&lt;br /&gt;
&lt;br /&gt;
====update packages====&lt;br /&gt;
 pip install pip-review&lt;br /&gt;
 pip-review --auto&lt;br /&gt;
&lt;br /&gt;
====generate requirements.txt====&lt;br /&gt;
 pip install pipreqs&lt;br /&gt;
 pipreqs ./&lt;br /&gt;
&lt;br /&gt;
===Loops===&lt;br /&gt;
ATTENTION: The loops do not create a new variable scope. Only functions and modules introduce a new scope!&lt;br /&gt;
 for p in all_files:&lt;br /&gt;
     print(p)&lt;br /&gt;
 &lt;br /&gt;
 for i, p in enumerate(all_files):&lt;br /&gt;
     print(i, p)&lt;br /&gt;
 &lt;br /&gt;
 for i in range(10):&lt;br /&gt;
     print(i)&lt;br /&gt;
 # del i &lt;br /&gt;
 &lt;br /&gt;
 while i &amp;lt;= 100:&lt;br /&gt;
     i += 1&lt;br /&gt;
     ...&lt;br /&gt;
     if sth1:&lt;br /&gt;
         continue # start next loop&lt;br /&gt;
     if sth2:&lt;br /&gt;
         break # exit loop&lt;br /&gt;
 &lt;br /&gt;
 # inline if (requires a dummy else):&lt;br /&gt;
 print(&amp;quot;something&amp;quot;) if sth else 0&lt;br /&gt;
&lt;br /&gt;
===Math===&lt;br /&gt;
see [[Python - Math]] for linear regression&lt;br /&gt;
&lt;br /&gt;
Modulo&lt;br /&gt;
 15 % 4&lt;br /&gt;
 # &amp;gt; 3&lt;br /&gt;
&lt;br /&gt;
Integer Division&lt;br /&gt;
 17 // 4&lt;br /&gt;
 # &amp;gt; 4&lt;br /&gt;
&lt;br /&gt;
====Random====&lt;br /&gt;
 import random&lt;br /&gt;
 random.randint(1000000, 9999999)&lt;br /&gt;
&lt;br /&gt;
==Basic Objects==&lt;br /&gt;
===Variables===&lt;br /&gt;
 del var  # delete / undef a variable&lt;br /&gt;
 var = None  # sets to null&lt;br /&gt;
 &lt;br /&gt;
 # check if variable is defined&lt;br /&gt;
 if &amp;quot;var&amp;quot; in locals():&lt;br /&gt;
     pass&lt;br /&gt;
 # for object oriented projects:&lt;br /&gt;
 if &amp;quot;var&amp;quot; in self.__dict__:&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # Access global variables in functions&lt;br /&gt;
 var = 123&lt;br /&gt;
 &lt;br /&gt;
 def test() -&amp;gt; None:&lt;br /&gt;
     global var  # point to global instead of creation of local var&lt;br /&gt;
     var = 321&lt;br /&gt;
&lt;br /&gt;
===Strings===&lt;br /&gt;
 # num &amp;lt;-&amp;gt; str&lt;br /&gt;
 s = str(i)  # int to string&lt;br /&gt;
 f = float(s)  # str -&amp;gt; float&lt;br /&gt;
 i = int(s)&lt;br /&gt;
 str(round(f, 1))  # round first&lt;br /&gt;
 # tests&lt;br /&gt;
 s.isdigit()  # 0-9&lt;br /&gt;
 # note isdecimal() does also not match &#039;1.1&#039;&lt;br /&gt;
 &lt;br /&gt;
 # printf: 1 digit&lt;br /&gt;
 s = f&amp;quot;{value:0.1f}&amp;quot;&lt;br /&gt;
 s = &amp;quot;%0.1f&amp;quot; % value&lt;br /&gt;
&lt;br /&gt;
====Modify Strings==== &lt;br /&gt;
 # get string from prompt&lt;br /&gt;
 s = input(&amp;quot;Enter Text: &amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 s = s.strip()  # trim spaces from both sides, rstrip for right only&lt;br /&gt;
 s = s.lower()  # lower case&lt;br /&gt;
 s = s.upper()  # upper case&lt;br /&gt;
 s = s.title()  # upper case for first char of word&lt;br /&gt;
 &lt;br /&gt;
 # upper case first letter of each word and also removes multiple and trailing spaces&lt;br /&gt;
 import string&lt;br /&gt;
 s = string.capwords(s)&lt;br /&gt;
 &lt;br /&gt;
 # replace&lt;br /&gt;
 s.replace(x, y)&lt;br /&gt;
 &lt;br /&gt;
 # trim whitespaces from left and right&lt;br /&gt;
 s.strip()&lt;br /&gt;
 &lt;br /&gt;
 # replace all (multiple) whitespaces by single space &#039; &#039;&lt;br /&gt;
 s = &amp;quot; &amp;quot;.join(s.split())&lt;br /&gt;
 &lt;br /&gt;
 # generate key value pairs from dict&lt;br /&gt;
 # key1=value1&amp;amp;key2=value2&lt;br /&gt;
 param_str = &amp;quot;&amp;amp;&amp;quot;.join(&amp;quot;=&amp;quot;.join(tup) for tup in dict.items())&lt;br /&gt;
 &lt;br /&gt;
 # repeat string multiple times&lt;br /&gt;
 s * 5  # = s+s+s+s+s&lt;br /&gt;
&lt;br /&gt;
====Substrings====&lt;br /&gt;
 # find a substring:&lt;br /&gt;
 if x in s:  # True / False&lt;br /&gt;
     pass&lt;br /&gt;
 if len(s) &amp;gt; 0:&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # handling substrings&lt;br /&gt;
 a = &amp;quot;abcd&amp;quot;&lt;br /&gt;
 b = a[:1] + &amp;quot;o&amp;quot; + a[2:]&lt;br /&gt;
 # &amp;gt; &#039;aocd&#039;&lt;br /&gt;
 &lt;br /&gt;
 s = &amp;quot;Hello there !bob@&amp;quot;&lt;br /&gt;
 i1 = s.find(&amp;quot;!&amp;quot;) + 1&lt;br /&gt;
 i2 = s.find(&amp;quot;@&amp;quot;)&lt;br /&gt;
 substr = s[i1:i2]&lt;br /&gt;
 &lt;br /&gt;
 def substr_between(s: str, s1: str, s2: str) -&amp;gt; str:&lt;br /&gt;
     assert s1 in s, f&amp;quot;E: can&#039;t find &#039;{s1}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     assert s2 in s, f&amp;quot;E: can&#039;t find &#039;{s1}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     i1 = s.find(s1) + len(s1)&lt;br /&gt;
     i2 = s.find(s2)&lt;br /&gt;
     assert i1 &amp;lt; i2, f&amp;quot;E: &#039;{s1}&#039; not before &#039;{s2}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     return s[i1:i2]&lt;br /&gt;
&lt;br /&gt;
====Binary, raw strings, html encoding====&lt;br /&gt;
 # Binary Strings&lt;br /&gt;
 sb = b&amp;quot;asdf&amp;quot;&lt;br /&gt;
 # or&lt;br /&gt;
 sb = str.encode(&amp;quot;asdf&amp;quot;)&lt;br /&gt;
 s = sb.decode(&amp;quot;ascii&amp;quot;)  # decode binary strings&lt;br /&gt;
 sb = s.encode(&amp;quot;ascii&amp;quot;)  # encode string to binary&lt;br /&gt;
 &lt;br /&gt;
 # raw string&lt;br /&gt;
 s = r&amp;quot;c:\Windows&amp;quot;  # no escape of \  needed&lt;br /&gt;
 &lt;br /&gt;
 # convert utf-8 to html umlaute&lt;br /&gt;
 s = &amp;quot;Nürnberg&amp;quot;.encode(&amp;quot;ascii&amp;quot;, &amp;quot;xmlcharrefreplace&amp;quot;).decode()&lt;br /&gt;
 # -&amp;gt; N&amp;amp;#252;rnberg&lt;br /&gt;
&lt;br /&gt;
====Guess encoding via chardet====&lt;br /&gt;
 import chardet  # pip install chardet&lt;br /&gt;
 result = chardet.detect(raw_data)&lt;br /&gt;
 if result[&amp;quot;encoding&amp;quot;] and result[&amp;quot;confidence&amp;quot;] &amp;gt; 0.5:  # noqa: PLR2004&lt;br /&gt;
     encoding = result[&amp;quot;encoding&amp;quot;]&lt;br /&gt;
 else:&lt;br /&gt;
     encoding = &amp;quot;utf-8&amp;quot;&lt;br /&gt;
&lt;br /&gt;
there is a much faster alternative [https://github.com/PyYoshi/cChardet cchardet], but that requires Microsoft Visual C++ 14.0&lt;br /&gt;
&lt;br /&gt;
====Merge variables in string / sprintf====&lt;br /&gt;
 print(&amp;quot;Renner =&amp;quot;, i)&lt;br /&gt;
 print(&amp;quot;Renner = %3d&amp;quot; % i)  # leading 0&#039;s&lt;br /&gt;
 print(f&amp;quot;Renner = {i}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # place formatted numbers in a string / sprintf&lt;br /&gt;
 s = &amp;quot;The {:.1f}% {} cost {:05.2f} euros&amp;quot;.format( 5.1, &amp;quot;beer&amp;quot;, 3.50)&lt;br /&gt;
 print(s)&lt;br /&gt;
 # &amp;gt; The 5.1% beer cost 03.50 euros&lt;br /&gt;
 &lt;br /&gt;
 s = f&amp;quot;The length is {72.8958:.2f} meters&amp;quot;&lt;br /&gt;
 # &amp;gt; The length is 72.90 meters&lt;br /&gt;
&lt;br /&gt;
===Lists===&lt;br /&gt;
like @array in Perl&lt;br /&gt;
 lst = [1, 2, 3, 4, 5, 6]&lt;br /&gt;
 lst = [x / 2 for x in range(10)]&lt;br /&gt;
 lst = [None] * 10 # Initiate list of None elements:&lt;br /&gt;
 len(lst) # length&lt;br /&gt;
 lst2 = lst[0:10]  # get elements 0-10&lt;br /&gt;
 for i in lst:&lt;br /&gt;
     print(i)&lt;br /&gt;
&lt;br /&gt;
====clone list====&lt;br /&gt;
 # dangerous: creates a link to the original list&lt;br /&gt;
 list_2 = my_list  # M&#039;s elements are LINKS to L&#039;s&lt;br /&gt;
 # clones can be achieved via:&lt;br /&gt;
 list_2 = my_list.copy&lt;br /&gt;
 list_2 = my_list[:]&lt;br /&gt;
 list_2 = list(my_list)&lt;br /&gt;
&lt;br /&gt;
====list modifications====&lt;br /&gt;
 lst.pop()  # returns and removes the last item&lt;br /&gt;
 lst.pop(i)  # returns and removes the item at  position int i&lt;br /&gt;
 lst.insert(i, x)  # insert item x at position int i&lt;br /&gt;
 lst.remove(x)  # removes the first occurrence of  item x&lt;br /&gt;
 lst.append(x)  # append a single element&lt;br /&gt;
 lst.extend(m)  # append elements of another list&lt;br /&gt;
 &lt;br /&gt;
 lst.reverse()  # reverse the order of the list&lt;br /&gt;
 lst = sorted([&amp;quot;B&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;c&amp;quot;], key=str.casefold)  #  case insensitive / ignore case&lt;br /&gt;
 &lt;br /&gt;
 # list to string&lt;br /&gt;
 s = &amp;quot;&amp;quot;.join(lst)&lt;br /&gt;
 s = &amp;quot;\n&amp;quot;.join(lst)&lt;br /&gt;
 # string to list&lt;br /&gt;
 lst = s.split()  # default: split on space&lt;br /&gt;
 lst = s.split(&amp;quot;,&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # remove empty values from end&lt;br /&gt;
 while L[-1] == &amp;quot;&amp;quot;:&lt;br /&gt;
     L.pop()&lt;br /&gt;
 &lt;br /&gt;
 # check and remove items from list&lt;br /&gt;
  # from https://stackoverflow.com/a/6024599&lt;br /&gt;
  # iterates in-situ via reversed index&lt;br /&gt;
 for i in range(len(lst) - 1, -1, -1):&lt;br /&gt;
     element = lst[i]&lt;br /&gt;
     if check(element):&lt;br /&gt;
         del lst[i]&lt;br /&gt;
&lt;br /&gt;
====check if item is in list====&lt;br /&gt;
 x in lst&lt;br /&gt;
 x not in lst&lt;br /&gt;
 lst.count(x)  # how many items x are in the list&lt;br /&gt;
 i = lst.index(&amp;quot;word&amp;quot;)  # find in list / returns the  position of the first match in list&lt;br /&gt;
&lt;br /&gt;
====pair 2 lists====&lt;br /&gt;
 # zip: merge 2 lists to list of tuples&lt;br /&gt;
 data = list(zip(data_x, data_y, strict=True))&lt;br /&gt;
 &lt;br /&gt;
 # unzip: split list of pairs into 2 lists&lt;br /&gt;
 data_x, data_y = zip(*data, strict=True)&lt;br /&gt;
 &lt;br /&gt;
 # Cartesian product of lists / tuples&lt;br /&gt;
 import itertools&lt;br /&gt;
 for i in itertools.product(*list_of_lists):&lt;br /&gt;
     print(i)&lt;br /&gt;
&lt;br /&gt;
====sort multidim list====&lt;br /&gt;
 lst = sorted(lst, key=lambda x: x[0], reverse=False)&lt;br /&gt;
 lst = sorted(lst, key=lambda row: (row[&amp;quot;Wann&amp;quot;], row [&amp;quot;Wer&amp;quot;]), reverse=False)&lt;br /&gt;
&lt;br /&gt;
====filter list====&lt;br /&gt;
 lines = [x for x in lines if not x.startswith (&amp;quot;word&amp;quot;)]&lt;br /&gt;
 lst = [&amp;quot;asdf&amp;quot;, &amp;quot;asdf2&amp;quot;, &amp;quot;qwertz&amp;quot;]&lt;br /&gt;
 lst = [elem for elem in lst if &amp;quot;asdf&amp;quot; in elem]&lt;br /&gt;
&lt;br /&gt;
====for each element====&lt;br /&gt;
 # trim/strip spaces for each element&lt;br /&gt;
 lines = [s.strip() for s in lines]&lt;br /&gt;
 # modify each item in list by adding constant string&lt;br /&gt;
 l = [s + &#039;;&#039; + v for v in l]&lt;br /&gt;
  &lt;br /&gt;
 # modify item in list&lt;br /&gt;
 for idx, line in enumerate(cont):&lt;br /&gt;
     if &amp;quot;K1001/1&amp;quot; in line:&lt;br /&gt;
         line = &amp;quot;K1001/1 Test Nr &amp;quot; + str(i) + &amp;quot;\n&amp;quot;&lt;br /&gt;
         cont[idx] = line&lt;br /&gt;
         break&lt;br /&gt;
&lt;br /&gt;
===Tuples===&lt;br /&gt;
Ordered sequence, with no ability to replace or delete items&lt;br /&gt;
 tpl = (1,2,3,4,5,6)&lt;br /&gt;
&lt;br /&gt;
list -&amp;gt; tuple&lt;br /&gt;
 tpl = tuple(lst)&lt;br /&gt;
&lt;br /&gt;
combine 2 tuples&lt;br /&gt;
 tpl = tpl1 + tpl2&lt;br /&gt;
&lt;br /&gt;
===Dictionaries===&lt;br /&gt;
like %hash in Perl&lt;br /&gt;
 d = {&amp;quot;key1&amp;quot;: &amp;quot;value1&amp;quot;, &amp;quot;key2&amp;quot;: 123}&lt;br /&gt;
 d[&amp;quot;key3&amp;quot;] = (1, 2, 3)&lt;br /&gt;
 del d[&amp;quot;key3&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
 len(d)&lt;br /&gt;
 d.clear()&lt;br /&gt;
 d.copy()&lt;br /&gt;
 d.keys()&lt;br /&gt;
 d.values()&lt;br /&gt;
 d.items()  # returns a list of tuples (key, value)&lt;br /&gt;
 d.get(k)  # returns value of key k&lt;br /&gt;
 d.get(k, x)  # returns value of key k; if k is not  in d it returns x&lt;br /&gt;
 count = d.get(&amp;quot;key&amp;quot;, 0) + 1 # nice for counters&lt;br /&gt;
 d.pop(k)  # returns and removes item k&lt;br /&gt;
 d.pop(k, x)  # returns and removes item k; if k is  not in d it returns x&lt;br /&gt;
 # checks&lt;br /&gt;
 x in d&lt;br /&gt;
 x not in d&lt;br /&gt;
 &lt;br /&gt;
 # dict can have tuple as key&lt;br /&gt;
 tpl = (&amp;quot;key1&amp;quot;, &amp;quot;key2&amp;quot;, 123)&lt;br /&gt;
 d[tpl] = 123456&lt;br /&gt;
 &lt;br /&gt;
 # loop over all key-value pairs&lt;br /&gt;
 for key, value in d.items():&lt;br /&gt;
     print(f&amp;quot;{key} = {value}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # sorted keys:&lt;br /&gt;
 for key in sorted(dict.keys()):&lt;br /&gt;
     pass&lt;br /&gt;
 # sorted values, reversed&lt;br /&gt;
 for key, value in sorted(d.items(), key=lambda item:  item[1], reverse=True):&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # join / merge 2 dicts&lt;br /&gt;
 d.update(d2)&lt;br /&gt;
 &lt;br /&gt;
 # flatten dict of dict&lt;br /&gt;
 flat = d.copy()&lt;br /&gt;
 meta = d.pop(&amp;quot;metadata&amp;quot;)&lt;br /&gt;
 flat.update(meta)&lt;br /&gt;
&lt;br /&gt;
====MultiDim Dictionaries====&lt;br /&gt;
 d = {}&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;] = {}&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;][&amp;quot;value&amp;quot;] = 1.909e18&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;][&amp;quot;name&amp;quot;] = &amp;quot;Item 1&amp;quot;&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;] = {}&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;][&amp;quot;value&amp;quot;] = 1.725e18&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;][&amp;quot;name&amp;quot;] = &amp;quot;Item 2&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 for k in d.keys():&lt;br /&gt;
     d[k][&amp;quot;value&amp;quot;] = d[k][&amp;quot;value&amp;quot;] * 1.1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def find_common_strings(dict1: dict, dict2: dict) -&amp;gt; dict[Any, Any]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Find common strings in two dict, returning a new dict of those strings.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     def recursive_match(d1: dict, d2: dict) -&amp;gt; dict[Any, Any]:&lt;br /&gt;
         common_dict = {}&lt;br /&gt;
         for key in d1:  # noqa: PLC0206&lt;br /&gt;
             if key in d2:&lt;br /&gt;
                 if isinstance(d1[key], dict) and isinstance(d2[key], dict):&lt;br /&gt;
                     # Recurse if both values are dicts&lt;br /&gt;
                     nested_common = recursive_match(d1[key], d2[key])&lt;br /&gt;
                     if nested_common:  # Add to result if common values are found&lt;br /&gt;
                         common_dict[key] = nested_common&lt;br /&gt;
                 elif isinstance(d1[key], str) and isinstance(d2[key], str):&lt;br /&gt;
                     # Check if both are strings and identical&lt;br /&gt;
                     if d1[key] == d2[key]:&lt;br /&gt;
                         common_dict[key] = d1[key]&lt;br /&gt;
         return common_dict&lt;br /&gt;
 &lt;br /&gt;
     return recursive_match(dict1, dict2)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def update_dict_with_new_values(original_dict: dict, new_values: dict) -&amp;gt; dict:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Update strings in the original dict with new values from the new_values dict.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     def recursive_update(d1: dict, new_vals: dict) -&amp;gt; None:&lt;br /&gt;
         for key in new_vals:  # noqa: PLC0206&lt;br /&gt;
             if key in d1:&lt;br /&gt;
                 if isinstance(d1[key], dict) and isinstance(new_vals[key], dict):&lt;br /&gt;
                     # Recurse if both values are dicts&lt;br /&gt;
                     recursive_update(d1[key], new_vals[key])&lt;br /&gt;
                 elif isinstance(d1[key], str) and isinstance(new_vals[key], str):&lt;br /&gt;
                     # Update the string&lt;br /&gt;
                     d1[key] = new_vals[key]&lt;br /&gt;
 &lt;br /&gt;
     recursive_update(original_dict, new_values)&lt;br /&gt;
     return original_dict&lt;br /&gt;
&lt;br /&gt;
===Datetime, Date and Time===&lt;br /&gt;
 import datetime as dt&lt;br /&gt;
 from zoneinfo import ZoneInfo&lt;br /&gt;
 TZ_DE = ZoneInfo(&amp;quot;Europe/Berlin&amp;quot;)&lt;br /&gt;
 TZ_UTC = ZoneInfo(&amp;quot;UTC&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
====Create====&lt;br /&gt;
 # date&lt;br /&gt;
 date = dt.date(2023, 12, 31)&lt;br /&gt;
 date = dt.date.fromisocalendar(int(year), int(week),  int(daynum))  # daynum: 1..7&lt;br /&gt;
 date = dt.date.fromisoformat(&amp;quot;2020-03-10&amp;quot;)&lt;br /&gt;
 date = dt.datetime.strptime(datestr, &amp;quot;%y%m%d&amp;quot;).date()&lt;br /&gt;
 date_today = dt.datetime.now(tz=dt.UTC).date()&lt;br /&gt;
 date_yesterday = dt.datetime.now(tz=TZ_DE).date() -  dt.timedelta(days=1)&lt;br /&gt;
 days_overdue = (date_completed - date_due).days&lt;br /&gt;
 # to add one month (which is not supported by timedelta) &lt;br /&gt;
 from dateutil.relativedelta import relativedelta&lt;br /&gt;
 date_end = date_start + relativedelta(months=1)&lt;br /&gt;
 delta_days = 1 + (end - start).days&lt;br /&gt;
 &lt;br /&gt;
 # datetime&lt;br /&gt;
 dt_now = dt.datetime.now(tz=TZ_DE)&lt;br /&gt;
 my_dt = dt.datetime(2023, 12, 31, 14, 31, 56,  tzinfo=TZ_DE)  # 2023-12-31 14:31:56&lt;br /&gt;
 my_dt = dt.datetime.fromtimestamp(my_timestamp,  tz=TZ_DE)&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat (&amp;quot;2017-01-01T12:30:59.000000&amp;quot;)&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(&amp;quot;2020-03-10  06:01:01+00:00&amp;quot;)&lt;br /&gt;
 s = &amp;quot;2020-03-10T06:01:01Z&amp;quot;&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(s.replace(&amp;quot;Z&amp;quot;, &amp;quot; +00:00&amp;quot;))&lt;br /&gt;
 my_date_local = my_dt.astimezone(tz=TZ_DE).date()&lt;br /&gt;
 &lt;br /&gt;
 # datetime -&amp;gt; date&lt;br /&gt;
 my_date = my_dt.date()&lt;br /&gt;
 # date -&amp;gt; datetime&lt;br /&gt;
 my_date = dt.date(2023, 12, 31)&lt;br /&gt;
 my_dt = dt.datetime(my_date.year, my_date.month,  my_date.day, tzinfo=TZ_DE)&lt;br /&gt;
 &lt;br /&gt;
 # to string&lt;br /&gt;
 date_str = my_dt.strftime(&amp;quot;%y%m%d&amp;quot;)&lt;br /&gt;
 date_str = my_dt.strftime(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;)&lt;br /&gt;
 # alternative using time&lt;br /&gt;
 date_str = time.strftime(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;)&lt;br /&gt;
 # now in UTC without milliseconds&lt;br /&gt;
 date_str = dt.datetime.now(tz=dt.timezone.utc).replace(microsecond=0).isoformat() + &amp;quot;Z&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # remove timezone&lt;br /&gt;
 my_dt = my_dt.replace(tzinfo=None)&lt;br /&gt;
 &lt;br /&gt;
 # German format&lt;br /&gt;
 import locale&lt;br /&gt;
 locale.setlocale(locale.LC_ALL, &amp;quot;de_DE&amp;quot;)&lt;br /&gt;
 print(my_date.strftime(&amp;quot;%a %x&amp;quot;))  # Mo 25.12.2023&lt;br /&gt;
 &lt;br /&gt;
 # Calendar week&lt;br /&gt;
 week = my_date.isocalendar()[1]&lt;br /&gt;
 print(&amp;quot;KW%02d&amp;quot; % week)&lt;br /&gt;
 &lt;br /&gt;
 # first day of quarter&lt;br /&gt;
 def get_first_day_of_the_quarter(my_date: dt.date) -&amp;gt; dt.date:&lt;br /&gt;
     return dt.date(my_date.year, 1 + 3 * ((my_date.month - 1) // 3), 1)&lt;br /&gt;
&lt;br /&gt;
====rounding datetimes to minutes====&lt;br /&gt;
 def floor_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Floor (=round down) minutes to X min  resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     min_new = res * (my_dt.minute // res)&lt;br /&gt;
     return my_dt.replace(minute=min_new, second=0,  microsecond=0)&lt;br /&gt;
 &lt;br /&gt;
 def ceil_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Ceil (=round up) minutes to X min resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    min_new = res * (1 + my_dt.minute // res)&lt;br /&gt;
    return my_dt.replace(minute=0, second=0, microsecond=0) + dt.timedelta(&lt;br /&gt;
        minutes=min_new&lt;br /&gt;
    )&lt;br /&gt;
 &lt;br /&gt;
 def round_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Cound minutes to X min resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    min_old_dec = float(my_dt.minute) + float(my_dt.second * 60)&lt;br /&gt;
    min_new = res * round(min_old_dec / res)&lt;br /&gt;
    return my_dt.replace(minute=0, second=0, microsecond=0) + dt.timedelta(&lt;br /&gt;
        minutes=min_new&lt;br /&gt;
    )&lt;br /&gt;
 &lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(&amp;quot;2011-11-04  00:05:23+00:00&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;original: {my_dt}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;floored: {floor_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;ceileded: {ceil_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;rounded: {round_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
====Timing / Time elapsed====&lt;br /&gt;
 import time &lt;br /&gt;
 timestart = time.time()&lt;br /&gt;
 sec = time.time() - timestart&lt;br /&gt;
 print(f&amp;quot;Time elapsed: {sec:.2f} sec&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==OS, Argpars, etc.==&lt;br /&gt;
===Hostname===&lt;br /&gt;
 from socket import gethostname&lt;br /&gt;
 print(gethostname())&lt;br /&gt;
&lt;br /&gt;
===Checking Operating System===&lt;br /&gt;
 import os&lt;br /&gt;
 import sys&lt;br /&gt;
  &lt;br /&gt;
 if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
     print(&amp;quot;posix/Unix/Linux&amp;quot;)&lt;br /&gt;
 elif os.name == &amp;quot;nt&amp;quot;:&lt;br /&gt;
     print(&amp;quot;windows&amp;quot;)&lt;br /&gt;
 else:&lt;br /&gt;
     print(&amp;quot;unknown os&amp;quot;)&lt;br /&gt;
     sys.exit(1)  # throws exception, use quit() to   close / die silently&lt;br /&gt;
&lt;br /&gt;
===Get filename of python script===&lt;br /&gt;
 my_file_path = __file__&lt;br /&gt;
&lt;br /&gt;
alternative using sys package&lt;br /&gt;
 from sys import argv&lt;br /&gt;
 myFilename = argv[0]&lt;br /&gt;
&lt;br /&gt;
===accessing os envrionment variables===&lt;br /&gt;
 import os&lt;br /&gt;
 print(os.getenv(&amp;quot;tmp&amp;quot;))&lt;br /&gt;
 # better:&lt;br /&gt;
 try:&lt;br /&gt;
     s = os.environ[key] # throws error if unset&lt;br /&gt;
 except KeyError:&lt;br /&gt;
     error_message(f&amp;quot;Environment variable {key} not set.&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Command Line Arguments===&lt;br /&gt;
====ArgumentParser====&lt;br /&gt;
 import argparse&lt;br /&gt;
 &lt;br /&gt;
 parser = (&lt;br /&gt;
     argparse.ArgumentParser()&lt;br /&gt;
 )  # construct the argument parser and parse the  arguments&lt;br /&gt;
 # -h comes automatically&lt;br /&gt;
 &lt;br /&gt;
 # Boolean Parameter&lt;br /&gt;
 parser.add_argument(&lt;br /&gt;
     &amp;quot;-v&amp;quot;, &amp;quot;--verbose&amp;quot;, help=&amp;quot;increase output  verbosity&amp;quot;, action=&amp;quot;store_true&amp;quot;&lt;br /&gt;
 )  # store_true -&amp;gt; Boolean Value&lt;br /&gt;
 &lt;br /&gt;
 # Choice Parameter&lt;br /&gt;
 # restrict to a list of possible values / choices&lt;br /&gt;
 # parser.add_argument(&amp;quot;--choice&amp;quot;, type=int, choices= [0, 1, 2], help=&amp;quot;Test choices&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Positional Parameter (like text.py 123)&lt;br /&gt;
 # parser.add_argument(&amp;quot;num&amp;quot;, type=int, help=&amp;quot;Number  of things&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Required Parameter&lt;br /&gt;
 # parser.add_argument(&amp;quot;-i&amp;quot;, &amp;quot;--input&amp;quot;, type=str,  required=True, help=&amp;quot;Path of file&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Optional Parameter&lt;br /&gt;
 parser.add_argument(&amp;quot;-n&amp;quot;, &amp;quot;--number&amp;quot;, type=int,  help=&amp;quot;Number of clicks&amp;quot;)&lt;br /&gt;
 # Optional Parameter with Default&lt;br /&gt;
 parser.add_argument(&lt;br /&gt;
     &amp;quot;-s&amp;quot;,&lt;br /&gt;
     &amp;quot;--seconds&amp;quot;,&lt;br /&gt;
     type=int,&lt;br /&gt;
     default=sec_default,&lt;br /&gt;
     help=&amp;quot;Duration of clicking, default = %i (sec)&amp;quot;  % sec_default,&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 args = vars(parser.parse_args())&lt;br /&gt;
 &lt;br /&gt;
 if args[&amp;quot;verbose&amp;quot;]:&lt;br /&gt;
     pass  # do nothing&lt;br /&gt;
 # print (&amp;quot;verbosity turned on&amp;quot;)&lt;br /&gt;
 if args[&amp;quot;number&amp;quot;]:&lt;br /&gt;
     print(&amp;quot;num=%i&amp;quot; % args[&amp;quot;number&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
==== match case statement ====&lt;br /&gt;
(new in python 3.10)&lt;br /&gt;
 match args:&lt;br /&gt;
   case {&amp;quot;sap&amp;quot;: &amp;quot;prod&amp;quot;, &amp;quot;version&amp;quot;: 1}:&lt;br /&gt;
     ...&lt;br /&gt;
   case {&amp;quot;sap&amp;quot;: &amp;quot;prod&amp;quot;, &amp;quot;version&amp;quot;: 2}:&lt;br /&gt;
     ...&lt;br /&gt;
   case _: # default case&lt;br /&gt;
&lt;br /&gt;
==File Access==&lt;br /&gt;
===Pathlib===&lt;br /&gt;
for migrating see table [https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module]&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 p = Path(&amp;quot;dir/test.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 log_file = Path(__file__).with_suffix(&amp;quot;.log&amp;quot;)  # __file__ is name of python script&lt;br /&gt;
 &lt;br /&gt;
 my_fname = p.name  #  alternative to basename&lt;br /&gt;
 my_ext = p.suffix&lt;br /&gt;
 (filepath_without_ext, file_ext) = (p.stem, p.suffix)&lt;br /&gt;
 my_dir = p.parent&lt;br /&gt;
 my_parent_dir = p.parents[1]&lt;br /&gt;
 p2 = p.with_suffix(&amp;quot;.json&amp;quot;)&lt;br /&gt;
 p3 = p.parent / (p.name + &amp;quot;-autofix.tex&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # checks&lt;br /&gt;
 Path(my_file_str).exists()&lt;br /&gt;
 Path(my_file_str).is_file()&lt;br /&gt;
 Path(my_file_str).is_dir()&lt;br /&gt;
 &lt;br /&gt;
 # loop over glob of files matching wildcard&lt;br /&gt;
 for file_out in Path(&amp;quot;mydir&amp;quot;).glob(&amp;quot;*-autofix.tex&amp;quot;):&lt;br /&gt;
 &lt;br /&gt;
 # loop over dirs&lt;br /&gt;
 p = Path() # cwd&lt;br /&gt;
 list_of_repos = [x for x in p.iterdir() if x.is_dir() and (x / &amp;quot;.git&amp;quot;).is_dir()]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
alternative using old os functions&lt;br /&gt;
Split path into folder, filename, ext&lt;br /&gt;
 import os&lt;br /&gt;
 (dirName, fileName) = os.path.split(f)&lt;br /&gt;
 (fileBaseName, fileExtension) = os.path.splitext(fileName)&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-out.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===File Manipulations: copy, move, delete, touch===&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 # copy&lt;br /&gt;
 from shutil import copyfile&lt;br /&gt;
 copyfile(Path(&amp;quot;file-source.txt&amp;quot;), Path(&amp;quot;dir&amp;quot;) / Path(&amp;quot;file-target.txt&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # move / rename&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).replace(Path(&amp;quot;file2.txt&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # delete&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).unlink()&lt;br /&gt;
 &lt;br /&gt;
 # touch&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).touch()&lt;br /&gt;
&lt;br /&gt;
===File size and timestamp===&lt;br /&gt;
 # size&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).stat().st_size&lt;br /&gt;
 &lt;br /&gt;
 # timestamp last modified&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).stat().st_mtime&lt;br /&gt;
&lt;br /&gt;
===Dir create/mkdir and delete===&lt;br /&gt;
 # create dir&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 Path(&amp;quot;myDir1/myDir2&amp;quot;).mkdir(parents=True, exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
 # delete dir&lt;br /&gt;
 import shutil&lt;br /&gt;
 shutil.rmtree(d)&lt;br /&gt;
&lt;br /&gt;
===Loop over Directories===&lt;br /&gt;
====Fetch Dir Contents / Loop over Files====&lt;br /&gt;
glob via pathlib&lt;br /&gt;
 for fileOut in Path(&amp;quot;mydir&amp;quot;).glob(&amp;quot;*-autofix.tex&amp;quot;):&lt;br /&gt;
&lt;br /&gt;
Get list of files in directory, filter dirs from list, filter by ext&lt;br /&gt;
 dir= &amp;quot;/path/to/some/dir&amp;quot;&lt;br /&gt;
 listoffiles = [ f for f in os.listdir(dir) if os.path.isfile(os.path.join(dir ,f)) and f.lower()[-4:] == &amp;quot;.gpx&amp;quot;]&lt;br /&gt;
 listoffiles.sort()&lt;br /&gt;
&lt;br /&gt;
====Traverse in Subdirs====&lt;br /&gt;
 # walk into path an fetch all files matching extension jpe?g&lt;br /&gt;
 files = []&lt;br /&gt;
 for (dirpath, dirnames, filenames) in os.walk(&amp;quot;.&amp;quot;):&lt;br /&gt;
     dirpath = dirpath.replace(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;)&lt;br /&gt;
     for file in filenames:&lt;br /&gt;
         if file.endswith(&amp;quot;.txt&amp;quot;):&lt;br /&gt;
             files.append(dirpath + &amp;quot;/&amp;quot; + file)&lt;br /&gt;
         elif re.search(r&amp;quot;\.jpe?g$&amp;quot;, file, re.IGNORECASE):&lt;br /&gt;
             files.append(dirpath + &amp;quot;/&amp;quot; + file)&lt;br /&gt;
&lt;br /&gt;
==File Parsing==&lt;br /&gt;
===File General===&lt;br /&gt;
====File Read====&lt;br /&gt;
 path_file_in = Path(&amp;quot;file.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # via pathlib&lt;br /&gt;
 cont = path_file_in.read_text(encoding=&amp;quot;utf-8&amp;quot;) # note: utf-8-sig for UTF-8-BOM&lt;br /&gt;
 &lt;br /&gt;
 # general&lt;br /&gt;
 with path_file_in.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
     cont = fh.read()&lt;br /&gt;
     # or&lt;br /&gt;
     lst = fh.readlines()&lt;br /&gt;
     # or&lt;br /&gt;
     line = fh.readline()&lt;br /&gt;
     # or&lt;br /&gt;
     for line in fh:&lt;br /&gt;
         print(line)&lt;br /&gt;
 &lt;br /&gt;
 fh = path_file_in.open(encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 ...&lt;br /&gt;
 fh.close()&lt;br /&gt;
&lt;br /&gt;
====File Write====&lt;br /&gt;
 path_file_out = Path(&amp;quot;out/1/out.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # write via pathlib&lt;br /&gt;
 path_file_out.write_text(&amp;quot;some text&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # write general&lt;br /&gt;
 with path_file_out.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     # w = overWrite file ; a = append to file&lt;br /&gt;
     # If running Python in Windows, &amp;quot;\n&amp;quot; is automatically replaced by &amp;quot;\r\n&amp;quot;.&lt;br /&gt;
     # To prevent this use newline=&#039;\n&#039;&lt;br /&gt;
     fh.writelines(lst)  # no linebreaks&lt;br /&gt;
     # or&lt;br /&gt;
     fh.write(&amp;quot;\n&amp;quot;.join(lst))&lt;br /&gt;
     # or&lt;br /&gt;
     for line in lst:&lt;br /&gt;
         fh.write(line)&lt;br /&gt;
 &lt;br /&gt;
     # Force update of file contents without closing it&lt;br /&gt;
     fh.flush()&lt;br /&gt;
 &lt;br /&gt;
 # alternative&lt;br /&gt;
 fh = path_file_out.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;)&lt;br /&gt;
 ...&lt;br /&gt;
 fh.close()&lt;br /&gt;
&lt;br /&gt;
===CSV===&lt;br /&gt;
====CSV Read====&lt;br /&gt;
 import csv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 with Path(&amp;quot;data.csv&amp;quot;).open(mode=&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as fh:  # for Excel use ANSI&lt;br /&gt;
     csv_reader = csv.DictReader(fh, dialect=&amp;quot;excel&amp;quot;, delimiter=&amp;quot;\t&amp;quot;)&lt;br /&gt;
     for row in csv_reader:&lt;br /&gt;
         print(f&#039;\t{row[&amp;quot;name&amp;quot;]} works in the {row[&amp;quot;department&amp;quot;]} department&#039;)&lt;br /&gt;
&lt;br /&gt;
====CSV Write====&lt;br /&gt;
 import csv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 # simple&lt;br /&gt;
 with Path(&amp;quot;data.tsv&amp;quot;).open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     csvwriter = csv.writer(fh, delimiter=&amp;quot;\t&amp;quot;)&lt;br /&gt;
     csvwriter.writerow((&amp;quot;Date&amp;quot;, &amp;quot;Confirmed&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # dictwriter&lt;br /&gt;
 with Path(&amp;quot;data.tsv&amp;quot;).open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     csvwriter = csv.DictWriter(&lt;br /&gt;
         fh,&lt;br /&gt;
         delimiter=&amp;quot;\t&amp;quot;,&lt;br /&gt;
         extrasaction=&amp;quot;ignore&amp;quot;,&lt;br /&gt;
         fieldnames=[&amp;quot;date&amp;quot;, &amp;quot;occupied_percent&amp;quot;, &amp;quot;occupied&amp;quot;, &amp;quot;total&amp;quot;],&lt;br /&gt;
     )&lt;br /&gt;
     csvwriter.writeheader()&lt;br /&gt;
     for d in my_list_of_dicts:&lt;br /&gt;
         d[&amp;quot;occupied_percent&amp;quot;] = round(100 * d[&amp;quot;occupied&amp;quot;] / d[&amp;quot;total&amp;quot;], 1)&lt;br /&gt;
         csvwriter.writerow(d)&lt;br /&gt;
&lt;br /&gt;
===JSON===&lt;br /&gt;
====JSON Read====&lt;br /&gt;
 import json&lt;br /&gt;
 # from file&lt;br /&gt;
 with path_to_download_file.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
     d_json = json.load(fh)&lt;br /&gt;
 # from string&lt;br /&gt;
 d_json = json.loads(response_text)&lt;br /&gt;
&lt;br /&gt;
 def json_read(file_path: Path) -&amp;gt; list[dict[str, str]]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Read JSON data from file.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     with file_path.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
         json_data = json.load(fh)&lt;br /&gt;
     return json_data&lt;br /&gt;
&lt;br /&gt;
====JSON Write====&lt;br /&gt;
Write dict to file in JSON format, keeping utf-8 encoding&lt;br /&gt;
 import json&lt;br /&gt;
 &lt;br /&gt;
 with path_to_download_file.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     json.dump(my_dict, fh, ensure_ascii=False, sort_keys=False, indent=2)&lt;br /&gt;
&lt;br /&gt;
 def json_write(file_path: Path, json_data: list[dict[str, str]]) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Write JSON data to file.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     with file_path.open(&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
         json.dump(json_data, fh, ensure_ascii=False, sort_keys=False, indent=2)&lt;br /&gt;
&lt;br /&gt;
===Excel===&lt;br /&gt;
====Excel Read====&lt;br /&gt;
 import openpyxl&lt;br /&gt;
 # or xlsxwriter for write-only&lt;br /&gt;
 &lt;br /&gt;
 workbook = openpyxl.load_workbook(&lt;br /&gt;
     pathToMyExcelFile,&lt;br /&gt;
     data_only=True,  # read values instead of formulas&lt;br /&gt;
     read_only=True,  # suppresses: &amp;quot;UserWarning: wmf image format is not supported so the image is being dropped&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 sheet = workbook[&amp;quot;mySheetName&amp;quot;]&lt;br /&gt;
 # or fetch active sheet&lt;br /&gt;
 sheet = workbook.active&lt;br /&gt;
 cell = sheet[&amp;quot;A34&amp;quot;]&lt;br /&gt;
 # or&lt;br /&gt;
 cell = sheet.cell(row=34, column=1)  # index start here with 1&lt;br /&gt;
 print(cell.value)&lt;br /&gt;
 # or&lt;br /&gt;
 print(sheet.cell(column=col, row=row).value)&lt;br /&gt;
&lt;br /&gt;
====Excel Write====&lt;br /&gt;
 import openpyxl&lt;br /&gt;
 &lt;br /&gt;
 workbook = openpyxl.Workbook()&lt;br /&gt;
 sheet = workbook.active&lt;br /&gt;
 cell = sheet[&amp;quot;A34&amp;quot;]&lt;br /&gt;
 # or&lt;br /&gt;
 cell = sheet.cell(row=i, column=j)  # index starts at 1&lt;br /&gt;
 cell.value = &amp;quot;asdf&amp;quot;&lt;br /&gt;
 workbook.save(&amp;quot;out.xlsx&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Word===&lt;br /&gt;
====Word Read====&lt;br /&gt;
 from io import BytesIO&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import pandas as pd&lt;br /&gt;
 from docx import Document&lt;br /&gt;
 from docx.document import Document as DocxDocument&lt;br /&gt;
 &lt;br /&gt;
 def read_doc_to_df(p_doc: Path | BytesIO) -&amp;gt; pd.DataFrame:  # noqa: C901, PLR0912&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Read Word document to DataFrame.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if isinstance(p_doc, Path):&lt;br /&gt;
         doc = Document(str(p_doc))&lt;br /&gt;
     elif isinstance(p_doc, BytesIO):&lt;br /&gt;
         doc = Document(p_doc)&lt;br /&gt;
 &lt;br /&gt;
     # list of multiple paragraphs per key&lt;br /&gt;
     data: dict[str, dict[str, list[str]]] = {}&lt;br /&gt;
 &lt;br /&gt;
     sections1: list[str] = []&lt;br /&gt;
     section1 = &amp;quot;&amp;quot;&lt;br /&gt;
     key = &amp;quot;&amp;quot;&lt;br /&gt;
     section2 = &amp;quot;&amp;quot;&lt;br /&gt;
     score = &amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     for paragraph in doc.paragraphs:&lt;br /&gt;
         assert paragraph.style is not None&lt;br /&gt;
         text = paragraph.text.strip()&lt;br /&gt;
         if paragraph.style.name == &amp;quot;Title&amp;quot;:  # doc title&lt;br /&gt;
             continue&lt;br /&gt;
         if paragraph.style.name == &amp;quot;Heading 1&amp;quot;:  # sheet name&lt;br /&gt;
             section1 = text&lt;br /&gt;
             if section1 not in sections1:&lt;br /&gt;
                 sections1.append(section1)&lt;br /&gt;
             key = &amp;quot;&amp;quot;&lt;br /&gt;
             section2 = &amp;quot;&amp;quot;&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Heading 2&amp;quot;:  # Key of the question&lt;br /&gt;
             key = text.split(&amp;quot; | &amp;quot;)[0]&lt;br /&gt;
             key = section1 + &amp;quot;|&amp;quot; + key&lt;br /&gt;
             section2 = &amp;quot;&amp;quot;&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Normal&amp;quot; and text.startswith(&amp;quot;[&amp;quot;) and &amp;quot;]&amp;quot; in text:&lt;br /&gt;
             section2, t2 = text[1:].split(&amp;quot;]&amp;quot;, maxsplit=1)&lt;br /&gt;
             if section2 == &amp;quot;Score&amp;quot;:&lt;br /&gt;
                 score = t2.strip()&lt;br /&gt;
                 data[key][section2] = [score]&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Normal&amp;quot;:&lt;br /&gt;
             text = paragraph.text.strip()&lt;br /&gt;
             # skip requirements leftovers and empty paragraphs&lt;br /&gt;
             if (section2 == &amp;quot;&amp;quot; and text.startswith(&amp;quot;...&amp;quot;)) or text == &amp;quot;&amp;quot;:&lt;br /&gt;
                 continue&lt;br /&gt;
             lst = data.setdefault(key, {}).setdefault(section2, [])&lt;br /&gt;
             lst.append(text)&lt;br /&gt;
             data[key][section2] = lst&lt;br /&gt;
         else:&lt;br /&gt;
             msg = f&amp;quot;Unexpected style: {paragraph.style.name} in paragraph: {text}&amp;quot;&lt;br /&gt;
             raise ValueError(msg)&lt;br /&gt;
 &lt;br /&gt;
     # flatten the text from list to str&lt;br /&gt;
     data2: dict[str, dict[str, str]] = {}&lt;br /&gt;
     for key, sections in data.items():&lt;br /&gt;
         for section2, lst in sections.items():&lt;br /&gt;
             s = &amp;quot;\n&amp;quot;.join(lst)&lt;br /&gt;
             if key not in data2:&lt;br /&gt;
                 data2[key] = {}&lt;br /&gt;
             data2[key][section2] = s.strip()&lt;br /&gt;
 &lt;br /&gt;
     df1 = pd.DataFrame.from_dict(data2, orient=&amp;quot;index&amp;quot;)&lt;br /&gt;
     df1.index.name = &amp;quot;Key&amp;quot;&lt;br /&gt;
     df1 = df1.reset_index()&lt;br /&gt;
     df1[ [ &amp;quot;Sheet&amp;quot;, &amp;quot;Key&amp;quot; ] ] = df1[&amp;quot;Key&amp;quot;].str.split(&amp;quot;|&amp;quot;, n=1, expand=True)&lt;br /&gt;
 &lt;br /&gt;
     return df1&lt;br /&gt;
&lt;br /&gt;
====Word Write====&lt;br /&gt;
 from io import BytesIO&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import pandas as pd from docx import Document&lt;br /&gt;
 from docx.document import Document as DocxDocument&lt;br /&gt;
 from docx.shared import Cm&lt;br /&gt;
 &lt;br /&gt;
 LAYOUT_SMALL_MARGINS = True&lt;br /&gt;
 if LAYOUT_SMALL_MARGINS:&lt;br /&gt;
     sections = doc.sections&lt;br /&gt;
     for section in sections:&lt;br /&gt;
         section.top_margin = Cm(1)&lt;br /&gt;
         section.bottom_margin = Cm(1)&lt;br /&gt;
         section.left_margin = Cm(1)&lt;br /&gt;
         section.right_margin = Cm(1)&lt;br /&gt;
 &lt;br /&gt;
 doc.add_heading(&amp;quot;My Title&amp;quot;, level=0)&lt;br /&gt;
 doc.add_heading(&amp;quot;My Section&amp;quot;, level=1)&lt;br /&gt;
 doc.add_paragraph(&amp;quot;Some Text)&lt;br /&gt;
 p = doc.add_paragraph()&lt;br /&gt;
 runner = p.add_run(bold text&amp;quot;)&lt;br /&gt;
 runner.bold = True  # runner.italic = True&lt;br /&gt;
&lt;br /&gt;
==Regular Expressions==&lt;br /&gt;
See [http://docs.python.org/library/re.html]&lt;br /&gt;
&lt;br /&gt;
See [https://pythex.org] for an online tester&lt;br /&gt;
&lt;br /&gt;
multiple flags are joined via pipe |&lt;br /&gt;
 s = re.sub(&amp;quot;asdf.*&amp;quot;, r&amp;quot;qwertz&amp;quot;, s, flags=re.DOTALL | re.IGNORECASE)&lt;br /&gt;
&lt;br /&gt;
====Lookahead and Lookbehind====&lt;br /&gt;
 pos lookahead: (?=...)&lt;br /&gt;
 neg lookahead: (?!...)&lt;br /&gt;
 pos lookbehind (?&amp;lt;=...)&lt;br /&gt;
 neg lookbehind (?&amp;lt;!...)&lt;br /&gt;
&lt;br /&gt;
====matching====&lt;br /&gt;
 import re&lt;br /&gt;
 &lt;br /&gt;
 # V0: simple 1&lt;br /&gt;
 myPattern = &amp;quot;(/\*\*\* 0097_210000_0192539580000_2898977_0050 \*\*\*/.*?)($|/\*\*\*)&amp;quot;&lt;br /&gt;
 myRegExp = re.compile(myPattern, re.DOTALL)&lt;br /&gt;
 myMatch = myRegExp.search(cont)&lt;br /&gt;
 assert myMatch != None, f&amp;quot;golden file not found in file {filename}&amp;quot;&lt;br /&gt;
 cont_golden = myMatch.group(1)&lt;br /&gt;
 &lt;br /&gt;
 # V1: simple 2&lt;br /&gt;
 assert (&lt;br /&gt;
     re.match(&amp;quot;^[a-z]{2}$&amp;quot;, d_settings[&amp;quot;country&amp;quot;]) != None&lt;br /&gt;
 ), f&#039;Error: county must be 2 digit lower case. We got: {d_settings[&amp;quot;country&amp;quot;]}&#039;&lt;br /&gt;
 &lt;br /&gt;
 # V2: find and count&lt;br /&gt;
 was = r&amp;quot;&amp;quot;&amp;quot;Part: ([^&amp;lt;+])&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 cnt_parts = len(re.findall(was, cont))&lt;br /&gt;
 cont = re.sub(was, r&amp;quot;\n\nTeil: \1\n\n&amp;quot;, cont)&lt;br /&gt;
 assert cnt_parts == 4, f&amp;quot;{cnt_parts} == 4&amp;quot;&lt;br /&gt;
 assert &amp;quot;Part:&amp;quot; not in cont&lt;br /&gt;
&lt;br /&gt;
=====Match email=====&lt;br /&gt;
 def checkValidEMail(email: str) -&amp;gt; bool:&lt;br /&gt;
     # from https://stackoverflow.com/posts/719543/timeline bottom edit&lt;br /&gt;
     if not re.fullmatch(r&amp;quot;^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$&amp;quot;, email):&lt;br /&gt;
         print(&amp;quot;Error: invalid email&amp;quot;)&lt;br /&gt;
         quit()&lt;br /&gt;
     return True&lt;br /&gt;
&lt;br /&gt;
====Find all====&lt;br /&gt;
 myMatches = re.findall(&#039;href=&amp;quot;([^&amp;quot;]+)&amp;quot;&#039;, cont)&lt;br /&gt;
 for myMatch in myMatches:&lt;br /&gt;
     print(myMatch)&lt;br /&gt;
&lt;br /&gt;
====substring====&lt;br /&gt;
 import re&lt;br /&gt;
 &lt;br /&gt;
 # simple via search&lt;br /&gt;
 lk_id = re.search(&#039;^.*timeseries\-(\d+)\.json$&#039;, f).group(1)&lt;br /&gt;
 &lt;br /&gt;
 # simple via sub&lt;br /&gt;
 myPattern = &amp;quot;^.*&amp;quot; + s1 + &amp;quot;(.*)&amp;quot; + s2 + &amp;quot;.*$&amp;quot;&lt;br /&gt;
 out = re.sub(myPattern, r&amp;quot;\1&amp;quot;, s)&lt;br /&gt;
 &lt;br /&gt;
 # more robust including an assert&lt;br /&gt;
 def substr_between(s: str, s1: str, s2: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     returns substring of s, found between strings s1 and s2&lt;br /&gt;
     s1 and s2 can be regular expressions&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     myPattern = s1 + &#039;(.*)&#039; + s2&lt;br /&gt;
     myRegExp = re.compile(myPattern)&lt;br /&gt;
     myMatches = myRegExp.search(s)&lt;br /&gt;
     assert myMatches != None, f&amp;quot;E: can&#039;t find &#039;{s1}&#039;...&#039;{s2}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     out = myMatches.group(1)&lt;br /&gt;
     return out&lt;br /&gt;
&lt;br /&gt;
 matchObj = re.search(r&amp;quot;(\d+\.\d+)&amp;quot;, text&lt;br /&gt;
 if matchObj:&lt;br /&gt;
   price = float( &#039;%s&#039; % (matchObj).group(0) )&lt;br /&gt;
&lt;br /&gt;
====Naming of match groups====&lt;br /&gt;
(?P&amp;lt;name&amp;gt;...), see [https://docs.python.org/3/library/re.html]&lt;br /&gt;
&lt;br /&gt;
====Search and Replace====&lt;br /&gt;
From [http://www.regular-expressions.info/python.html]&amp;lt;br&amp;gt;&lt;br /&gt;
re.sub(regex, replacement, str) performs a search-and-replace across subject, replacing all matches of regex in str with replacement. The result is returned by the sub() function. The str string you pass is not modified.&lt;br /&gt;
&lt;br /&gt;
 s = re.sub(&amp;quot;  +&amp;quot;, &amp;quot; &amp;quot;, s)&lt;br /&gt;
&lt;br /&gt;
====Splitting====&lt;br /&gt;
From [http://docs.python.org/library/re.html]&amp;lt;br&amp;gt;&lt;br /&gt;
split() splits a string into a list delimited by the passed pattern. The method is invaluable for converting textual data into data structures that can be easily read and modified by Python as demonstrated in the following example that creates a phonebook.&lt;br /&gt;
&lt;br /&gt;
First, here is the input. Normally it may come from a file, here we are using triple-quoted string syntax:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; input = &amp;quot;&amp;quot;&amp;quot;Ross McFluff: 834.345.1254 155 Elm Street&lt;br /&gt;
 ...&lt;br /&gt;
 ... Ronald Heathmore: 892.345.3428 436 Finley Avenue&lt;br /&gt;
 ... Frank Burger: 925.541.7625 662 South Dogwood Way&lt;br /&gt;
 ...&lt;br /&gt;
 ...&lt;br /&gt;
 ... Heather Albrecht: 548.326.4584 919 Park Place&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The entries are separated by one or more newlines. Now we convert the string into a list with each nonempty line having its own entry:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; entries = re.split(&amp;quot;\n+&amp;quot;, input)&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; entries&lt;br /&gt;
 [&#039;Ross McFluff: 834.345.1254 155 Elm Street&#039;,&lt;br /&gt;
 &#039;Ronald Heathmore: 892.345.3428 436 Finley Avenue&#039;,&lt;br /&gt;
 &#039;Frank Burger: 925.541.7625 662 South Dogwood Way&#039;,&lt;br /&gt;
 &#039;Heather Albrecht: 548.326.4584 919 Park Place&#039;]&lt;br /&gt;
&lt;br /&gt;
Finally, split each entry into a list with first name, last name, telephone number, and address. We use the maxsplit parameter of split() because the address has spaces, our splitting pattern, in it:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; [re.split(&amp;quot;:? &amp;quot;, entry, 3) for entry in entries]&lt;br /&gt;
 [[&#039;Ross&#039;, &#039;McFluff&#039;, &#039;834.345.1254&#039;, &#039;155 Elm Street&#039;],&lt;br /&gt;
 [&#039;Ronald&#039;, &#039;Heathmore&#039;, &#039;892.345.3428&#039;, &#039;436 Finley Avenue&#039;],&lt;br /&gt;
 [&#039;Frank&#039;, &#039;Burger&#039;, &#039;925.541.7625&#039;, &#039;662 South Dogwood Way&#039;],&lt;br /&gt;
 [&#039;Heather&#039;, &#039;Albrecht&#039;, &#039;548.326.4584&#039;, &#039;919 Park Place&#039;]]&lt;br /&gt;
&lt;br /&gt;
==== replace cont by linebreaks====&lt;br /&gt;
 def replace_cont_by_linebreaks(s: str, regex: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Replace regex in s by the number of linebreaks it originally contained.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     myMatches = re.findall(regex, s, flags=re.DOTALL)&lt;br /&gt;
     for match in myMatches:&lt;br /&gt;
         linebreaks = match.count(&amp;quot;\n&amp;quot;)&lt;br /&gt;
         s = s.replace(match, &amp;quot;\n&amp;quot; * linebreaks, 1)&lt;br /&gt;
     return s&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Image/Picture/Photo==&lt;br /&gt;
 from PIL import Image, ImageFilter  # pip install Pillow&lt;br /&gt;
 &lt;br /&gt;
 fileIn = &amp;quot;2018-02-09 13.56.25.jpg&amp;quot;&lt;br /&gt;
 # Read image&lt;br /&gt;
 img = Image.open(fileIn)&lt;br /&gt;
PROBLEM:&amp;lt;br/&amp;gt;&lt;br /&gt;
PIL Image.save() drops the IPTC data like tags, keywords, copywrite, ...&amp;lt;br/&amp;gt;&lt;br /&gt;
better using https://imagemagick.org instead when tags shall be kept&lt;br /&gt;
&lt;br /&gt;
====Resize====&lt;br /&gt;
 # Resize keeping aspect ration -&amp;gt; img.thumbnail&lt;br /&gt;
 # drops exif data, exif can be added from source file via exif= in save, see below&lt;br /&gt;
 size = 1920, 1920&lt;br /&gt;
 img.thumbnail(size, Image.ANTIALIAS)&lt;br /&gt;
&lt;br /&gt;
====Export file====&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-edit.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img = Image.open(fileIn)&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=&#039;keep&#039;)  # exif=dict_exif_bytes&lt;br /&gt;
     # JPEG Parameters&lt;br /&gt;
     # * qualitiy : &#039;keep&#039; or 1 (worst) to 95 (best), default = 75. Values above 95 should be avoided.&lt;br /&gt;
     # * dpi : tuple of integers representing the pixel density, (x,y)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     print(&amp;quot;cannot write file &#039;%s&#039;&amp;quot; % fileOut)&lt;br /&gt;
&lt;br /&gt;
====Export Progressive / web optimized JPEG====&lt;br /&gt;
 from PIL import ImageFile  # for MAXBLOCK for progressive export&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-progressive.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=80, optimize=True, progressive=True)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     ImageFile.MAXBLOCK = img.size[0] * img.size[1]&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=80, optimize=True, progressive=True)&lt;br /&gt;
&lt;br /&gt;
====JPEG Meta Data: EXIF and IPTC====&lt;br /&gt;
=====IPTC: Tags/Keywords=====&lt;br /&gt;
 from iptcinfo3 import IPTCInfo  # this works in pyhton 3!&lt;br /&gt;
 iptc = IPTCInfo(fileIn)&lt;br /&gt;
 if len(iptc[&#039;keywords&#039;]) &amp;gt; 0:  # or supplementalCategories or contacts&lt;br /&gt;
     print(&#039;====&amp;gt; Keywords&#039;)&lt;br /&gt;
     for key in sorted(iptc[&#039;keywords&#039;]):&lt;br /&gt;
         s = key.decode(&#039;ascii&#039;)  # decode binary strings&lt;br /&gt;
         print(s)&lt;br /&gt;
&lt;br /&gt;
=====EXIF via piexif=====&lt;br /&gt;
 import piexif  # pip install piexif&lt;br /&gt;
 exif_dict = piexif.load(img.info[&#039;exif&#039;])&lt;br /&gt;
 print(exif_dict[&#039;GPS&#039;][piexif.GPSIFD.GPSAltitude])&lt;br /&gt;
 # returns list of 2 integers: value and donator  -&amp;gt; v / d&lt;br /&gt;
 # (340000, 1000) =&amp;gt; 340m&lt;br /&gt;
 # (51, 2) =&amp;gt; 25.5m&lt;br /&gt;
 &lt;br /&gt;
 # Modify altitude&lt;br /&gt;
 exif_dict[&#039;GPS&#039;][piexif.GPSIFD.GPSAltitude] = (140, 1)  # 140m&lt;br /&gt;
 &lt;br /&gt;
 # write to file&lt;br /&gt;
 exif_bytes = piexif.dump(exif_dict)&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-modExif.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;jpeg&amp;quot;, exif=exif_bytes, quality=&#039;keep&#039;)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     print(&amp;quot;cannot write file &#039;%s&#039;&amp;quot; % fileOut)&lt;br /&gt;
or&lt;br /&gt;
 exif_dict = piexif.load(fileIn)&lt;br /&gt;
 for ifd in (&amp;quot;0th&amp;quot;, &amp;quot;Exif&amp;quot;, &amp;quot;GPS&amp;quot;, &amp;quot;1st&amp;quot;):&lt;br /&gt;
     print(&amp;quot;===&amp;quot; + ifd)&lt;br /&gt;
     for tag in exif_dict[ifd]:&lt;br /&gt;
         print(piexif.TAGS[ifd][tag][&amp;quot;name&amp;quot;], &amp;quot;\t&amp;quot;,&lt;br /&gt;
               tag, &amp;quot;\t&amp;quot;, exif_dict[ifd][tag])&lt;br /&gt;
 print(exif_dict[&#039;0th&#039;][306]) # 306 = DateTime&lt;br /&gt;
&lt;br /&gt;
=====EXIF via exifread=====&lt;br /&gt;
 # Open image file for reading (binary mode)&lt;br /&gt;
 fh = open(fileIn, &amp;quot;rb&amp;quot;)&lt;br /&gt;
 # Return Exif tags&lt;br /&gt;
 exif = exifread.process_file(fh)&lt;br /&gt;
 fh.close()&lt;br /&gt;
 # for tag in exif.keys():&lt;br /&gt;
 #     if tag not in (&#039;JPEGThumbnail&#039;, &#039;TIFFThumbnail&#039;, &#039;Filename&#039;, &#039;EXIF MakerNote&#039;):&lt;br /&gt;
 #         print(&amp;quot;%s\t%s&amp;quot; % (tag, exif[tag]))&lt;br /&gt;
 print(exif[&amp;quot;Image DateTime&amp;quot;])&lt;br /&gt;
 print(exif[&amp;quot;GPS GPSLatitude&amp;quot;])&lt;br /&gt;
 print(exif[&amp;quot;GPS GPSLongitude&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
=====EXIF GPS via PIL=====&lt;br /&gt;
 # from https://developer.here.com/blog/getting-started-with-geocoding-exif-image-metadata-in-python3&lt;br /&gt;
 def get_exif(filename):&lt;br /&gt;
     image = Image.open(filename)&lt;br /&gt;
     image.verify()&lt;br /&gt;
     image.close()&lt;br /&gt;
     return image._getexif()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_labeled_exif(exif):&lt;br /&gt;
     labeled = {}&lt;br /&gt;
     for (key, val) in exif.items():&lt;br /&gt;
         labeled[TAGS.get(key)] = val&lt;br /&gt;
     return labeled&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_geotagging(exif):&lt;br /&gt;
     if not exif:&lt;br /&gt;
         raise ValueError(&amp;quot;No EXIF metadata found&amp;quot;)&lt;br /&gt;
     geotagging = {}&lt;br /&gt;
     for (idx, tag) in TAGS.items():&lt;br /&gt;
         if tag == &amp;quot;GPSInfo&amp;quot;:&lt;br /&gt;
             if idx not in exif:&lt;br /&gt;
                 raise ValueError(&amp;quot;No EXIF geotagging found&amp;quot;)&lt;br /&gt;
             for (key, val) in GPSTAGS.items():&lt;br /&gt;
                 if key in exif[idx]:&lt;br /&gt;
                     geotagging[val] = exif[idx][key]&lt;br /&gt;
     return geotagging&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_decimal_from_dms(dms, ref):&lt;br /&gt;
     degrees = dms[0][0] / dms[0][1]&lt;br /&gt;
     minutes = dms[1][0] / dms[1][1] / 60.0&lt;br /&gt;
     seconds = dms[2][0] / dms[2][1] / 3600.0&lt;br /&gt;
     if ref in [&amp;quot;S&amp;quot;, &amp;quot;W&amp;quot;]:&lt;br /&gt;
         degrees = -degrees&lt;br /&gt;
         minutes = -minutes&lt;br /&gt;
         seconds = -seconds&lt;br /&gt;
     return round(degrees + minutes + seconds, 5)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_coordinates(geotags):&lt;br /&gt;
     lat = get_decimal_from_dms(geotags[&amp;quot;GPSLatitude&amp;quot;], geotags[&amp;quot;GPSLatitudeRef&amp;quot;])&lt;br /&gt;
     lon = get_decimal_from_dms(geotags[&amp;quot;GPSLongitude&amp;quot;], geotags[&amp;quot;GPSLongitudeRef&amp;quot;])&lt;br /&gt;
     return (lat, lon)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 exif = get_exif(fileIn)&lt;br /&gt;
 exif_labeled = get_labeled_exif(exif)&lt;br /&gt;
 print(exif_labeled[&amp;quot;DateTime&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
 geotags = get_geotagging(exif)&lt;br /&gt;
 print(get_coordinates(geotags))&lt;br /&gt;
&lt;br /&gt;
====Template Matching / Image Regocnition Using CV2 / OpenCV====&lt;br /&gt;
see [[Python - CV2]]&lt;br /&gt;
&lt;br /&gt;
====Optical Character Recognition (OCR) ====&lt;br /&gt;
see [[Python - OCR]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Templates/Snippets==&lt;br /&gt;
===Retry on Exception===&lt;br /&gt;
 for retry_no in range(MAX_RETRIES):&lt;br /&gt;
     try:&lt;br /&gt;
         if retry_no &amp;gt; 0:&lt;br /&gt;
             logger.warning(&amp;quot;retrying %d/%d...&amp;quot;, retry_no + 1, MAX_RETRIES)&lt;br /&gt;
         doit()&lt;br /&gt;
     except json.JSONDecodeError:&lt;br /&gt;
         logger.exception(&amp;quot;JSONDecodeError&amp;quot;)&lt;br /&gt;
         if retry_no == MAX_RETRIES - 1:&lt;br /&gt;
             # end of retries&lt;br /&gt;
             raise&lt;br /&gt;
&lt;br /&gt;
===Diff of 2 files===&lt;br /&gt;
 import difflib&lt;br /&gt;
 &lt;br /&gt;
 fh1 = p_file1.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 fh2 = p_file1.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 diff = difflib.ndiff(fh1.readlines(), fh2.readlines())&lt;br /&gt;
 delta = &amp;quot;&amp;quot;.join(line for line in diff if line.startswith((&amp;quot;+ &amp;quot;, &amp;quot;- &amp;quot;)))&lt;br /&gt;
 print(delta)&lt;br /&gt;
&lt;br /&gt;
===GPX parsing===&lt;br /&gt;
 import gpxpy&lt;br /&gt;
 import gpxpy.gpx&lt;br /&gt;
 # Elevation data by NASA: see lib at https://github.com/tkrajina/srtm.py&lt;br /&gt;
 fh_gpx_file = open(gpx_file_path, &#039;r&#039;)&lt;br /&gt;
 gpx = gpxpy.parse(fh_gpx_file)&lt;br /&gt;
 #  Loops for accessing the data&lt;br /&gt;
 for track in gpx.tracks:&lt;br /&gt;
     for segment in track.segments:&lt;br /&gt;
         for point in segment.points:&lt;br /&gt;
 for waypoint in gpx.waypoints:&lt;br /&gt;
 for route in gpx.routes:&lt;br /&gt;
     for point in route.points: &lt;br /&gt;
 # interesting properties of point / waypoint objects:&lt;br /&gt;
 point.time&lt;br /&gt;
 point.latitude&lt;br /&gt;
 point.longitude&lt;br /&gt;
 point.source&lt;br /&gt;
 waypoint.name&lt;br /&gt;
&lt;br /&gt;
===TypeHints===&lt;br /&gt;
 from typing import Any, Dict, cast&lt;br /&gt;
 creds = cast(dict[str, str], tomllib.load(f))  # type: ignore&lt;br /&gt;
 o = cast(Dict[str, Any], tomllib.load(f))  # type: ignore&lt;br /&gt;
 o[&amp;quot;sap&amp;quot;] = cast(Dict[str, str], o[&amp;quot;sap&amp;quot;])&lt;br /&gt;
 o[&amp;quot;settings&amp;quot;] = cast(Dict[str, str | int | bool], o[&amp;quot;settings&amp;quot;])&lt;br /&gt;
 o[&amp;quot;settings&amp;quot;][&amp;quot;sleep_time&amp;quot;] = cast(int, o[&amp;quot;settings&amp;quot;][&amp;quot;sleep_time&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
===Pylance/Pyright===&lt;br /&gt;
 import tomllib&lt;br /&gt;
 # shows warning: Import &amp;quot;xyz&amp;quot; could not be resolved&lt;br /&gt;
 # fix by &lt;br /&gt;
 import tomllib # pyright: ignore&lt;br /&gt;
&lt;br /&gt;
===asserts function argument validation===&lt;br /&gt;
aus [https://bildungsportal.sachsen.de/opal/auth/RepositoryEntry/12518195218/CourseNode/94506982489539?0 Python Kurs von Carsten Knoll]&lt;br /&gt;
 def eine_funktion(satz, ganzzahl, zahl2, liste):&lt;br /&gt;
   if not type(satz) == str:&lt;br /&gt;
     print &amp;quot;Datentpyfehler: satz&amp;quot;&lt;br /&gt;
     return -1&lt;br /&gt;
   if not isinstance(ganzzahl, int):&lt;br /&gt;
     print &amp;quot;Datentpyfehler: ganzzahl&amp;quot;&lt;br /&gt;
     return -2&lt;br /&gt;
   if not isinstance(liste, (tuple, list)):&lt;br /&gt;
     print &amp;quot;Datentpyfehler: liste&amp;quot;&lt;br /&gt;
     return -3&lt;br /&gt;
   # Kompakteste Variante (empfohlen): &lt;br /&gt;
   assert zahl2 &amp;gt; 0, &amp;quot;Error: zahl2 ist nicht &amp;gt; 0&amp;quot; # Assertation-Error bei Nichterfuellung&lt;br /&gt;
&lt;br /&gt;
 def F(x):&lt;br /&gt;
   if not isinstance(x, (float, int)):&lt;br /&gt;
     msg = &amp;quot;Zahl erwartet, %s bekommen&amp;quot; % type(x)&lt;br /&gt;
     raise ValueError(msg)&lt;br /&gt;
   return x**2&lt;br /&gt;
better:&lt;br /&gt;
 def F(x):&lt;br /&gt;
   assert isinstance(x, (float, int)), &amp;quot;Error: x is not of type float or int&amp;quot;&lt;br /&gt;
   return x**2&lt;br /&gt;
&lt;br /&gt;
 assert variant in [&lt;br /&gt;
     &amp;quot;normal&amp;quot;,&lt;br /&gt;
     &amp;quot;gray&amp;quot;,&lt;br /&gt;
     &amp;quot;cannyedge&amp;quot;,&lt;br /&gt;
 ], &amp;quot;Error: variant is not in &#039;normal&#039;, &#039;gray&#039;, &#039;cannyedge&#039;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===Exceptions===&lt;br /&gt;
see [https://medium.com/@saadjamilakhtar/5-best-practices-for-python-exception-handling-5e54b876a20]&lt;br /&gt;
&lt;br /&gt;
====Catching====&lt;br /&gt;
Catch keyboard interrupt and do a &amp;quot;save exit&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     i = 0&lt;br /&gt;
     while 1:&lt;br /&gt;
         i += 1&lt;br /&gt;
         print(i)&lt;br /&gt;
 except KeyboardInterrupt:&lt;br /&gt;
     print(&amp;quot;stopped&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
Catch &#039;&#039;&#039;all&#039;&#039;&#039; exceptions&lt;br /&gt;
 try:&lt;br /&gt;
   [...]&lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     print(&amp;quot;Exception: &amp;quot;, e)&lt;br /&gt;
&lt;br /&gt;
====Raising Exceptions====&lt;br /&gt;
 if resp.status_code != 200:  # noqa: PLR2004&lt;br /&gt;
     msg = f&amp;quot;Bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
     raise ValueError(msg) from None&lt;br /&gt;
&lt;br /&gt;
Custom Exceptions&lt;br /&gt;
 try: &lt;br /&gt;
   raise Exception(&amp;quot;HiHo&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
 raise ValueError¶&lt;br /&gt;
&lt;br /&gt;
===perl grep and map===&lt;br /&gt;
from [https://stackoverflow.com/questions/12845288/grep-on-elements-of-a-list]&lt;br /&gt;
 def grep(list, pattern):&lt;br /&gt;
     expr = re.compile(pattern)&lt;br /&gt;
     return [elem for elem in list if expr.match(elem)]&lt;br /&gt;
 or&lt;br /&gt;
 filteredList = filter(lambda x: x &amp;lt; 7 and x &amp;gt; 2, unfilteredList)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def map(list, was, womit):&lt;br /&gt;
     return list(map(lambda i: re.sub(was, womit, i), list))&lt;br /&gt;
     # was = &#039;.*&amp;quot;(\d+)&amp;quot;.*&#039;&lt;br /&gt;
     # womit = r&amp;quot;\1&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===unit testing using pytest===&lt;br /&gt;
install via&lt;br /&gt;
 pip install pytest&lt;br /&gt;
activate in vscode, see [https://code.visualstudio.com/docs/python/testing#_enable-a-test-framework]: To enable testing, use the Python: Configure Tests command on the Command Palette.&lt;br /&gt;
&lt;br /&gt;
see https://docs.pytest.org/en/6.2.x/assert.html#assert&lt;br /&gt;
&lt;br /&gt;
test_1.py:&lt;br /&gt;
 import myLib # my custom lib to test&lt;br /&gt;
 &lt;br /&gt;
 class TestClass:&lt;br /&gt;
     def test_one(self):&lt;br /&gt;
         x = &amp;quot;this&amp;quot;&lt;br /&gt;
         assert &amp;quot;h&amp;quot; in x&lt;br /&gt;
 &lt;br /&gt;
     def test_two(self):&lt;br /&gt;
         assert myLib.multiply(1, 2) == 2&lt;br /&gt;
 &lt;br /&gt;
     def test_three(self):&lt;br /&gt;
         assert myLib.multiply(2, 2) == 2&lt;br /&gt;
&lt;br /&gt;
====project structure====&lt;br /&gt;
=====simplest structore: test next to script=====&lt;br /&gt;
Alternative without tests dir:&lt;br /&gt;
 ./main.py&lt;br /&gt;
 ./helper.py &lt;br /&gt;
 ./helper_test.py&lt;br /&gt;
with helper_test.py:&lt;br /&gt;
 from helper import fnc1&lt;br /&gt;
&lt;br /&gt;
=====standalone model with src and tests dirs=====&lt;br /&gt;
for this dir structure&lt;br /&gt;
 src/helper.py &lt;br /&gt;
 tests/helper_test.py&lt;br /&gt;
helper_test.py needs&lt;br /&gt;
 import sys&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 sys.path.insert(0, (Path(__file__).parent.parent / &amp;quot;src&amp;quot;).as_posix())&lt;br /&gt;
&lt;br /&gt;
=====standalone model with tests dir=====&lt;br /&gt;
if the test files are placed inside a ./tests/ dir&lt;br /&gt;
 ./main.py&lt;br /&gt;
 ./helper.py &lt;br /&gt;
 tests/helper_test.py&lt;br /&gt;
helper_test.py needs &lt;br /&gt;
 sys.path.insert(0, Path(__file__).parent.parent.as_posix())&lt;br /&gt;
 from helper import fnc1&lt;br /&gt;
&lt;br /&gt;
====parametrize====&lt;br /&gt;
 import pytest&lt;br /&gt;
 @pytest.mark.parametrize(&lt;br /&gt;
     (&amp;quot;test_input&amp;quot;, &amp;quot;expected&amp;quot;),&lt;br /&gt;
     [(&amp;quot;&amp;quot;, None), (&amp;quot;PT30M&amp;quot;, 30), (&amp;quot;PT3H&amp;quot;, 3 * 60), (&amp;quot;PT2H30M&amp;quot;, 2 * 60 + 30)],&lt;br /&gt;
 )&lt;br /&gt;
 def test_task_est_to_minutes(test_input: str, expected: int) -&amp;gt; None:&lt;br /&gt;
     assert task_est_to_minutes(test_input) == expected&lt;br /&gt;
&lt;br /&gt;
=====fixures: prepare and cleanup test_data=====&lt;br /&gt;
to automatically run before and after each testcase&lt;br /&gt;
 @pytest.fixture(autouse=True)&lt;br /&gt;
 def _setup_tests():  # noqa: ANN202&lt;br /&gt;
     cache_prepare_lists()&lt;br /&gt;
     cache_prepare_tasks()&lt;br /&gt;
 &lt;br /&gt;
     yield&lt;br /&gt;
 &lt;br /&gt;
     cache_cleanup_test_data()&lt;br /&gt;
&lt;br /&gt;
====Test coverage report====&lt;br /&gt;
 pip install pytest-cov&lt;br /&gt;
 # console output&lt;br /&gt;
 pytest --cov&lt;br /&gt;
 &lt;br /&gt;
 # html report in coverage_report/index.html with details per file&lt;br /&gt;
 pytest --cov --cov-report=html:coverage_report&lt;br /&gt;
to exclude a code block from coverage report, add&lt;br /&gt;
 # pragma: no cover&lt;br /&gt;
&lt;br /&gt;
=== Sleep / Wait for input ===&lt;br /&gt;
sleep for a while&lt;br /&gt;
 import time&lt;br /&gt;
 time.sleep(60)&lt;br /&gt;
&lt;br /&gt;
wait for user input&lt;br /&gt;
 input(&amp;quot;press Enter to close&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Suppress Warnings===&lt;br /&gt;
see [https://docs.python.org/3/library/warnings.html#temporarily-suppressing-warnings]&lt;br /&gt;
 import warnings&lt;br /&gt;
 warnings.filterwarnings(&amp;quot;ignore&amp;quot;, message=&amp;quot;.*native_field_num.*not found in message.*&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Logging===&lt;br /&gt;
====V4 minimal stdout logging====&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.addLevelName(logging.DEBUG, &amp;quot;D&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.INFO, &amp;quot;I&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.WARNING, &amp;quot;W&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.ERROR, &amp;quot;E&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.CRITICAL, &amp;quot;C&amp;quot;)&lt;br /&gt;
 logging.basicConfig(level=logging.INFO, format=&amp;quot;%(levelname)s: %(name)s: %(message)s&amp;quot;)&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
 LOGGER.setLevel(logging.INFO)&lt;br /&gt;
 logging.getLogger(&amp;quot;httpx&amp;quot;).setLevel(logging.WARNING)&lt;br /&gt;
 &lt;br /&gt;
 # usage of variables and rounding of numbers. (%d also rounds floats)&lt;br /&gt;
 LOGGER.info(&amp;quot;file %s has %d lines with avg %.1f char/line&amp;quot;, filename, lines, c_p_l)&lt;br /&gt;
&lt;br /&gt;
====V3 simple logging====&lt;br /&gt;
 # main file:&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.basicConfig(&lt;br /&gt;
     level=logging.INFO,&lt;br /&gt;
     format=&amp;quot;%(asctime)s\t%(levelname)s\t%(name)s\t%(message)s&amp;quot;,&lt;br /&gt;
     handlers=[&lt;br /&gt;
         RotatingFileHandler(&lt;br /&gt;
             Path(__file__).with_suffix(&amp;quot;.log&amp;quot;),&lt;br /&gt;
             maxBytes=10485760,  # 10 MB = 10*1024*1024,&lt;br /&gt;
             backupCount=1,&lt;br /&gt;
             encoding=&amp;quot;utf-8&amp;quot;,&lt;br /&gt;
         )&lt;br /&gt;
     ],&lt;br /&gt;
     datefmt=&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
 logger = logging.getLogger(Path(__file__).stem)&lt;br /&gt;
 logger.info(&amp;quot;Start&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # other files:&lt;br /&gt;
 import logging&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
&lt;br /&gt;
====V2: rotating file and STDOUT====&lt;br /&gt;
 # 1. setup&lt;br /&gt;
 import logging&lt;br /&gt;
 from logging.handlers import RotatingFileHandler&lt;br /&gt;
 &lt;br /&gt;
 logfile = &amp;quot;myApp.log&amp;quot;&lt;br /&gt;
 maxBytes = 20 * 1024 * 1024&lt;br /&gt;
 backupCount = 5&lt;br /&gt;
 loglevel_console = logging.INFO&lt;br /&gt;
 loglevel_file = logging.DEBUG&lt;br /&gt;
 &lt;br /&gt;
 # create logger&lt;br /&gt;
 logger = logging.getLogger(&amp;quot;root&amp;quot;)&lt;br /&gt;
 logger.setLevel(loglevel_file)&lt;br /&gt;
 &lt;br /&gt;
 # console handler&lt;br /&gt;
 ch = logging.StreamHandler()&lt;br /&gt;
 ch.setLevel(loglevel_console)&lt;br /&gt;
 &lt;br /&gt;
 # rotating file handler&lt;br /&gt;
 # fh = logging.FileHandler(logfile)&lt;br /&gt;
 fh = RotatingFileHandler(logfile, maxBytes=maxBytes, backupCount=backupCount)&lt;br /&gt;
 fh.setLevel(loglevel_file)&lt;br /&gt;
 &lt;br /&gt;
 # create formatter and add it to the handlers&lt;br /&gt;
 # %(name)s = LoggerName, %(threadName)s = TreadName&lt;br /&gt;
 formatter = logging.Formatter(&lt;br /&gt;
     &amp;quot;%(asctime)s - %(levelname)s - %(name)s - %(threadName)s - %(message)s &amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 fh.setFormatter(formatter)&lt;br /&gt;
 ch.setFormatter(formatter)&lt;br /&gt;
 &lt;br /&gt;
 # add the handlers to the logger&lt;br /&gt;
 logger.addHandler(ch)&lt;br /&gt;
 logger.addHandler(fh)&lt;br /&gt;
 &lt;br /&gt;
 logger.debug(&amp;quot;DebugMe&amp;quot;)&lt;br /&gt;
 logger.info(&amp;quot;Starting&amp;quot;)&lt;br /&gt;
 logger.warning(&amp;quot;Attention&amp;quot;)&lt;br /&gt;
 logger.error(&amp;quot;Something went wrong&amp;quot;)&lt;br /&gt;
 logger.critical(&amp;quot;Something seriously went wrong &amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # 2. in other files/modules now use&lt;br /&gt;
 import logging&lt;br /&gt;
 &lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 logger.info(&amp;quot;text&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 ...&lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     logger.exception(&amp;quot;Unhandeled exception&amp;quot;)&lt;br /&gt;
     quit()&lt;br /&gt;
&lt;br /&gt;
====V1: Simple====&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.basicConfig(&lt;br /&gt;
     level=logging.INFO,&lt;br /&gt;
     format=&amp;quot;%(asctime)s\t%(levelname)s\t%(message)s&amp;quot;,  # \t%(name)s&lt;br /&gt;
     filename=Path(__file__).with_suffix(&amp;quot;.log&amp;quot;), # uncomment to switch to stdout&lt;br /&gt;
     filemode=&amp;quot;a&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 logger.debug(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.info(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.warning(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.error(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.critical(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 try:&lt;br /&gt;
 ...&lt;br /&gt;
 except psycopg2.DatabaseError:&lt;br /&gt;
     logger.exception(&amp;quot;Database connection error: %s&amp;quot;)&lt;br /&gt;
     raise&lt;br /&gt;
&lt;br /&gt;
===Process Bar===&lt;br /&gt;
see [https://tqdm.github.io tqdm]&lt;br /&gt;
 from tqdm import tqdm&lt;br /&gt;
 for i in tqdm(range(10000)):&lt;br /&gt;
     ....&lt;br /&gt;
or&lt;br /&gt;
 with tqdm(total=total_iterations, desc=&amp;quot;Processing&amp;quot;) as progress_bar:&lt;br /&gt;
     ...&lt;br /&gt;
     progress_bar.update(1)&lt;br /&gt;
&lt;br /&gt;
===CGI Web development===&lt;br /&gt;
 # Print necessary headers.&lt;br /&gt;
 print(&amp;quot;Content-Type: text/html&amp;quot;)&lt;br /&gt;
 print()&lt;br /&gt;
 &lt;br /&gt;
 # errors and debugging info to browser&lt;br /&gt;
 import cgitb&lt;br /&gt;
 cgitb.enable()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Access URL or Form Parameters&lt;br /&gt;
 # V2 from https://www.tutorialspoint.com/python/python_cgi_programming.htm&lt;br /&gt;
 import cgi&lt;br /&gt;
 form = cgi.FieldStorage()&lt;br /&gt;
 username = form.getvalue(&#039;username&#039;)&lt;br /&gt;
 print(username)&lt;br /&gt;
&lt;br /&gt;
 # V1&lt;br /&gt;
 import sys&lt;br /&gt;
 import urllib.parse&lt;br /&gt;
 query = os.environ.get(&#039;QUERY_STRING&#039;)&lt;br /&gt;
 query = urllib.parse.unquote(query, errors=&amp;quot;surrogateescape&amp;quot;)&lt;br /&gt;
 d = dict(qc.split(&amp;quot;=&amp;quot;) for qc in query.split(&amp;quot;&amp;amp;&amp;quot;))&lt;br /&gt;
 print(d)&lt;br /&gt;
&lt;br /&gt;
====CGI Backend Returning JSONs====&lt;br /&gt;
 #!/usr/local/bin/python3.6&lt;br /&gt;
 # -*- coding: utf-8 -*-&lt;br /&gt;
 &lt;br /&gt;
 import cgi&lt;br /&gt;
 import json&lt;br /&gt;
 &lt;br /&gt;
 # Print necessary headers.&lt;br /&gt;
 print(&amp;quot;Content-type: application/json&amp;quot;)&lt;br /&gt;
 print()&lt;br /&gt;
 &lt;br /&gt;
 def get_form_parameter(para: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;asserts that a given parameter is set and returns its value&amp;quot;&lt;br /&gt;
     value = form.getvalue(para)&lt;br /&gt;
     assert value, f&amp;quot;Error: parameter {para} missing&amp;quot;&lt;br /&gt;
     assert value != &amp;quot;&amp;quot;, f&amp;quot;Error: parameter {para} missing&amp;quot;&lt;br /&gt;
     return value&lt;br /&gt;
  &lt;br /&gt;
 response = {}&lt;br /&gt;
 response[&#039;status&#039;] = &amp;quot;ok&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 try:&lt;br /&gt;
     action = get_form_parameter(&amp;quot;action&amp;quot;)&lt;br /&gt;
     response[&#039;action&#039;] = action&lt;br /&gt;
     if action == &amp;quot;myAction&amp;quot;:&lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     response[&#039;status&#039;] = &amp;quot;error&amp;quot;&lt;br /&gt;
     d = {&amp;quot;type&amp;quot;: str(type(e)), &amp;quot;text&amp;quot;: str(e)}&lt;br /&gt;
     response[&amp;quot;exception&amp;quot;] = d&lt;br /&gt;
 &lt;br /&gt;
 finally:&lt;br /&gt;
     print(json.dumps(response))&lt;br /&gt;
&lt;br /&gt;
==Databases==&lt;br /&gt;
===PostgreSQL===&lt;br /&gt;
====Improved helper function====&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Helper functions for DB access.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 import datetime as dt&lt;br /&gt;
 import logging&lt;br /&gt;
 &lt;br /&gt;
 import psycopg2&lt;br /&gt;
 import psycopg2.extras&lt;br /&gt;
 from creds_db import credentials as creds_db&lt;br /&gt;
 &lt;br /&gt;
 # Configure logging&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 &lt;br /&gt;
 connection: psycopg2.extensions.connection = None  # type: ignore&lt;br /&gt;
 cursor_query: psycopg2.extensions.cursor = None  # type: ignore&lt;br /&gt;
 cursor_write: psycopg2.extensions.cursor = None  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def connect() -&amp;gt; (&lt;br /&gt;
     tuple[&lt;br /&gt;
         psycopg2.extensions.connection,&lt;br /&gt;
         psycopg2.extensions.cursor,&lt;br /&gt;
         psycopg2.extensions.cursor,&lt;br /&gt;
     ]&lt;br /&gt;
 ):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Connect to the database.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     try:&lt;br /&gt;
         connection = psycopg2.connect(**creds_db)&lt;br /&gt;
         cursor_query = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)&lt;br /&gt;
         cursor_write = connection.cursor()&lt;br /&gt;
         logger.info(&lt;br /&gt;
             &amp;quot;Connected to database %s on host %s&amp;quot;,&lt;br /&gt;
             creds_db[&amp;quot;database&amp;quot;],&lt;br /&gt;
             creds_db[&amp;quot;host&amp;quot;],&lt;br /&gt;
         )&lt;br /&gt;
     except psycopg2.DatabaseError:&lt;br /&gt;
         logger.exception(&amp;quot;Database connection error: %s&amp;quot;)&lt;br /&gt;
         raise&lt;br /&gt;
     else:&lt;br /&gt;
         return connection, cursor_query, cursor_write&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def disconnect() -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Disconnect from the database.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     try:&lt;br /&gt;
         if cursor_query:&lt;br /&gt;
             cursor_query.close()&lt;br /&gt;
         if cursor_write:&lt;br /&gt;
             cursor_write.close()&lt;br /&gt;
         if connection:&lt;br /&gt;
             connection.close()&lt;br /&gt;
         logger.info(&amp;quot;Disconnected from the database&amp;quot;)&lt;br /&gt;
     except psycopg2.DatabaseError:&lt;br /&gt;
         logger.exception(&amp;quot;Error during disconnection: %s&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def exists(url: str, valid_hours: float) -&amp;gt; bool | None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Check if a record exists.&lt;br /&gt;
 &lt;br /&gt;
     Returns&lt;br /&gt;
     -------&lt;br /&gt;
     None for missing record&lt;br /&gt;
     True for valid record&lt;br /&gt;
     False for expired record&lt;br /&gt;
 &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=valid_hours)&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT delete_at &amp;gt; %s AS &amp;quot;valid&amp;quot;&lt;br /&gt;
 FROM my_table&lt;br /&gt;
 WHERE url = %s LIMIT 1;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_query.execute(sql, (delete_at, url))&lt;br /&gt;
     res = cursor_query.fetchone()&lt;br /&gt;
     return res[0] if res else None&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def query1(url: str) -&amp;gt; dict | None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Execute a query.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT *&lt;br /&gt;
 FROM my_table&lt;br /&gt;
 WHERE url = %s LIMIT 1&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_query.execute(sql, (url,))&lt;br /&gt;
     return cursor_query.fetchone()  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def insert(url: str, response: str, delete_in_hours: float) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Insert a record.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=delete_in_hours)&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 INSERT INTO my_table (url, response, delete_at)&lt;br /&gt;
 VALUES (%s, %s, %s);&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (url, response, delete_at))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def delete(url: str) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Delete a record.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 DELETE FROM my_table&lt;br /&gt;
 WHERE url = %s;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (url,))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def delete_expired(in_hours: float) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Delete records that expire in less than in_hours from now.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=in_hours)&lt;br /&gt;
 &lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 DELETE FROM my_table&lt;br /&gt;
 WHERE delete_at &amp;lt; %s;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (delete_at,))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 connection, cursor_query, cursor_write = connect()&lt;br /&gt;
&lt;br /&gt;
====Basics====&lt;br /&gt;
 import psycopg2&lt;br /&gt;
 import psycopg2.extras&lt;br /&gt;
 &lt;br /&gt;
 credentials = {&lt;br /&gt;
     &amp;quot;host&amp;quot;: &amp;quot;localhost&amp;quot;,&lt;br /&gt;
     &amp;quot;port&amp;quot;: 5432,&lt;br /&gt;
     &amp;quot;database&amp;quot;: &amp;quot;myDB&amp;quot;,&lt;br /&gt;
     &amp;quot;user&amp;quot;: &amp;quot;myUser&amp;quot;,&lt;br /&gt;
     &amp;quot;password&amp;quot;: &amp;quot;myPwd&amp;quot;,&lt;br /&gt;
 }&lt;br /&gt;
 connection = psycopg2.connect(**credentials)&lt;br /&gt;
 cursor = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)&lt;br /&gt;
 &lt;br /&gt;
 l_bind_vars = [[&amp;quot;A1&amp;quot;, &amp;quot;A2&amp;quot;], [&amp;quot;B1&amp;quot;, &amp;quot;B2&amp;quot;]]&lt;br /&gt;
 &lt;br /&gt;
 sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT * FROM myTable&lt;br /&gt;
 WHERE 1=1 &lt;br /&gt;
 AND status NOT IN (&#039;CLOSED&#039;) &lt;br /&gt;
 AND ColA = %s &lt;br /&gt;
 AND ColB = %s &lt;br /&gt;
 ORDER BY created DESC&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 cursor.execute(sql, l_bind_vars)&lt;br /&gt;
 d_data = dict(cursor.fetchone())&lt;br /&gt;
&lt;br /&gt;
====export result to csv file====&lt;br /&gt;
 sql1 = &amp;quot;SELECT * FROM table&amp;quot;&lt;br /&gt;
 sql2 = &amp;quot;COPY (&amp;quot; + sql1 + &amp;quot;) TO STDOUT WITH CSV HEADER DELIMITER &#039;\t&#039;&amp;quot;&lt;br /&gt;
         with open(&amp;quot;out.csv&amp;quot;, &amp;quot;w&amp;quot;) as file:&lt;br /&gt;
             cursor.copy_expert(sql2, file)&lt;br /&gt;
&lt;br /&gt;
====ReadConfigFile to connect to PostgreSQL====&lt;br /&gt;
from [http://www.postgresqltutorial.com/postgresql-python/connect/]&lt;br /&gt;
database.ini&lt;br /&gt;
 [postgresql]&lt;br /&gt;
 host=dbhost&lt;br /&gt;
 port=5432&lt;br /&gt;
 database=dbname&lt;br /&gt;
 user=dbuser&lt;br /&gt;
 password=dbpass&lt;br /&gt;
&lt;br /&gt;
 from configparser import ConfigParser&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def config(filename=&amp;quot;database.ini&amp;quot;, section=&amp;quot;postgresql&amp;quot;):&lt;br /&gt;
     parser = ConfigParser()&lt;br /&gt;
     parser.read(filename)&lt;br /&gt;
     # get section, default to postgresql&lt;br /&gt;
     db = {}&lt;br /&gt;
     if parser.has_section(section):&lt;br /&gt;
         params = parser.items(section)&lt;br /&gt;
         for param in params:&lt;br /&gt;
             db[param[0]] = param[1]&lt;br /&gt;
     else:&lt;br /&gt;
         raise Exception(&lt;br /&gt;
             &amp;quot;Section {0} not found in the {1} file&amp;quot;.format(section, filename)&lt;br /&gt;
         )&lt;br /&gt;
     return db&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
main.py&lt;br /&gt;
 import psycopg2&lt;br /&gt;
 from config import config&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def connect():&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Connect to the PostgreSQL database server&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     conn = None&lt;br /&gt;
     try:&lt;br /&gt;
         params = config()  # read connection parameters&lt;br /&gt;
         print(&amp;quot;Connecting to the PostgreSQL database...&amp;quot;)&lt;br /&gt;
         conn = psycopg2.connect(**params)&lt;br /&gt;
         cur = conn.cursor()  # create a cursor&lt;br /&gt;
         print(&amp;quot;PostgreSQL database version:&amp;quot;)&lt;br /&gt;
         cur.execute(&amp;quot;SELECT version()&amp;quot;)  # execute a statement&lt;br /&gt;
         db_version = cur.fetchone()&lt;br /&gt;
         print(db_version)&lt;br /&gt;
         cur.close()&lt;br /&gt;
     except (Exception, psycopg2.DatabaseError) as error:&lt;br /&gt;
         print(error)&lt;br /&gt;
     finally:&lt;br /&gt;
         if conn is not None:&lt;br /&gt;
             conn.close()&lt;br /&gt;
             print(&amp;quot;Database connection closed.&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     connect()&lt;br /&gt;
&lt;br /&gt;
===SQL Lite / SQLite===&lt;br /&gt;
see page [[SQLite]]&lt;br /&gt;
&lt;br /&gt;
===InfluxDB===&lt;br /&gt;
see [[InfluxDB]]&lt;br /&gt;
&lt;br /&gt;
==Internet Access==&lt;br /&gt;
===Send E-Mails===&lt;br /&gt;
see [[Python - eMail]]&lt;br /&gt;
&lt;br /&gt;
===Download data using browser UA / REST===&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 headers = {&lt;br /&gt;
     &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0&amp;quot;,&lt;br /&gt;
 }&lt;br /&gt;
 resp = requests.get(&lt;br /&gt;
     url,&lt;br /&gt;
     headers=headers,&lt;br /&gt;
     timeout=3,  # timeout in sec, requests should always have a timeout!&lt;br /&gt;
     # timeout=(1,3), # 1s connect-timout, 3s read-timeout&lt;br /&gt;
 )&lt;br /&gt;
 if resp.status_code != 200:  # noqa: PLR2004&lt;br /&gt;
     msg = f&amp;quot;E: bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
     raise Exception(msg)  # noqa: TRY002&lt;br /&gt;
&lt;br /&gt;
====Download only if cache is too old====&lt;br /&gt;
 import time&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 def fetch_url_or_cache(file_cache: Path, url) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Fetch URL and cache in a file.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if check_cache_file_available_and_recent(&lt;br /&gt;
         file_path=file_cache, max_age=3600, verbose=False&lt;br /&gt;
     ):&lt;br /&gt;
         with file_cache.open(mode=&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
             s = fh.read()&lt;br /&gt;
     else:&lt;br /&gt;
         s = fetch(url=url)&lt;br /&gt;
         with file_cache.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
             fh.writelines(s)&lt;br /&gt;
     return s&lt;br /&gt;
 &lt;br /&gt;
 def check_cache_file_available_and_recent(&lt;br /&gt;
     file_path: Path,&lt;br /&gt;
     max_age: int = 3500,&lt;br /&gt;
 ) -&amp;gt; bool:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Check if cache file exists and is recent.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cache_good = False&lt;br /&gt;
     if file_path.exists() and (time.time() - file_path.stat().st_mtime &amp;lt; max_age):&lt;br /&gt;
         cache_good = True&lt;br /&gt;
     return cache_good&lt;br /&gt;
 &lt;br /&gt;
 # or&lt;br /&gt;
 def check_cache_file_available_and_recent_verbose(&lt;br /&gt;
     file_path: Path, max_age: int = 3600, *, verbose: bool = False&lt;br /&gt;
 ) -&amp;gt; bool:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Check if cache file exists and is recent.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cache_good = True&lt;br /&gt;
     if not file_path.exists():&lt;br /&gt;
         if verbose:&lt;br /&gt;
             print(f&amp;quot;No Cache available: {file_path}&amp;quot;)&lt;br /&gt;
         cache_good = False&lt;br /&gt;
     if cache_good and time.time() - (time.time() - file_path.stat().st_mtime &amp;lt; max_age):&lt;br /&gt;
         if verbose:&lt;br /&gt;
             print(f&amp;quot;Cache too old: {file_path}&amp;quot;)&lt;br /&gt;
         cache_good = False&lt;br /&gt;
     return cache_good&lt;br /&gt;
 &lt;br /&gt;
 def fetch(url) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Fetch URL.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     headers = {&lt;br /&gt;
         &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0&amp;quot;,  # noqa: E501&lt;br /&gt;
     }&lt;br /&gt;
     resp = requests.get(url, headers=headers, timeout=5)&lt;br /&gt;
     if resp.status_code == 200:  # noqa: PLR2004&lt;br /&gt;
         return resp.content.decode(&amp;quot;ascii&amp;quot;)&lt;br /&gt;
     else:  # noqa: RET505&lt;br /&gt;
         msg = f&amp;quot;E: bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
         raise Exception(msg)  # noqa: TRY002&lt;br /&gt;
&lt;br /&gt;
====Download using urllib.request====&lt;br /&gt;
 from urllib.request import urlopen&lt;br /&gt;
 &lt;br /&gt;
 url = &amp;quot;https://pomber.github.io/covid19/timeseries.json&amp;quot;&lt;br /&gt;
 with urlopen(url) as response:  # noqa: S310&lt;br /&gt;
     response_text = response.read()&lt;br /&gt;
&lt;br /&gt;
===HTML parsing and extracting of elements===&lt;br /&gt;
V2: via BeautifulSoup&lt;br /&gt;
 from bs4 import BeautifulSoup  # pip install beautifulsoup4&lt;br /&gt;
 &lt;br /&gt;
 soup = BeautifulSoup(cont, features=&amp;quot;html.parser&amp;quot;)&lt;br /&gt;
 my_element = soup.find(&amp;quot;div&amp;quot;, {&amp;quot;class&amp;quot;: &amp;quot;user-formatted-inner&amp;quot;})&lt;br /&gt;
 my_body = my_element.prettify()&lt;br /&gt;
 # my_body = my_element.encode()&lt;br /&gt;
 # my_body = str(my_element)&lt;br /&gt;
&lt;br /&gt;
V1: via lxml and xpath&lt;br /&gt;
 from lxml import html&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 page = requests.get(url)&lt;br /&gt;
 tree = html.fromstring(page.content)&lt;br /&gt;
 tbody_trs = tree.xpath(&amp;quot;//*/tbody/tr&amp;quot;)&lt;br /&gt;
 l_rows = []&lt;br /&gt;
 for tr in tbody_trs:&lt;br /&gt;
     l_columns = []&lt;br /&gt;
     if len(tr) != 15:&lt;br /&gt;
         continue&lt;br /&gt;
     for td in tr:&lt;br /&gt;
         l_columns.append(td.text_content())&lt;br /&gt;
         l_rows.append(list(l_columns))&lt;br /&gt;
&lt;br /&gt;
===HTML entities to unicode===&lt;br /&gt;
 import html&lt;br /&gt;
 cont = html.unescape(cont)&lt;br /&gt;
&lt;br /&gt;
==GUI Interactions==&lt;br /&gt;
===Take Screenshot===&lt;br /&gt;
 import pyautogui # (c:\Python\Scripts\)pip install pyautogui&lt;br /&gt;
 # pyautogui does only support screenshots on monitor #1&lt;br /&gt;
 ...&lt;br /&gt;
 screenshot = pyautogui.screenshot()&lt;br /&gt;
 # screenshot = pyautogui.screenshot(region=(screenshotX,screenshotY, screenshotW, screenshotH))&lt;br /&gt;
 screenshot = np.array(screenshot) &lt;br /&gt;
 # Convert RGB to BGR &lt;br /&gt;
 screenshot = screenshot[:, :, ::-1].copy()&lt;br /&gt;
&lt;br /&gt;
===Mouse Actions===&lt;br /&gt;
 def clickIt(x,y,key=&amp;quot;&amp;quot;) :&lt;br /&gt;
   x0, y0 = pyautogui.position()&lt;br /&gt;
   if key != &amp;quot;&amp;quot;: # crtl, shift&lt;br /&gt;
     pyautogui.keyDown(key)&lt;br /&gt;
   pyautogui.moveTo(x, y, duration=0.2)&lt;br /&gt;
   pyautogui.click(x=x , y=y, button=&#039;left&#039;, clicks=1, interval=0.1)&lt;br /&gt;
   if key != &amp;quot;&amp;quot;: # crtl, shift&lt;br /&gt;
     pyautogui.keyUp(key)&lt;br /&gt;
   pyautogui.moveTo(x0, y0)&lt;br /&gt;
&lt;br /&gt;
===Web Automation===&lt;br /&gt;
 from selenium import webdriver&lt;br /&gt;
 from selenium.webdriver.common.keys import Keys&lt;br /&gt;
 &lt;br /&gt;
 # from selenium.webdriver import Firefox&lt;br /&gt;
 from selenium.webdriver.firefox.options import Options&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 import glob&lt;br /&gt;
 &lt;br /&gt;
 class StravaUserMapDL():&lt;br /&gt;
     def __init__(self):&lt;br /&gt;
         self.driver = webdriver.Firefox()&lt;br /&gt;
 &lt;br /&gt;
     def login(self):&lt;br /&gt;
         driver = self.driver&lt;br /&gt;
         url = &amp;quot;https://www.somewebpage.com&amp;quot;&lt;br /&gt;
         email = &amp;quot;myemail&amp;quot;&lt;br /&gt;
         password = &amp;quot;mypassword&amp;quot;&lt;br /&gt;
         driver.get(url)&lt;br /&gt;
 &lt;br /&gt;
         title = driver.title&lt;br /&gt;
         urlIs = driver.current_url&lt;br /&gt;
         cont = driver.page_source #  as string&lt;br /&gt;
         FILE = open(filename,&amp;quot;w&amp;quot;) # w = overWrite file ; a = append to file&lt;br /&gt;
         FILE.write(cont)&lt;br /&gt;
         FILE.close()         &lt;br /&gt;
 &lt;br /&gt;
         # handle login if urlIs != url&lt;br /&gt;
         if (urlIs != url): &lt;br /&gt;
             # activate checkbox &#039;remember_me&#039;&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;remember_me&#039;)&lt;br /&gt;
             if (elem.is_selected() == False):&lt;br /&gt;
                 elem.click()&lt;br /&gt;
             assert elem.is_selected() == True&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;email&#039;)&lt;br /&gt;
             elem.send_keys(email)&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;password&#039;)&lt;br /&gt;
             elem.send_keys(password)&lt;br /&gt;
             elem.send_keys(Keys.RETURN)&lt;br /&gt;
             # Wait until login pages is replaced by real page&lt;br /&gt;
             urlIs = driver.current_url&lt;br /&gt;
             while (urlIs != url):&lt;br /&gt;
                 time.sleep(1)&lt;br /&gt;
                 urlIs = driver.current_url&lt;br /&gt;
             print (urlIs)&lt;br /&gt;
 &lt;br /&gt;
             # results = driver.find_elements_by_class_name(&#039;following&#039;)&lt;br /&gt;
             # results = driver.find_elements_by_tag_name(&#039;li&#039;)&lt;br /&gt;
 &lt;br /&gt;
             # print(results[0].text)&lt;br /&gt;
         assert (urlIs == url)&lt;br /&gt;
&lt;br /&gt;
===Unit Tests using Web Automation===&lt;br /&gt;
 import unittest&lt;br /&gt;
 from selenium import webdriver&lt;br /&gt;
 from selenium.webdriver.common.keys import Keys&lt;br /&gt;
 &lt;br /&gt;
 #from selenium.webdriver import Firefox&lt;br /&gt;
 from selenium.webdriver.firefox.options import Options&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 class PythonOrgSearch(unittest.TestCase):&lt;br /&gt;
 #    def __init__(self,asdf):&lt;br /&gt;
 #        self.driver = webdriver.Firefox() &lt;br /&gt;
 &lt;br /&gt;
     def setUp(self):&lt;br /&gt;
         print (&amp;quot;setUp&amp;quot;)&lt;br /&gt;
         # headless mode:&lt;br /&gt;
         # opts = Options()&lt;br /&gt;
         # opts.set_headless()&lt;br /&gt;
         # assert opts.headless  # Operating in headless mode&lt;br /&gt;
         # self.driver = webdriver.Firefox(options=opts)&lt;br /&gt;
 &lt;br /&gt;
         self.driver = webdriver.Firefox()&lt;br /&gt;
 &lt;br /&gt;
     def test_search_in_python_org(self):&lt;br /&gt;
         driver = self.driver&lt;br /&gt;
         driver.get(&amp;quot;http://www.python.org&amp;quot;)&lt;br /&gt;
         self.assertIn(&amp;quot;Python&amp;quot;, driver.title)&lt;br /&gt;
         elem = driver.find_element_by_name(&amp;quot;q&amp;quot;)&lt;br /&gt;
         elem.send_keys(&amp;quot;pycon&amp;quot;)&lt;br /&gt;
         elem.send_keys(Keys.RETURN)&lt;br /&gt;
         assert &amp;quot;No results found.&amp;quot; not in driver.page_source&lt;br /&gt;
         print (&amp;quot;fertig: python_org&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     def tearDown(self):&lt;br /&gt;
         print (&amp;quot;tearDown&amp;quot;)&lt;br /&gt;
         print (&amp;quot;close Firefox&amp;quot;)&lt;br /&gt;
         self.driver.close() # close tab&lt;br /&gt;
         self.driver.quit() # quit browser&lt;br /&gt;
         # os._exit(1) # exit unittest without Exception&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     try:&lt;br /&gt;
         unittest.main()&lt;br /&gt;
     except SystemExit as e:&lt;br /&gt;
         os._exit(1)&lt;br /&gt;
&lt;br /&gt;
==Cryptography and Hashing==&lt;br /&gt;
====Hashing via SHA256====&lt;br /&gt;
 def gen_SHA256_string(s: str) -&amp;gt; str:&lt;br /&gt;
     m = hashlib.sha256()&lt;br /&gt;
     m.update(s.encode(&amp;quot;ascii&amp;quot;))&lt;br /&gt;
     return m.hexdigest()&lt;br /&gt;
&lt;br /&gt;
====Hashing via MD5====&lt;br /&gt;
(MD5 is not secure, better use SHA256)&lt;br /&gt;
 def gen_MD5_string(s: str) -&amp;gt; str:&lt;br /&gt;
     m = hashlib.md5()&lt;br /&gt;
     m.update(s.encode(&amp;quot;ascii&amp;quot;))&lt;br /&gt;
     return m.hexdigest()&lt;br /&gt;
&lt;br /&gt;
====Password hashing via bcrypt====&lt;br /&gt;
 import bcrypt&lt;br /&gt;
 pwd = &#039;geheim&#039;&lt;br /&gt;
 pwd = pwd.encode(&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 # or &lt;br /&gt;
 pwd = b&#039;geheim&#039;&lt;br /&gt;
 &lt;br /&gt;
 hashed = bcrypt.hashpw(pwd, bcrypt.gensalt())&lt;br /&gt;
 if bcrypt.checkpw(pwd, hashed):&lt;br /&gt;
     print(&amp;quot;It Matches!&amp;quot;)&lt;br /&gt;
     print(hashed.decode(&amp;quot;utf-8&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
To use version 2a instead of 2b (default):&lt;br /&gt;
 bcrypt.gensalt(prefix=b&amp;quot;2a&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Multiprocessing, subprocesses and Threading==&lt;br /&gt;
see [[Python_-_Multithreading]] as well&lt;br /&gt;
&lt;br /&gt;
use processes for CPU limited work &amp;lt;br/&amp;gt;&lt;br /&gt;
use threads for I/O limited work&lt;br /&gt;
&lt;br /&gt;
===Simple single process===&lt;br /&gt;
 import subprocess&lt;br /&gt;
 process = subprocess.run([&amp;quot;sudo&amp;quot;, &amp;quot;du&amp;quot;, &amp;quot;--max-depth=1&amp;quot;, mydir], capture_output=True, text=True)&lt;br /&gt;
 print (process.stdout)&lt;br /&gt;
old, depricated way:&lt;br /&gt;
 os.system( &amp;quot;gnuplot &amp;quot; + gpfile)&lt;br /&gt;
&lt;br /&gt;
===Multiprocessing===&lt;br /&gt;
see [[Python - Multithreading]] as well&lt;br /&gt;
&lt;br /&gt;
V2 using pool and starmap&lt;br /&gt;
 import multiprocessing&lt;br /&gt;
 import os&lt;br /&gt;
 &lt;br /&gt;
 def worker(i: int, s: str) -&amp;gt; list:&lt;br /&gt;
     result = (i, s, os.getpid())&lt;br /&gt;
     return result&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     # gen. pile of work&lt;br /&gt;
     l_pile_of_work = []&lt;br /&gt;
     for i in range(1_000):&lt;br /&gt;
         tup = (i, &amp;quot;n&amp;quot; + str(i))&lt;br /&gt;
         l_pile_of_work.append((tup))&lt;br /&gt;
     # gen pool of processes&lt;br /&gt;
     num_processes = min(multiprocessing.cpu_count(), len(l_pile_of_work))&lt;br /&gt;
     pool = multiprocessing.Pool(processes=num_processes)&lt;br /&gt;
     # start processes on pile of work&lt;br /&gt;
     l_results_unsorted = pool.starmap(&lt;br /&gt;
         func=worker, iterable=l_pile_of_work  # each item is a list of 2 parameters&lt;br /&gt;
     )&lt;br /&gt;
     &lt;br /&gt;
     # or if only one parameter:&lt;br /&gt;
     # l_results_unsorted = pool.map(doit_de_district, l_pile_of_work)&lt;br /&gt;
     &lt;br /&gt;
     l_results = sorted(l_results_unsorted)  # sort by i&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
V1&lt;br /&gt;
 import subprocess&lt;br /&gt;
 l_subprocesses = []  # queue list of subprocesses&lt;br /&gt;
 max_processes = 4&lt;br /&gt;
 &lt;br /&gt;
 def process_enqueue(new_process_parameters):&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     # wait for free slot&lt;br /&gt;
     while len(l_subprocesses) &amp;gt;= max_processes:&lt;br /&gt;
         process_remove_finished_from_queue()&lt;br /&gt;
         time.sleep(0.1)  # sleep 0.1s&lt;br /&gt;
     process = subprocess.Popen(new_process_parameters,&lt;br /&gt;
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE,&lt;br /&gt;
                                universal_newlines=True)&lt;br /&gt;
     l_subprocesses.append(process)&lt;br /&gt;
 &lt;br /&gt;
 def process_remove_finished_from_queue():&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     i = 0&lt;br /&gt;
     while i &amp;lt;= len(l_subprocesses) - 1:&lt;br /&gt;
         process = l_subprocesses[i]&lt;br /&gt;
         if process.poll != None:  # has already finished&lt;br /&gt;
             process_print_output(process)&lt;br /&gt;
             l_subprocesses.pop(i)&lt;br /&gt;
         else:  # still running&lt;br /&gt;
             i += 1&lt;br /&gt;
 &lt;br /&gt;
 def process_print_output(process):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;waits for process to finish and prints process output&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     stdout, stderr = process.communicate()&lt;br /&gt;
     if stdout != &#039;&#039;:&lt;br /&gt;
         print(f&#039;Out: {stdout}&#039;)&lt;br /&gt;
     if stderr != &#039;&#039;:&lt;br /&gt;
         print(f&#039;ERROR: {stderr}&#039;)&lt;br /&gt;
 &lt;br /&gt;
 def process_wait_for_all_finished():&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     for process in l_subprocesses:&lt;br /&gt;
         process_print_output(process)&lt;br /&gt;
     l_subprocesses = []  # empty list of done subprocesses&lt;br /&gt;
 &lt;br /&gt;
 process_enqueue(l_parameters1)&lt;br /&gt;
 ...&lt;br /&gt;
 process_enqueue(l_parameters999)&lt;br /&gt;
 process_wait_for_all_finished()&lt;br /&gt;
&lt;br /&gt;
===Threading===&lt;br /&gt;
 import threading&lt;br /&gt;
 import queue&lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 def worker(q_work: queue.Queue, results: dict):&lt;br /&gt;
     while not q_work.empty():&lt;br /&gt;
         i, s = q_work.get()&lt;br /&gt;
         time.sleep(.1)&lt;br /&gt;
         result = (i, s, os.getpid())&lt;br /&gt;
         results[i] = result&lt;br /&gt;
         q_work.task_done()&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &#039;__main__&#039;:&lt;br /&gt;
     d_results = {}  # threads can write into dict&lt;br /&gt;
     # gen. pile of work&lt;br /&gt;
     l_pile_of_work = []&lt;br /&gt;
     for i in range(1_000):&lt;br /&gt;
         tup = (i, &amp;quot;n&amp;quot;+str(i))&lt;br /&gt;
         l_pile_of_work.append((tup))&lt;br /&gt;
     # convert list of work to queue&lt;br /&gt;
     q_pile_of_work = queue.Queue(&lt;br /&gt;
         maxsize=len(l_pile_of_work))  # maxsize=0 -&amp;gt; unlimited&lt;br /&gt;
     for params in l_pile_of_work:&lt;br /&gt;
         q_pile_of_work.put(params)&lt;br /&gt;
     # gen threads&lt;br /&gt;
     num_threads = 100&lt;br /&gt;
     l_threads = []  # List of threads, not used here&lt;br /&gt;
     for i in range(num_threads):&lt;br /&gt;
         t = threading.Thread(name=&#039;myThread-&#039;+str(i),&lt;br /&gt;
                              target=worker,&lt;br /&gt;
                              args=(q_pile_of_work, d_results),&lt;br /&gt;
                              daemon=True)&lt;br /&gt;
         l_threads.append(t)&lt;br /&gt;
         t.start()&lt;br /&gt;
     q_pile_of_work.join()  # wait for all threas to complete&lt;br /&gt;
     l_results_unsorted = d_results.values()&lt;br /&gt;
     l_results = sorted(l_results_unsorted)  # sort by i&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===asyncio — Asynchronous I/O===&lt;br /&gt;
see https://docs.python.org/3/library/asyncio-task.html&lt;br /&gt;
 import asyncio&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 # basics&lt;br /&gt;
 # task = asyncio.create_task(coro())&lt;br /&gt;
 # Wrap the coro coroutine into a Task and schedule its execution. Return the Task object.&lt;br /&gt;
 &lt;br /&gt;
 # sleep&lt;br /&gt;
 # await asyncio.sleep(1)&lt;br /&gt;
 &lt;br /&gt;
 # Running Tasks Concurrently and gathers the return values in list L&lt;br /&gt;
 # L = await asyncio.gather(coro(x1,y1), coro(x2,y2), coro(x3,y3))&lt;br /&gt;
 &lt;br /&gt;
 async def say_after(delay, what):&lt;br /&gt;
     # Coroutines declared with the async/await syntax&lt;br /&gt;
     await asyncio.sleep(delay)&lt;br /&gt;
     print(what)&lt;br /&gt;
 &lt;br /&gt;
 async def main():&lt;br /&gt;
     # The asyncio.create_task() function to run coroutines concurrently as asyncio Tasks.&lt;br /&gt;
     task1 = asyncio.create_task(&lt;br /&gt;
         say_after(1, &#039;hello&#039;))&lt;br /&gt;
 &lt;br /&gt;
     task2 = asyncio.create_task(&lt;br /&gt;
         say_after(1, &#039;world&#039;))&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;started at {time.strftime(&#039;%X&#039;)}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     # Wait until both tasks are completed&lt;br /&gt;
     await task1&lt;br /&gt;
     await task2&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;finished at {time.strftime(&#039;%X&#039;)}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 asyncio.run(main())&lt;br /&gt;
&lt;br /&gt;
==Pandas==&lt;br /&gt;
see [[Pandas]]&lt;br /&gt;
&lt;br /&gt;
==Matplotlib==&lt;br /&gt;
see [[Matplotlib]]&lt;br /&gt;
&lt;br /&gt;
== GUI via tkinter==&lt;br /&gt;
 import tkinter as tk  # no need to install via pip&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 class App(tk.Tk):&lt;br /&gt;
     def __init__(self):&lt;br /&gt;
         super().__init__()&lt;br /&gt;
         self.title(&amp;quot;robot-CClicker&amp;quot;)&lt;br /&gt;
         self.geometry(&amp;quot;200x200+0+1080&amp;quot;)&lt;br /&gt;
         self.resizable(width=False, height=False)&lt;br /&gt;
 &lt;br /&gt;
         self.l_buttons = []&lt;br /&gt;
         self.__create_widgets()&lt;br /&gt;
 &lt;br /&gt;
     def __create_widgets(self):&lt;br /&gt;
         self.btn_click500 = tk.Button(&lt;br /&gt;
             master=self,&lt;br /&gt;
             text=&amp;quot;500 clicks&amp;quot;,&lt;br /&gt;
             width=20,&lt;br /&gt;
             command=lambda: self.clickBigCookie(500),&lt;br /&gt;
         )&lt;br /&gt;
         self.l_buttons.append(self.btn_click500)&lt;br /&gt;
 &lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button.pack(anchor=tk.W)&lt;br /&gt;
 &lt;br /&gt;
     def clickBigCookie(self, num):&lt;br /&gt;
         # TODO: the disabling of the buttons is not working&lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button[&amp;quot;state&amp;quot;] = tk.DISABLED&lt;br /&gt;
         helper.clickIt(self.posBigCockie[0], self.posBigCockie[1], num=num)&lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button[&amp;quot;state&amp;quot;] = tk.NORMAL&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     app = App()&lt;br /&gt;
     app.mainloop()&lt;br /&gt;
&lt;br /&gt;
==Protobuf==&lt;br /&gt;
===convert .proto file to python class===&lt;br /&gt;
see https://www.datascienceblog.net/post/programming/essential-protobuf-guide-python/&lt;br /&gt;
 protoc my_message.proto --python_out ./ &lt;br /&gt;
&lt;br /&gt;
===read/decode protobuf message===&lt;br /&gt;
parse message from file&lt;br /&gt;
 import machine_message_pb2&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.bin&amp;quot;, &amp;quot;rb&amp;quot;) as f:&lt;br /&gt;
     my_message = my_message_pb2.my_message()&lt;br /&gt;
     my_message.ParseFromString(f.read())&lt;br /&gt;
 print(machine_message)&lt;br /&gt;
&lt;br /&gt;
===create/encode protobuf message===&lt;br /&gt;
 import machine_message_pb2&lt;br /&gt;
 &lt;br /&gt;
 my_message = my_message_pb2.my_message()&lt;br /&gt;
 my_message.data.field1 = 1&lt;br /&gt;
 my_message.data.field2 = &amp;quot;asdf&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.bin&amp;quot;, &amp;quot;wb&amp;quot;) as f:&lt;br /&gt;
     f.write(my_message.data.SerializeToString())&lt;br /&gt;
&lt;br /&gt;
==venv / virtual environments==&lt;br /&gt;
 pip install --upgrade pip&lt;br /&gt;
 python -m venv .venv --prompt $(basename $(pwd))&lt;br /&gt;
 source .venv/bin/activate&lt;br /&gt;
 pip install -r requirements.txt&lt;br /&gt;
 ...&lt;br /&gt;
 deactivate&lt;br /&gt;
&lt;br /&gt;
==Pydantic data validation==&lt;br /&gt;
===Function input parameter validation===&lt;br /&gt;
see https://docs.pydantic.dev/usage/validation_decorator/&lt;br /&gt;
 @validate_arguments&lt;br /&gt;
 def sum(x: int, y: float) -&amp;gt; float:&lt;br /&gt;
     print(x, y)&lt;br /&gt;
     return x + y&lt;br /&gt;
  &lt;br /&gt;
 print(sum(1.1, 1.1))&lt;br /&gt;
 # -&amp;gt; 2.1&lt;br /&gt;
&lt;br /&gt;
==Read config file==&lt;br /&gt;
===TOML===&lt;br /&gt;
see https://learnxinyminutes.com/docs/toml/&lt;br /&gt;
&lt;br /&gt;
minimal example:&lt;br /&gt;
config.toml&lt;br /&gt;
 # SAP API endpoint&lt;br /&gt;
 [general]&lt;br /&gt;
 sleep_time = 60&lt;br /&gt;
 use_proxy = true&lt;br /&gt;
tool.py&lt;br /&gt;
 try:&lt;br /&gt;
     import tomllib  # comes with python3.11&lt;br /&gt;
 except ModuleNotFoundError:&lt;br /&gt;
     import tomli as tomllib  # pip install tomli&lt;br /&gt;
 with open(&amp;quot;config.toml&amp;quot;, &amp;quot;rb&amp;quot;) as f:&lt;br /&gt;
    o = tomllib.load(f)  # type: ignore&lt;br /&gt;
 if o[&amp;quot;settings&amp;quot;][&amp;quot;use_proxy&amp;quot;]:&lt;br /&gt;
 ...&lt;br /&gt;
for validation, see https://realpython.com/python-toml/&lt;br /&gt;
&lt;br /&gt;
====TOML Write====&lt;br /&gt;
In for deployments I sometime want to modify a TOML config file for production, based on the dev version.&lt;br /&gt;
 import tomli_w  # pip install tomli-w&lt;br /&gt;
 p_in = Path(__file__).parent.parent / &amp;quot;.streamlit/config.toml&amp;quot;&lt;br /&gt;
 p_out = p_in.parent / &amp;quot;config-prod.toml&amp;quot; &lt;br /&gt;
 with p_in.open(&amp;quot;rb&amp;quot;) as fh:&lt;br /&gt;
     o = tomllib.load(fh)&lt;br /&gt;
 o[&amp;quot;server&amp;quot;][&amp;quot;fileWatcherType&amp;quot;] = &amp;quot;none&amp;quot;&lt;br /&gt;
 del o[&amp;quot;server&amp;quot;][&amp;quot;address&amp;quot;]&lt;br /&gt;
  with p_out.open(&amp;quot;wb&amp;quot;) as fh:&lt;br /&gt;
     tomli_w.dump(o, fh)&lt;br /&gt;
&lt;br /&gt;
===ConfigParser: INI Config File Reading===&lt;br /&gt;
better use TOML&lt;br /&gt;
&lt;br /&gt;
Config.ini&lt;br /&gt;
 [Section1]&lt;br /&gt;
 Cursor         = 205E18&lt;br /&gt;
 Grandma        =  18E18&lt;br /&gt;
 Farm           =  11E18&lt;br /&gt;
 Mine           = 514E18&lt;br /&gt;
 Factory        = 155E18&lt;br /&gt;
test.py&lt;br /&gt;
 from configparser import ConfigParser&lt;br /&gt;
 &lt;br /&gt;
 config = ConfigParser(&lt;br /&gt;
     interpolation=None&lt;br /&gt;
 )  # interpolation=None -&amp;gt; treats % in values as char % instead of interpreting it&lt;br /&gt;
 config.read(&amp;quot;Config.ini&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(config.getint(&amp;quot;Section1&amp;quot;, &amp;quot;key1&amp;quot;))&lt;br /&gt;
 print(config.getfloat(&amp;quot;Section1&amp;quot;, &amp;quot;key2&amp;quot;))&lt;br /&gt;
 print(config.get(&amp;quot;Section1&amp;quot;, &amp;quot;key3&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 for sec in config.sections():&lt;br /&gt;
     d_settings = {}&lt;br /&gt;
     for key in config.options(sec):&lt;br /&gt;
         value = config.get(sec, key)&lt;br /&gt;
         d_settings[key] = value&lt;br /&gt;
         print(&amp;quot;%15s : %s&amp;quot; % (key, value))&lt;br /&gt;
&lt;br /&gt;
==VCard / vcf==&lt;br /&gt;
 import codecs&lt;br /&gt;
 import vobject  # pip install vobject&lt;br /&gt;
 &lt;br /&gt;
 obj = vobject.readComponents(codecs.open(file_in, encoding=&amp;quot;utf-8&amp;quot;).read())  # type: ignore&lt;br /&gt;
 contacts: list[vobject.base.Component] = list(obj)  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 card = contacts[0]&lt;br /&gt;
 &lt;br /&gt;
 if &amp;quot;bday&amp;quot; not in card.contents:&lt;br /&gt;
     continue&lt;br /&gt;
 &lt;br /&gt;
 # bday: remove &#039;VALUE&#039;: [&#039;DATE&#039;]&lt;br /&gt;
 card.contents[&amp;quot;bday&amp;quot;][0].params = {}  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 # remove all fields but &amp;quot;bday&amp;quot;, &amp;quot;n&amp;quot;&lt;br /&gt;
 for key in card.contents.copy():  # loop over copy, to allow for deleting keys&lt;br /&gt;
     if key not in (&amp;quot;n&amp;quot;, &amp;quot;bday&amp;quot;):&lt;br /&gt;
         del card.contents[key]&lt;br /&gt;
 &lt;br /&gt;
 # recreate fn based on n&lt;br /&gt;
 card.add(&amp;quot;fn&amp;quot;)&lt;br /&gt;
 n = card.contents[&amp;quot;n&amp;quot;][0]&lt;br /&gt;
 fn = f&amp;quot;{n.value.given} {n.value.additional} {n.value.family}&amp;quot;  # type: ignore&lt;br /&gt;
 fn = re.sub(r&amp;quot;\s+&amp;quot;, &amp;quot; &amp;quot;, fn)&lt;br /&gt;
 card.fn.value = fn  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.vcf&amp;quot;, mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fhOut:&lt;br /&gt;
     fhOut.write(card.serialize())&lt;br /&gt;
&lt;br /&gt;
==Sentry Exception monitoring==&lt;br /&gt;
see [[Sentry]]&lt;br /&gt;
==MQTT==&lt;br /&gt;
 #!/usr/bin/env python3.12&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Read power data from Tasmota device via MQTT.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 import json&lt;br /&gt;
 from typing import Any&lt;br /&gt;
 &lt;br /&gt;
 import paho.mqtt.client as mqtt&lt;br /&gt;
 from mqtt_credentials import hostname, password, port, username&lt;br /&gt;
 from paho.mqtt.reasoncodes import ReasonCode&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_connect(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     flags: dict[str, int],&lt;br /&gt;
     reason_code: ReasonCode,&lt;br /&gt;
     properties: Any,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when the client connects MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     if reason_code == 0:&lt;br /&gt;
         print(&amp;quot;Connected to MQTT&amp;quot;)&lt;br /&gt;
         # print(f&amp;quot;MQTT protocol version: {mqtt_client._protocol}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         # Subscribing in on_connect() means that if we lose the connection and&lt;br /&gt;
         # reconnect then subscriptions will be renewed.&lt;br /&gt;
         mqtt_client.message_callback_add(&amp;quot;tele/tasmota_MT681/SENSOR&amp;quot;, on_message)&lt;br /&gt;
         mqtt_client.subscribe([(&amp;quot;tele/tasmota_MT681/SENSOR&amp;quot;, 0)])&lt;br /&gt;
 &lt;br /&gt;
     else:&lt;br /&gt;
         print(f&amp;quot;Connection to MQTT broker failed. Error: {reason_code}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_disconnect(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     reason_code: ReasonCode,&lt;br /&gt;
     properties: Any,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when the client is disconnected from the MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     if reason_code &amp;gt; 0:&lt;br /&gt;
         print(f&amp;quot;Unexpected disconnection. Error: {reason_code}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_message(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     message: mqtt.MQTTMessage,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when a Tasmota message is received from the MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     s = message.payload.decode()&lt;br /&gt;
     data = json.loads(s)&lt;br /&gt;
     # {&#039;Time&#039;: &#039;2024-03-16T06:44:08&#039;, &#039;MT681&#039;: {&#039;Total_in&#039;: 16.4396, &#039;Power_cur&#039;: 80, &#039;Total_out&#039;: 14.6403}}  # noqa: E501&lt;br /&gt;
 &lt;br /&gt;
     total_i = data[&amp;quot;MT681&amp;quot;][&amp;quot;Total_in&amp;quot;]&lt;br /&gt;
     total_o = data[&amp;quot;MT681&amp;quot;][&amp;quot;Total_out&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;In:\t{total_i}\nOut:\t{total_o}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     mqtt_client.disconnect()&lt;br /&gt;
     mqtt_client.loop_stop()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 try:&lt;br /&gt;
     # Create an MQTT client instance&lt;br /&gt;
     mqtt_client: mqtt.Client = mqtt.Client(&lt;br /&gt;
         mqtt.CallbackAPIVersion.VERSION2, &amp;quot;Python-Client-1&amp;quot;&lt;br /&gt;
     )  # type: ignore&lt;br /&gt;
     mqtt_client.username_pw_set(username, password)&lt;br /&gt;
 &lt;br /&gt;
     mqtt_client.on_connect = on_connect&lt;br /&gt;
 &lt;br /&gt;
     # Connect to the MQTT broker&lt;br /&gt;
     mqtt_client.connect(hostname, port, keepalive=60)&lt;br /&gt;
 &lt;br /&gt;
     # NOT: while True, as that eats the CPU !!!&lt;br /&gt;
     mqtt_client.loop_forever()&lt;br /&gt;
 except KeyboardInterrupt:&lt;br /&gt;
     print(&amp;quot;KeyboardInterrupt, disconnecting from MQTT broker.&amp;quot;)&lt;br /&gt;
     mqtt_client.disconnect()&lt;br /&gt;
     mqtt_client.loop_stop()&lt;br /&gt;
&lt;br /&gt;
==Caching Functions==&lt;br /&gt;
 from functools import lru_cache&lt;br /&gt;
 &lt;br /&gt;
 @lru_cache(maxsize=128)  # None equals to @cache declarator&lt;br /&gt;
 def fnc(n:int):&lt;br /&gt;
&lt;br /&gt;
==Performance/Resources==&lt;br /&gt;
===RAM/Memory consumption/bottleneck===&lt;br /&gt;
 import tracemalloc&lt;br /&gt;
 tracemalloc.start()&lt;br /&gt;
 &lt;br /&gt;
 # ...&lt;br /&gt;
 # Code&lt;br /&gt;
 # example:&lt;br /&gt;
 lst = list(range(1_000_000))&lt;br /&gt;
 # ...&lt;br /&gt;
 &lt;br /&gt;
 max_bytes = tracemalloc.get_traced_memory()[0]&lt;br /&gt;
 print(f&amp;quot;{round(max_bytes / 10**6)}MB&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 tracemalloc.stop()&lt;br /&gt;
&lt;br /&gt;
===Profiling / Count and Runtime per Function===&lt;br /&gt;
 call_stats = {}&lt;br /&gt;
 &lt;br /&gt;
 def track_function_usage(func: Callable) -&amp;gt; Callable:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Annotation for gathering runtime statistics.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     @wraps(func)&lt;br /&gt;
     def wrapper(*args: tuple, **kwargs: dict):  # noqa: ANN202&lt;br /&gt;
         start_time = time.time()&lt;br /&gt;
         result = func(*args, **kwargs)  # Call the original function&lt;br /&gt;
         end_time = time.time()&lt;br /&gt;
         call_stats[func.__name__][&amp;quot;calls&amp;quot;] += 1&lt;br /&gt;
         call_stats[func.__name__][&amp;quot;total_time&amp;quot;] += end_time - start_time&lt;br /&gt;
         return result&lt;br /&gt;
     return wrapper&lt;br /&gt;
&lt;br /&gt;
==JWT Token==&lt;br /&gt;
  try:&lt;br /&gt;
      decoded_token = jwt.decode(token, options={&amp;quot;verify_signature&amp;quot;: False})&lt;br /&gt;
  except jwt.ExpiredSignatureError:&lt;br /&gt;
      msg = &amp;quot;Token has expired.&amp;quot;&lt;br /&gt;
      logger.exception(msg)&lt;br /&gt;
  except jwt.InvalidTokenError:&lt;br /&gt;
      msg = &amp;quot;Invalid token.&amp;quot;&lt;br /&gt;
      logger.exception(msg)&lt;br /&gt;
&lt;br /&gt;
==Streamlit==&lt;br /&gt;
see https://github.com/entorb/streamlit-examples for a collection of my examples&lt;br /&gt;
&lt;br /&gt;
===Minimum Example===&lt;br /&gt;
 # src/app.py&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import streamlit as st&lt;br /&gt;
 from streamlit.logger import get_logger&lt;br /&gt;
 logger = get_logger(Path(__file__).stem)&lt;br /&gt;
 logger.info(&amp;quot;Start&amp;quot;)&lt;br /&gt;
 st.set_page_config(page_title=&amp;quot;AppTitle&amp;quot;, page_icon=None, layout=&amp;quot;wide&amp;quot;)&lt;br /&gt;
 st.title(&amp;quot;MyPageTitle&amp;quot;)&lt;br /&gt;
 st.header(&amp;quot;MyHeader&amp;quot;)&lt;br /&gt;
 st.subheader(&amp;quot;MySubHeader&amp;quot;)&lt;br /&gt;
 sel_param = st.selectbox(&amp;quot;Parameter&amp;quot;, (&amp;quot;count&amp;quot;, &amp;quot;sum&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
 # .streamlit/config.toml&lt;br /&gt;
 [browser]&lt;br /&gt;
 gatherUsageStats = false&lt;br /&gt;
 &lt;br /&gt;
 [server]&lt;br /&gt;
 port = 8501&lt;br /&gt;
&lt;br /&gt;
 # run it&lt;br /&gt;
 streamlit run src/app.py&lt;br /&gt;
&lt;br /&gt;
====Reduce Logging for K8s Deployed Docker Containers====&lt;br /&gt;
 # reduce log output&lt;br /&gt;
 for logger_name in [&lt;br /&gt;
     &amp;quot;streamlit.runtime.caching.cache_utils&amp;quot;,&lt;br /&gt;
     &amp;quot;streamlit.runtime.caching.storage.in_memory_cache_storage_wrapper&amp;quot;,&lt;br /&gt;
     &amp;quot;streamlit.runtime.runtime&amp;quot;,&lt;br /&gt;
     &amp;quot;urllib3.connectionpool&amp;quot;,&lt;br /&gt;
 ]:&lt;br /&gt;
     get_logger(logger_name).setLevel(logging.WARNING)&lt;br /&gt;
&lt;br /&gt;
===Chart via Altair===&lt;br /&gt;
====Line Chart====&lt;br /&gt;
 chart = st.line_chart(df[&amp;quot;value&amp;quot;])&lt;br /&gt;
 # corresponds to&lt;br /&gt;
 import altair as alt&lt;br /&gt;
 c = (&lt;br /&gt;
    alt.Chart(df)&lt;br /&gt;
    .mark_line()&lt;br /&gt;
    .encode(&lt;br /&gt;
        x=alt.X(&amp;quot;created&amp;quot;, title=None),&lt;br /&gt;
        y=alt.Y(&amp;quot;value&amp;quot;, title=None),&lt;br /&gt;
    )&lt;br /&gt;
 )&lt;br /&gt;
 st.altair_chart(c, use_container_width=True)&lt;br /&gt;
&lt;br /&gt;
====Bar Chart====&lt;br /&gt;
 chart = (&lt;br /&gt;
     alt.Chart(df)&lt;br /&gt;
     .mark_bar()&lt;br /&gt;
     .encode(&lt;br /&gt;
         x=alt.X(&amp;quot;yearmonth(date):T&amp;quot;, title=&amp;quot;Month&amp;quot;),&lt;br /&gt;
         y=alt.Y(f&amp;quot;count:Q&amp;quot;, title=None),&lt;br /&gt;
         color=&amp;quot;status:N&amp;quot;,&lt;br /&gt;
         tooltip=[&amp;quot;yearmonth(date):T&amp;quot;, &amp;quot;status:N&amp;quot;, &amp;quot;cnt:Q&amp;quot;],&lt;br /&gt;
     )&lt;br /&gt;
     # .properties(width=600, height=400)&lt;br /&gt;
 )&lt;br /&gt;
 st.altair_chart(chart, use_container_width=True)&lt;br /&gt;
&lt;br /&gt;
 :T stands for Temporal. It indicates that the field contains date or time values.&lt;br /&gt;
 :N stands for Nominal. It indicates that the field contains categorical data, which represents discrete categories or labels.&lt;br /&gt;
 :Q stands for Quantitative. It indicates that the field contains numerical data that can be measured and compared.&lt;br /&gt;
&lt;br /&gt;
===Excel Download===&lt;br /&gt;
 def excel_download_buttons(df: pd.DataFrame, file_name: str = &amp;quot;export.xlsx&amp;quot;) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Show prepare data and download buttons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if st.button(label=&amp;quot;Click to prepare download&amp;quot;):&lt;br /&gt;
         buffer = io.BytesIO()&lt;br /&gt;
         with pd.ExcelWriter(buffer, engine=&amp;quot;xlsxwriter&amp;quot;) as writer:&lt;br /&gt;
             df.to_excel(writer, sheet_name=&amp;quot;Sheet1&amp;quot;, index=False)&lt;br /&gt;
             writer.close()&lt;br /&gt;
 &lt;br /&gt;
             st.download_button(&lt;br /&gt;
                 label=&amp;quot;Download data as Excel&amp;quot;,&lt;br /&gt;
                 data=buffer,&lt;br /&gt;
                 file_name=file_name,&lt;br /&gt;
                 mime=&amp;quot;application/vnd.ms-excel&amp;quot;,&lt;br /&gt;
             )&lt;br /&gt;
&lt;br /&gt;
==UV Package Manager==&lt;br /&gt;
 # create/update lockfile&lt;br /&gt;
 uv lock --upgrade&lt;br /&gt;
 # sync venv&lt;br /&gt;
 uv sync --upgrade&lt;br /&gt;
 # extract package info to requirements.txt&lt;br /&gt;
 uv export --format requirements.txt --no-dev --no-hashes -o requirements.txt&lt;br /&gt;
&lt;br /&gt;
Update Python version for my current project&lt;br /&gt;
 uv venv --python=3.13.7&lt;br /&gt;
&lt;br /&gt;
===Update UV===&lt;br /&gt;
 uv self update&lt;br /&gt;
 # or&lt;br /&gt;
 brew update &amp;amp;&amp;amp; brew upgrade uv&lt;br /&gt;
&lt;br /&gt;
===Update Python Versions===&lt;br /&gt;
 uv python upgrade&lt;br /&gt;
&lt;br /&gt;
===UV + Docker on readOnlyRootFilesystem===&lt;br /&gt;
Example 1: https://github.com/SWE-Group-23/CM22007---Source/blob/10d02004f3a4208f7b30d37a0a87e89532a23575/create-service.sh#L117&lt;br /&gt;
&lt;br /&gt;
Dockerfile&lt;br /&gt;
 WORKDIR /app&lt;br /&gt;
 COPY . .&lt;br /&gt;
 &lt;br /&gt;
 ENV CASS_DRIVER_NO_EXTENSIONS=1&lt;br /&gt;
 &lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache/uv \\&lt;br /&gt;
     --mount=type=bind,source=uv.lock,target=uv.lock \\&lt;br /&gt;
     --mount=type=bind,source=pyproject.toml,target=pyproject.toml \\&lt;br /&gt;
     uv sync --frozen --no-install-project&lt;br /&gt;
     &lt;br /&gt;
 RUN uv pip install -e shared/&lt;br /&gt;
 &lt;br /&gt;
 RUN addgroup -S group1 &amp;amp;&amp;amp; adduser -S user1 -G group1 -u 1000&lt;br /&gt;
 RUN chown -R user1:group1 /app&lt;br /&gt;
 USER user1:group1&lt;br /&gt;
 &lt;br /&gt;
 CMD [&amp;quot;uv&amp;quot;, &amp;quot;run&amp;quot;, &amp;quot;main.py&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
Deployment&lt;br /&gt;
 securityContext:&lt;br /&gt;
   readOnlyRootFilesystem: true&lt;br /&gt;
   allowPrivilegeEscalation: false&lt;br /&gt;
   runAsNonRoot: true&lt;br /&gt;
   runAsUser: 1000&lt;br /&gt;
   capabilities:&lt;br /&gt;
     drop:&lt;br /&gt;
       - ALL&lt;br /&gt;
   seccompProfile:&lt;br /&gt;
     type: RuntimeDefault&lt;br /&gt;
 volumeMounts:&lt;br /&gt;
   - mountPath: /home/user1/.cache/uv&lt;br /&gt;
     name: uv&lt;br /&gt;
   - mountPath: /tmp&lt;br /&gt;
     name: temp&lt;br /&gt;
&lt;br /&gt;
Example 2: [https://hynek.me/articles/docker-uv/ Production-ready Python Docker Containers with uv]&lt;br /&gt;
&lt;br /&gt;
Dockerfile (without the comments)&lt;br /&gt;
 ENV UV_LINK_MODE=copy \&lt;br /&gt;
     UV_COMPILE_BYTECODE=1 \&lt;br /&gt;
     UV_PYTHON_DOWNLOADS=never \&lt;br /&gt;
     UV_PYTHON=python3.12 \&lt;br /&gt;
     UV_PROJECT_ENVIRONMENT=/app&lt;br /&gt;
 &lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache \&lt;br /&gt;
     --mount=type=bind,source=uv.lock,target=uv.lock \&lt;br /&gt;
     --mount=type=bind,source=pyproject.toml,target=pyproject.toml \&lt;br /&gt;
     uv sync \&lt;br /&gt;
         --locked \&lt;br /&gt;
         --no-dev \&lt;br /&gt;
         --no-install-project&lt;br /&gt;
 &lt;br /&gt;
 COPY . /src&lt;br /&gt;
 WORKDIR /src&lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache \&lt;br /&gt;
     uv sync \&lt;br /&gt;
         --locked \&lt;br /&gt;
         --no-dev \&lt;br /&gt;
         --no-editable&lt;br /&gt;
&lt;br /&gt;
=== Vulture: Search for dead code ===&lt;br /&gt;
see [https://github.com/jendrikseipp/vulture GitHub]&lt;br /&gt;
&lt;br /&gt;
pyproject.toml&lt;br /&gt;
 [tool.vulture]&lt;br /&gt;
 paths = [&amp;quot;src&amp;quot;]&lt;br /&gt;
 # exclude = [&amp;quot;src/models.py&amp;quot;]&lt;br /&gt;
 ignore_names = [&lt;br /&gt;
     &amp;quot;LOGGER&amp;quot;,&lt;br /&gt;
 ]&lt;br /&gt;
&lt;br /&gt;
.pre-commit-config.yaml&lt;br /&gt;
   # Vulture: find dead code&lt;br /&gt;
   - repo: https://github.com/jendrikseipp/vulture&lt;br /&gt;
     rev: v2.16&lt;br /&gt;
     hooks:&lt;br /&gt;
       - id: vulture&lt;br /&gt;
&lt;br /&gt;
run&lt;br /&gt;
 pip install vulture&lt;br /&gt;
 vulture src/&lt;br /&gt;
 &lt;br /&gt;
 uv add --dev vulture&lt;br /&gt;
 uv run vulture src/&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Python&amp;diff=5405</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5405"/>
		<updated>2026-04-12T12:29:27Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* UV + Docker on readOnlyRootFilesystem */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Coding]][[Category:Python]]&lt;br /&gt;
&lt;br /&gt;
==Getting Started==&lt;br /&gt;
===Install===&lt;br /&gt;
====Python====&lt;br /&gt;
Best use [https://docs.astral.sh/uv/ UV] for managing python and package versions.&lt;br /&gt;
&lt;br /&gt;
* for Windows: get and install Python from https://www.python.org&lt;br /&gt;
* for MacOS: use [https://github.com/pyenv/pyenv?tab=readme-ov-file#a-getting-pyenv pyenv]&lt;br /&gt;
 brew install xz &lt;br /&gt;
 brew install pyenv&lt;br /&gt;
 &lt;br /&gt;
 echo &#039;export PYENV_ROOT=&amp;quot;$HOME/.pyenv&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 echo &#039;[ [ -d $PYENV_ROOT/bin ] ] &amp;amp;&amp;amp; export PATH=&amp;quot;$PYENV_ROOT/bin:$PATH&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 echo &#039;eval &amp;quot;$(pyenv init - zsh)&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 exec &amp;quot;$SHELL&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 pyenv install --list&lt;br /&gt;
 pyenv install 3.13.5&lt;br /&gt;
 pyenv global 3.13.5&lt;br /&gt;
 &lt;br /&gt;
 vim ~/.zshrc &lt;br /&gt;
 # add &lt;br /&gt;
 if command -v pyenv 1&amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then&lt;br /&gt;
   eval &amp;quot;$(pyenv init -)&amp;quot;&lt;br /&gt;
 fi&lt;br /&gt;
&lt;br /&gt;
====Editor: Visual Studio Code====&lt;br /&gt;
excellent and free source-code editor that supports many languages.&lt;br /&gt;
* get and install from https://code.visualstudio.com&lt;br /&gt;
&lt;br /&gt;
See Wickie page [[Visual_Studio_Code]] for general setup, extension, config...&lt;br /&gt;
&lt;br /&gt;
Python Extensions&lt;br /&gt;
* Python&lt;br /&gt;
* Pylance&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff Ruff Formater and Linter]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items/?itemName=tamasfe.even-better-toml Even Better TOML]&lt;br /&gt;
Settings (CTRL + ,)&lt;br /&gt;
* Extensions -&amp;gt; Python -&amp;gt; Formatting: Provider = black&lt;br /&gt;
* Text Editor -&amp;gt; Editor: Format On Save&lt;br /&gt;
* Text Editor -&amp;gt; Files:Eol -&amp;gt; \n&lt;br /&gt;
* Linter: Ruff&lt;br /&gt;
Settings in settings.json&lt;br /&gt;
 &amp;quot;python.analysis.completeFunctionParens&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.autoImportCompletions&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.inlayHints.functionReturnTypes&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.typeCheckingMode&amp;quot;: &amp;quot;strict&amp;quot;,&lt;br /&gt;
 // Ruff&lt;br /&gt;
 &amp;quot;[python]&amp;quot;: {&lt;br /&gt;
   &amp;quot;editor.defaultFormatter&amp;quot;: &amp;quot;charliermarsh.ruff&amp;quot;,&lt;br /&gt;
   &amp;quot;editor.formatOnSave&amp;quot;: true,&lt;br /&gt;
   &amp;quot;editor.codeActionsOnSave&amp;quot;: {&lt;br /&gt;
     &amp;quot;source.fixAll&amp;quot;: &amp;quot;explicit&amp;quot;,&lt;br /&gt;
     &amp;quot;source.organizeImports.ruff&amp;quot;: &amp;quot;explicit&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 },&lt;br /&gt;
&lt;br /&gt;
Run your code&lt;br /&gt;
 CTRL + F5 : run&lt;br /&gt;
 F5        : run in debugger&lt;br /&gt;
&lt;br /&gt;
====Code Formatter Ruff====&lt;br /&gt;
use software for handling the code formatting like &amp;quot;ruff&amp;quot; or &amp;quot;black&amp;quot;, where Ruff is much faster and additionally provides linting.&lt;br /&gt;
 pip install ruff&lt;br /&gt;
than activate in editor like vs code&lt;br /&gt;
&lt;br /&gt;
===My Template===&lt;br /&gt;
see latest version at https://github.com/entorb/template-python&lt;br /&gt;
&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Main file.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 # by Torben Menke https://entorb.net &lt;br /&gt;
 import logging&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 def init_logging() -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Initialize logging.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     logging.addLevelName(logging.DEBUG, &amp;quot;D&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.INFO, &amp;quot;I&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.WARNING, &amp;quot;W&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.ERROR, &amp;quot;E&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.CRITICAL, &amp;quot;C&amp;quot;)&lt;br /&gt;
     logging.basicConfig(&lt;br /&gt;
         level=logging.INFO, format=&amp;quot;%(levelname)s: %(name)s: %(message)s&amp;quot;&lt;br /&gt;
     )&lt;br /&gt;
     logging.getLogger(&amp;quot;httpx&amp;quot;).setLevel(logging.WARNING)&lt;br /&gt;
 &lt;br /&gt;
 init_logging()&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
 &lt;br /&gt;
 def main():&lt;br /&gt;
     LOGGER.info(&amp;quot;Moin Moin&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     main()&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Other file&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 import logging&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
&lt;br /&gt;
===Compile to .exe===&lt;br /&gt;
 pip install pyinstaller&lt;br /&gt;
 pyinstaller --onefile --console your.py&lt;br /&gt;
 # for Excel and Matplotlib these options are required&lt;br /&gt;
 --hidden-import=openpyxl --hidden-import=matplotlib --hidden-import pandas.plotting._matplotlib &lt;br /&gt;
([[Python - py2exe]] is deprecated)&lt;br /&gt;
&lt;br /&gt;
==Basics==&lt;br /&gt;
=== Naming Conventions===&lt;br /&gt;
[https://google.github.io/styleguide/pyguide.html Google Python Style Guide]:&lt;br /&gt;
 module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_CONSTANT_NAME, global_var_name, instance_var_name, function_parameter_name, local_var_name&lt;br /&gt;
&lt;br /&gt;
====Doc Strings====&lt;br /&gt;
It is best practice to start a file with a docstring:&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;My Script&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
This can be accessed linke this:&lt;br /&gt;
 title = __doc__[:-1]  # type: ignore&lt;br /&gt;
&lt;br /&gt;
===Installing packages===&lt;br /&gt;
 python -m pip install --upgrade pip&lt;br /&gt;
 &lt;br /&gt;
 pip install somemodule&lt;br /&gt;
 # or &lt;br /&gt;
 pip3 install somemodule&lt;br /&gt;
 # or read from file&lt;br /&gt;
 pip install -r requirements.txt&lt;br /&gt;
 &lt;br /&gt;
 # uninstall&lt;br /&gt;
 pip uninstall somemodule&lt;br /&gt;
 &lt;br /&gt;
 # using a web proxy&lt;br /&gt;
 # set proxy for windows cmd session&lt;br /&gt;
 SET HTTPS_PROXY=http://myProxy:8080&lt;br /&gt;
 (afterwards --proxy setting below no longer required&lt;br /&gt;
 or&lt;br /&gt;
 pip install --proxy http://myProxy:8080 somemodule&lt;br /&gt;
 &lt;br /&gt;
 # list outdated packages&lt;br /&gt;
 pip list --outdated&lt;br /&gt;
 &lt;br /&gt;
 # update package&lt;br /&gt;
 pip install --upgrade pyinstaller&lt;br /&gt;
 &lt;br /&gt;
 # updating all via Windows Powershell (from [https://thecesrom.dev/2021/05/26/the-one-liner-for-updating-pip-and-all-outdated-packages/])&lt;br /&gt;
 pip freeze | %{$_.split(&#039;==&#039;)[0]} | %{pip install --upgrade $_}&lt;br /&gt;
 &lt;br /&gt;
 # updating all via Bash (from [https://thecesrom.dev/2021/05/26/the-one-liner-for-updating-pip-and-all-outdated-packages/])&lt;br /&gt;
 pip freeze | grep -v &#039;^\-e&#039; | cut -d = -f 1  | xargs -n1 pip install --upgrade&lt;br /&gt;
 &lt;br /&gt;
 # downgrade&lt;br /&gt;
 pip install --upgrade pandas==1.2.4&lt;br /&gt;
&lt;br /&gt;
====Oneline Updater for requirements.txt====&lt;br /&gt;
https://pkgui.com/pip&lt;br /&gt;
&lt;br /&gt;
====update packages====&lt;br /&gt;
 pip install pip-review&lt;br /&gt;
 pip-review --auto&lt;br /&gt;
&lt;br /&gt;
====generate requirements.txt====&lt;br /&gt;
 pip install pipreqs&lt;br /&gt;
 pipreqs ./&lt;br /&gt;
&lt;br /&gt;
===Loops===&lt;br /&gt;
ATTENTION: The loops do not create a new variable scope. Only functions and modules introduce a new scope!&lt;br /&gt;
 for p in all_files:&lt;br /&gt;
     print(p)&lt;br /&gt;
 &lt;br /&gt;
 for i, p in enumerate(all_files):&lt;br /&gt;
     print(i, p)&lt;br /&gt;
 &lt;br /&gt;
 for i in range(10):&lt;br /&gt;
     print(i)&lt;br /&gt;
 # del i &lt;br /&gt;
 &lt;br /&gt;
 while i &amp;lt;= 100:&lt;br /&gt;
     i += 1&lt;br /&gt;
     ...&lt;br /&gt;
     if sth1:&lt;br /&gt;
         continue # start next loop&lt;br /&gt;
     if sth2:&lt;br /&gt;
         break # exit loop&lt;br /&gt;
 &lt;br /&gt;
 # inline if (requires a dummy else):&lt;br /&gt;
 print(&amp;quot;something&amp;quot;) if sth else 0&lt;br /&gt;
&lt;br /&gt;
===Math===&lt;br /&gt;
see [[Python - Math]] for linear regression&lt;br /&gt;
&lt;br /&gt;
Modulo&lt;br /&gt;
 15 % 4&lt;br /&gt;
 # &amp;gt; 3&lt;br /&gt;
&lt;br /&gt;
Integer Division&lt;br /&gt;
 17 // 4&lt;br /&gt;
 # &amp;gt; 4&lt;br /&gt;
&lt;br /&gt;
====Random====&lt;br /&gt;
 import random&lt;br /&gt;
 random.randint(1000000, 9999999)&lt;br /&gt;
&lt;br /&gt;
==Basic Objects==&lt;br /&gt;
===Variables===&lt;br /&gt;
 del var  # delete / undef a variable&lt;br /&gt;
 var = None  # sets to null&lt;br /&gt;
 &lt;br /&gt;
 # check if variable is defined&lt;br /&gt;
 if &amp;quot;var&amp;quot; in locals():&lt;br /&gt;
     pass&lt;br /&gt;
 # for object oriented projects:&lt;br /&gt;
 if &amp;quot;var&amp;quot; in self.__dict__:&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # Access global variables in functions&lt;br /&gt;
 var = 123&lt;br /&gt;
 &lt;br /&gt;
 def test() -&amp;gt; None:&lt;br /&gt;
     global var  # point to global instead of creation of local var&lt;br /&gt;
     var = 321&lt;br /&gt;
&lt;br /&gt;
===Strings===&lt;br /&gt;
 # num &amp;lt;-&amp;gt; str&lt;br /&gt;
 s = str(i)  # int to string&lt;br /&gt;
 f = float(s)  # str -&amp;gt; float&lt;br /&gt;
 i = int(s)&lt;br /&gt;
 str(round(f, 1))  # round first&lt;br /&gt;
 # tests&lt;br /&gt;
 s.isdigit()  # 0-9&lt;br /&gt;
 # note isdecimal() does also not match &#039;1.1&#039;&lt;br /&gt;
 &lt;br /&gt;
 # printf: 1 digit&lt;br /&gt;
 s = f&amp;quot;{value:0.1f}&amp;quot;&lt;br /&gt;
 s = &amp;quot;%0.1f&amp;quot; % value&lt;br /&gt;
&lt;br /&gt;
====Modify Strings==== &lt;br /&gt;
 # get string from prompt&lt;br /&gt;
 s = input(&amp;quot;Enter Text: &amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 s = s.strip()  # trim spaces from both sides, rstrip for right only&lt;br /&gt;
 s = s.lower()  # lower case&lt;br /&gt;
 s = s.upper()  # upper case&lt;br /&gt;
 s = s.title()  # upper case for first char of word&lt;br /&gt;
 &lt;br /&gt;
 # upper case first letter of each word and also removes multiple and trailing spaces&lt;br /&gt;
 import string&lt;br /&gt;
 s = string.capwords(s)&lt;br /&gt;
 &lt;br /&gt;
 # replace&lt;br /&gt;
 s.replace(x, y)&lt;br /&gt;
 &lt;br /&gt;
 # trim whitespaces from left and right&lt;br /&gt;
 s.strip()&lt;br /&gt;
 &lt;br /&gt;
 # replace all (multiple) whitespaces by single space &#039; &#039;&lt;br /&gt;
 s = &amp;quot; &amp;quot;.join(s.split())&lt;br /&gt;
 &lt;br /&gt;
 # generate key value pairs from dict&lt;br /&gt;
 # key1=value1&amp;amp;key2=value2&lt;br /&gt;
 param_str = &amp;quot;&amp;amp;&amp;quot;.join(&amp;quot;=&amp;quot;.join(tup) for tup in dict.items())&lt;br /&gt;
 &lt;br /&gt;
 # repeat string multiple times&lt;br /&gt;
 s * 5  # = s+s+s+s+s&lt;br /&gt;
&lt;br /&gt;
====Substrings====&lt;br /&gt;
 # find a substring:&lt;br /&gt;
 if x in s:  # True / False&lt;br /&gt;
     pass&lt;br /&gt;
 if len(s) &amp;gt; 0:&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # handling substrings&lt;br /&gt;
 a = &amp;quot;abcd&amp;quot;&lt;br /&gt;
 b = a[:1] + &amp;quot;o&amp;quot; + a[2:]&lt;br /&gt;
 # &amp;gt; &#039;aocd&#039;&lt;br /&gt;
 &lt;br /&gt;
 s = &amp;quot;Hello there !bob@&amp;quot;&lt;br /&gt;
 i1 = s.find(&amp;quot;!&amp;quot;) + 1&lt;br /&gt;
 i2 = s.find(&amp;quot;@&amp;quot;)&lt;br /&gt;
 substr = s[i1:i2]&lt;br /&gt;
 &lt;br /&gt;
 def substr_between(s: str, s1: str, s2: str) -&amp;gt; str:&lt;br /&gt;
     assert s1 in s, f&amp;quot;E: can&#039;t find &#039;{s1}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     assert s2 in s, f&amp;quot;E: can&#039;t find &#039;{s1}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     i1 = s.find(s1) + len(s1)&lt;br /&gt;
     i2 = s.find(s2)&lt;br /&gt;
     assert i1 &amp;lt; i2, f&amp;quot;E: &#039;{s1}&#039; not before &#039;{s2}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     return s[i1:i2]&lt;br /&gt;
&lt;br /&gt;
====Binary, raw strings, html encoding====&lt;br /&gt;
 # Binary Strings&lt;br /&gt;
 sb = b&amp;quot;asdf&amp;quot;&lt;br /&gt;
 # or&lt;br /&gt;
 sb = str.encode(&amp;quot;asdf&amp;quot;)&lt;br /&gt;
 s = sb.decode(&amp;quot;ascii&amp;quot;)  # decode binary strings&lt;br /&gt;
 sb = s.encode(&amp;quot;ascii&amp;quot;)  # encode string to binary&lt;br /&gt;
 &lt;br /&gt;
 # raw string&lt;br /&gt;
 s = r&amp;quot;c:\Windows&amp;quot;  # no escape of \  needed&lt;br /&gt;
 &lt;br /&gt;
 # convert utf-8 to html umlaute&lt;br /&gt;
 s = &amp;quot;Nürnberg&amp;quot;.encode(&amp;quot;ascii&amp;quot;, &amp;quot;xmlcharrefreplace&amp;quot;).decode()&lt;br /&gt;
 # -&amp;gt; N&amp;amp;#252;rnberg&lt;br /&gt;
&lt;br /&gt;
====Guess encoding via chardet====&lt;br /&gt;
 import chardet  # pip install chardet&lt;br /&gt;
 result = chardet.detect(raw_data)&lt;br /&gt;
 if result[&amp;quot;encoding&amp;quot;] and result[&amp;quot;confidence&amp;quot;] &amp;gt; 0.5:  # noqa: PLR2004&lt;br /&gt;
     encoding = result[&amp;quot;encoding&amp;quot;]&lt;br /&gt;
 else:&lt;br /&gt;
     encoding = &amp;quot;utf-8&amp;quot;&lt;br /&gt;
&lt;br /&gt;
there is a much faster alternative [https://github.com/PyYoshi/cChardet cchardet], but that requires Microsoft Visual C++ 14.0&lt;br /&gt;
&lt;br /&gt;
====Merge variables in string / sprintf====&lt;br /&gt;
 print(&amp;quot;Renner =&amp;quot;, i)&lt;br /&gt;
 print(&amp;quot;Renner = %3d&amp;quot; % i)  # leading 0&#039;s&lt;br /&gt;
 print(f&amp;quot;Renner = {i}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # place formatted numbers in a string / sprintf&lt;br /&gt;
 s = &amp;quot;The {:.1f}% {} cost {:05.2f} euros&amp;quot;.format( 5.1, &amp;quot;beer&amp;quot;, 3.50)&lt;br /&gt;
 print(s)&lt;br /&gt;
 # &amp;gt; The 5.1% beer cost 03.50 euros&lt;br /&gt;
 &lt;br /&gt;
 s = f&amp;quot;The length is {72.8958:.2f} meters&amp;quot;&lt;br /&gt;
 # &amp;gt; The length is 72.90 meters&lt;br /&gt;
&lt;br /&gt;
===Lists===&lt;br /&gt;
like @array in Perl&lt;br /&gt;
 lst = [1, 2, 3, 4, 5, 6]&lt;br /&gt;
 lst = [x / 2 for x in range(10)]&lt;br /&gt;
 lst = [None] * 10 # Initiate list of None elements:&lt;br /&gt;
 len(lst) # length&lt;br /&gt;
 lst2 = lst[0:10]  # get elements 0-10&lt;br /&gt;
 for i in lst:&lt;br /&gt;
     print(i)&lt;br /&gt;
&lt;br /&gt;
====clone list====&lt;br /&gt;
 # dangerous: creates a link to the original list&lt;br /&gt;
 list_2 = my_list  # M&#039;s elements are LINKS to L&#039;s&lt;br /&gt;
 # clones can be achieved via:&lt;br /&gt;
 list_2 = my_list.copy&lt;br /&gt;
 list_2 = my_list[:]&lt;br /&gt;
 list_2 = list(my_list)&lt;br /&gt;
&lt;br /&gt;
====list modifications====&lt;br /&gt;
 lst.pop()  # returns and removes the last item&lt;br /&gt;
 lst.pop(i)  # returns and removes the item at  position int i&lt;br /&gt;
 lst.insert(i, x)  # insert item x at position int i&lt;br /&gt;
 lst.remove(x)  # removes the first occurrence of  item x&lt;br /&gt;
 lst.append(x)  # append a single element&lt;br /&gt;
 lst.extend(m)  # append elements of another list&lt;br /&gt;
 &lt;br /&gt;
 lst.reverse()  # reverse the order of the list&lt;br /&gt;
 lst = sorted([&amp;quot;B&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;c&amp;quot;], key=str.casefold)  #  case insensitive / ignore case&lt;br /&gt;
 &lt;br /&gt;
 # list to string&lt;br /&gt;
 s = &amp;quot;&amp;quot;.join(lst)&lt;br /&gt;
 s = &amp;quot;\n&amp;quot;.join(lst)&lt;br /&gt;
 # string to list&lt;br /&gt;
 lst = s.split()  # default: split on space&lt;br /&gt;
 lst = s.split(&amp;quot;,&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # remove empty values from end&lt;br /&gt;
 while L[-1] == &amp;quot;&amp;quot;:&lt;br /&gt;
     L.pop()&lt;br /&gt;
 &lt;br /&gt;
 # check and remove items from list&lt;br /&gt;
  # from https://stackoverflow.com/a/6024599&lt;br /&gt;
  # iterates in-situ via reversed index&lt;br /&gt;
 for i in range(len(lst) - 1, -1, -1):&lt;br /&gt;
     element = lst[i]&lt;br /&gt;
     if check(element):&lt;br /&gt;
         del lst[i]&lt;br /&gt;
&lt;br /&gt;
====check if item is in list====&lt;br /&gt;
 x in lst&lt;br /&gt;
 x not in lst&lt;br /&gt;
 lst.count(x)  # how many items x are in the list&lt;br /&gt;
 i = lst.index(&amp;quot;word&amp;quot;)  # find in list / returns the  position of the first match in list&lt;br /&gt;
&lt;br /&gt;
====pair 2 lists====&lt;br /&gt;
 # zip: merge 2 lists to list of tuples&lt;br /&gt;
 data = list(zip(data_x, data_y, strict=True))&lt;br /&gt;
 &lt;br /&gt;
 # unzip: split list of pairs into 2 lists&lt;br /&gt;
 data_x, data_y = zip(*data, strict=True)&lt;br /&gt;
 &lt;br /&gt;
 # Cartesian product of lists / tuples&lt;br /&gt;
 import itertools&lt;br /&gt;
 for i in itertools.product(*list_of_lists):&lt;br /&gt;
     print(i)&lt;br /&gt;
&lt;br /&gt;
====sort multidim list====&lt;br /&gt;
 lst = sorted(lst, key=lambda x: x[0], reverse=False)&lt;br /&gt;
 lst = sorted(lst, key=lambda row: (row[&amp;quot;Wann&amp;quot;], row [&amp;quot;Wer&amp;quot;]), reverse=False)&lt;br /&gt;
&lt;br /&gt;
====filter list====&lt;br /&gt;
 lines = [x for x in lines if not x.startswith (&amp;quot;word&amp;quot;)]&lt;br /&gt;
 lst = [&amp;quot;asdf&amp;quot;, &amp;quot;asdf2&amp;quot;, &amp;quot;qwertz&amp;quot;]&lt;br /&gt;
 lst = [elem for elem in lst if &amp;quot;asdf&amp;quot; in elem]&lt;br /&gt;
&lt;br /&gt;
====for each element====&lt;br /&gt;
 # trim/strip spaces for each element&lt;br /&gt;
 lines = [s.strip() for s in lines]&lt;br /&gt;
 # modify each item in list by adding constant string&lt;br /&gt;
 l = [s + &#039;;&#039; + v for v in l]&lt;br /&gt;
  &lt;br /&gt;
 # modify item in list&lt;br /&gt;
 for idx, line in enumerate(cont):&lt;br /&gt;
     if &amp;quot;K1001/1&amp;quot; in line:&lt;br /&gt;
         line = &amp;quot;K1001/1 Test Nr &amp;quot; + str(i) + &amp;quot;\n&amp;quot;&lt;br /&gt;
         cont[idx] = line&lt;br /&gt;
         break&lt;br /&gt;
&lt;br /&gt;
===Tuples===&lt;br /&gt;
Ordered sequence, with no ability to replace or delete items&lt;br /&gt;
 tpl = (1,2,3,4,5,6)&lt;br /&gt;
&lt;br /&gt;
list -&amp;gt; tuple&lt;br /&gt;
 tpl = tuple(lst)&lt;br /&gt;
&lt;br /&gt;
combine 2 tuples&lt;br /&gt;
 tpl = tpl1 + tpl2&lt;br /&gt;
&lt;br /&gt;
===Dictionaries===&lt;br /&gt;
like %hash in Perl&lt;br /&gt;
 d = {&amp;quot;key1&amp;quot;: &amp;quot;value1&amp;quot;, &amp;quot;key2&amp;quot;: 123}&lt;br /&gt;
 d[&amp;quot;key3&amp;quot;] = (1, 2, 3)&lt;br /&gt;
 del d[&amp;quot;key3&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
 len(d)&lt;br /&gt;
 d.clear()&lt;br /&gt;
 d.copy()&lt;br /&gt;
 d.keys()&lt;br /&gt;
 d.values()&lt;br /&gt;
 d.items()  # returns a list of tuples (key, value)&lt;br /&gt;
 d.get(k)  # returns value of key k&lt;br /&gt;
 d.get(k, x)  # returns value of key k; if k is not  in d it returns x&lt;br /&gt;
 count = d.get(&amp;quot;key&amp;quot;, 0) + 1 # nice for counters&lt;br /&gt;
 d.pop(k)  # returns and removes item k&lt;br /&gt;
 d.pop(k, x)  # returns and removes item k; if k is  not in d it returns x&lt;br /&gt;
 # checks&lt;br /&gt;
 x in d&lt;br /&gt;
 x not in d&lt;br /&gt;
 &lt;br /&gt;
 # dict can have tuple as key&lt;br /&gt;
 tpl = (&amp;quot;key1&amp;quot;, &amp;quot;key2&amp;quot;, 123)&lt;br /&gt;
 d[tpl] = 123456&lt;br /&gt;
 &lt;br /&gt;
 # loop over all key-value pairs&lt;br /&gt;
 for key, value in d.items():&lt;br /&gt;
     print(f&amp;quot;{key} = {value}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # sorted keys:&lt;br /&gt;
 for key in sorted(dict.keys()):&lt;br /&gt;
     pass&lt;br /&gt;
 # sorted values, reversed&lt;br /&gt;
 for key, value in sorted(d.items(), key=lambda item:  item[1], reverse=True):&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # join / merge 2 dicts&lt;br /&gt;
 d.update(d2)&lt;br /&gt;
 &lt;br /&gt;
 # flatten dict of dict&lt;br /&gt;
 flat = d.copy()&lt;br /&gt;
 meta = d.pop(&amp;quot;metadata&amp;quot;)&lt;br /&gt;
 flat.update(meta)&lt;br /&gt;
&lt;br /&gt;
====MultiDim Dictionaries====&lt;br /&gt;
 d = {}&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;] = {}&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;][&amp;quot;value&amp;quot;] = 1.909e18&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;][&amp;quot;name&amp;quot;] = &amp;quot;Item 1&amp;quot;&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;] = {}&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;][&amp;quot;value&amp;quot;] = 1.725e18&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;][&amp;quot;name&amp;quot;] = &amp;quot;Item 2&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 for k in d.keys():&lt;br /&gt;
     d[k][&amp;quot;value&amp;quot;] = d[k][&amp;quot;value&amp;quot;] * 1.1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def find_common_strings(dict1: dict, dict2: dict) -&amp;gt; dict[Any, Any]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Find common strings in two dict, returning a new dict of those strings.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     def recursive_match(d1: dict, d2: dict) -&amp;gt; dict[Any, Any]:&lt;br /&gt;
         common_dict = {}&lt;br /&gt;
         for key in d1:  # noqa: PLC0206&lt;br /&gt;
             if key in d2:&lt;br /&gt;
                 if isinstance(d1[key], dict) and isinstance(d2[key], dict):&lt;br /&gt;
                     # Recurse if both values are dicts&lt;br /&gt;
                     nested_common = recursive_match(d1[key], d2[key])&lt;br /&gt;
                     if nested_common:  # Add to result if common values are found&lt;br /&gt;
                         common_dict[key] = nested_common&lt;br /&gt;
                 elif isinstance(d1[key], str) and isinstance(d2[key], str):&lt;br /&gt;
                     # Check if both are strings and identical&lt;br /&gt;
                     if d1[key] == d2[key]:&lt;br /&gt;
                         common_dict[key] = d1[key]&lt;br /&gt;
         return common_dict&lt;br /&gt;
 &lt;br /&gt;
     return recursive_match(dict1, dict2)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def update_dict_with_new_values(original_dict: dict, new_values: dict) -&amp;gt; dict:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Update strings in the original dict with new values from the new_values dict.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     def recursive_update(d1: dict, new_vals: dict) -&amp;gt; None:&lt;br /&gt;
         for key in new_vals:  # noqa: PLC0206&lt;br /&gt;
             if key in d1:&lt;br /&gt;
                 if isinstance(d1[key], dict) and isinstance(new_vals[key], dict):&lt;br /&gt;
                     # Recurse if both values are dicts&lt;br /&gt;
                     recursive_update(d1[key], new_vals[key])&lt;br /&gt;
                 elif isinstance(d1[key], str) and isinstance(new_vals[key], str):&lt;br /&gt;
                     # Update the string&lt;br /&gt;
                     d1[key] = new_vals[key]&lt;br /&gt;
 &lt;br /&gt;
     recursive_update(original_dict, new_values)&lt;br /&gt;
     return original_dict&lt;br /&gt;
&lt;br /&gt;
===Datetime, Date and Time===&lt;br /&gt;
 import datetime as dt&lt;br /&gt;
 from zoneinfo import ZoneInfo&lt;br /&gt;
 TZ_DE = ZoneInfo(&amp;quot;Europe/Berlin&amp;quot;)&lt;br /&gt;
 TZ_UTC = ZoneInfo(&amp;quot;UTC&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
====Create====&lt;br /&gt;
 # date&lt;br /&gt;
 date = dt.date(2023, 12, 31)&lt;br /&gt;
 date = dt.date.fromisocalendar(int(year), int(week),  int(daynum))  # daynum: 1..7&lt;br /&gt;
 date = dt.date.fromisoformat(&amp;quot;2020-03-10&amp;quot;)&lt;br /&gt;
 date = dt.datetime.strptime(datestr, &amp;quot;%y%m%d&amp;quot;).date()&lt;br /&gt;
 date_today = dt.datetime.now(tz=dt.UTC).date()&lt;br /&gt;
 date_yesterday = dt.datetime.now(tz=TZ_DE).date() -  dt.timedelta(days=1)&lt;br /&gt;
 days_overdue = (date_completed - date_due).days&lt;br /&gt;
 # to add one month (which is not supported by timedelta) &lt;br /&gt;
 from dateutil.relativedelta import relativedelta&lt;br /&gt;
 date_end = date_start + relativedelta(months=1)&lt;br /&gt;
 delta_days = 1 + (end - start).days&lt;br /&gt;
 &lt;br /&gt;
 # datetime&lt;br /&gt;
 dt_now = dt.datetime.now(tz=TZ_DE)&lt;br /&gt;
 my_dt = dt.datetime(2023, 12, 31, 14, 31, 56,  tzinfo=TZ_DE)  # 2023-12-31 14:31:56&lt;br /&gt;
 my_dt = dt.datetime.fromtimestamp(my_timestamp,  tz=TZ_DE)&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat (&amp;quot;2017-01-01T12:30:59.000000&amp;quot;)&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(&amp;quot;2020-03-10  06:01:01+00:00&amp;quot;)&lt;br /&gt;
 s = &amp;quot;2020-03-10T06:01:01Z&amp;quot;&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(s.replace(&amp;quot;Z&amp;quot;, &amp;quot; +00:00&amp;quot;))&lt;br /&gt;
 my_date_local = my_dt.astimezone(tz=TZ_DE).date()&lt;br /&gt;
 &lt;br /&gt;
 # datetime -&amp;gt; date&lt;br /&gt;
 my_date = my_dt.date()&lt;br /&gt;
 # date -&amp;gt; datetime&lt;br /&gt;
 my_date = dt.date(2023, 12, 31)&lt;br /&gt;
 my_dt = dt.datetime(my_date.year, my_date.month,  my_date.day, tzinfo=TZ_DE)&lt;br /&gt;
 &lt;br /&gt;
 # to string&lt;br /&gt;
 date_str = my_dt.strftime(&amp;quot;%y%m%d&amp;quot;)&lt;br /&gt;
 date_str = my_dt.strftime(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;)&lt;br /&gt;
 # alternative using time&lt;br /&gt;
 date_str = time.strftime(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;)&lt;br /&gt;
 # now in UTC without milliseconds&lt;br /&gt;
 date_str = dt.datetime.now(tz=dt.timezone.utc).replace(microsecond=0).isoformat() + &amp;quot;Z&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # remove timezone&lt;br /&gt;
 my_dt = my_dt.replace(tzinfo=None)&lt;br /&gt;
 &lt;br /&gt;
 # German format&lt;br /&gt;
 import locale&lt;br /&gt;
 locale.setlocale(locale.LC_ALL, &amp;quot;de_DE&amp;quot;)&lt;br /&gt;
 print(my_date.strftime(&amp;quot;%a %x&amp;quot;))  # Mo 25.12.2023&lt;br /&gt;
 &lt;br /&gt;
 # Calendar week&lt;br /&gt;
 week = my_date.isocalendar()[1]&lt;br /&gt;
 print(&amp;quot;KW%02d&amp;quot; % week)&lt;br /&gt;
 &lt;br /&gt;
 # first day of quarter&lt;br /&gt;
 def get_first_day_of_the_quarter(my_date: dt.date) -&amp;gt; dt.date:&lt;br /&gt;
     return dt.date(my_date.year, 1 + 3 * ((my_date.month - 1) // 3), 1)&lt;br /&gt;
&lt;br /&gt;
====rounding datetimes to minutes====&lt;br /&gt;
 def floor_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Floor (=round down) minutes to X min  resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     min_new = res * (my_dt.minute // res)&lt;br /&gt;
     return my_dt.replace(minute=min_new, second=0,  microsecond=0)&lt;br /&gt;
 &lt;br /&gt;
 def ceil_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Ceil (=round up) minutes to X min resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    min_new = res * (1 + my_dt.minute // res)&lt;br /&gt;
    return my_dt.replace(minute=0, second=0, microsecond=0) + dt.timedelta(&lt;br /&gt;
        minutes=min_new&lt;br /&gt;
    )&lt;br /&gt;
 &lt;br /&gt;
 def round_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Cound minutes to X min resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    min_old_dec = float(my_dt.minute) + float(my_dt.second * 60)&lt;br /&gt;
    min_new = res * round(min_old_dec / res)&lt;br /&gt;
    return my_dt.replace(minute=0, second=0, microsecond=0) + dt.timedelta(&lt;br /&gt;
        minutes=min_new&lt;br /&gt;
    )&lt;br /&gt;
 &lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(&amp;quot;2011-11-04  00:05:23+00:00&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;original: {my_dt}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;floored: {floor_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;ceileded: {ceil_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;rounded: {round_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
====Timing / Time elapsed====&lt;br /&gt;
 import time &lt;br /&gt;
 timestart = time.time()&lt;br /&gt;
 sec = time.time() - timestart&lt;br /&gt;
 print(f&amp;quot;Time elapsed: {sec:.2f} sec&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==OS, Argpars, etc.==&lt;br /&gt;
===Hostname===&lt;br /&gt;
 from socket import gethostname&lt;br /&gt;
 print(gethostname())&lt;br /&gt;
&lt;br /&gt;
===Checking Operating System===&lt;br /&gt;
 import os&lt;br /&gt;
 import sys&lt;br /&gt;
  &lt;br /&gt;
 if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
     print(&amp;quot;posix/Unix/Linux&amp;quot;)&lt;br /&gt;
 elif os.name == &amp;quot;nt&amp;quot;:&lt;br /&gt;
     print(&amp;quot;windows&amp;quot;)&lt;br /&gt;
 else:&lt;br /&gt;
     print(&amp;quot;unknown os&amp;quot;)&lt;br /&gt;
     sys.exit(1)  # throws exception, use quit() to   close / die silently&lt;br /&gt;
&lt;br /&gt;
===Get filename of python script===&lt;br /&gt;
 my_file_path = __file__&lt;br /&gt;
&lt;br /&gt;
alternative using sys package&lt;br /&gt;
 from sys import argv&lt;br /&gt;
 myFilename = argv[0]&lt;br /&gt;
&lt;br /&gt;
===accessing os envrionment variables===&lt;br /&gt;
 import os&lt;br /&gt;
 print(os.getenv(&amp;quot;tmp&amp;quot;))&lt;br /&gt;
 # better:&lt;br /&gt;
 try:&lt;br /&gt;
     s = os.environ[key] # throws error if unset&lt;br /&gt;
 except KeyError:&lt;br /&gt;
     error_message(f&amp;quot;Environment variable {key} not set.&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Command Line Arguments===&lt;br /&gt;
====ArgumentParser====&lt;br /&gt;
 import argparse&lt;br /&gt;
 &lt;br /&gt;
 parser = (&lt;br /&gt;
     argparse.ArgumentParser()&lt;br /&gt;
 )  # construct the argument parser and parse the  arguments&lt;br /&gt;
 # -h comes automatically&lt;br /&gt;
 &lt;br /&gt;
 # Boolean Parameter&lt;br /&gt;
 parser.add_argument(&lt;br /&gt;
     &amp;quot;-v&amp;quot;, &amp;quot;--verbose&amp;quot;, help=&amp;quot;increase output  verbosity&amp;quot;, action=&amp;quot;store_true&amp;quot;&lt;br /&gt;
 )  # store_true -&amp;gt; Boolean Value&lt;br /&gt;
 &lt;br /&gt;
 # Choice Parameter&lt;br /&gt;
 # restrict to a list of possible values / choices&lt;br /&gt;
 # parser.add_argument(&amp;quot;--choice&amp;quot;, type=int, choices= [0, 1, 2], help=&amp;quot;Test choices&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Positional Parameter (like text.py 123)&lt;br /&gt;
 # parser.add_argument(&amp;quot;num&amp;quot;, type=int, help=&amp;quot;Number  of things&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Required Parameter&lt;br /&gt;
 # parser.add_argument(&amp;quot;-i&amp;quot;, &amp;quot;--input&amp;quot;, type=str,  required=True, help=&amp;quot;Path of file&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Optional Parameter&lt;br /&gt;
 parser.add_argument(&amp;quot;-n&amp;quot;, &amp;quot;--number&amp;quot;, type=int,  help=&amp;quot;Number of clicks&amp;quot;)&lt;br /&gt;
 # Optional Parameter with Default&lt;br /&gt;
 parser.add_argument(&lt;br /&gt;
     &amp;quot;-s&amp;quot;,&lt;br /&gt;
     &amp;quot;--seconds&amp;quot;,&lt;br /&gt;
     type=int,&lt;br /&gt;
     default=sec_default,&lt;br /&gt;
     help=&amp;quot;Duration of clicking, default = %i (sec)&amp;quot;  % sec_default,&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 args = vars(parser.parse_args())&lt;br /&gt;
 &lt;br /&gt;
 if args[&amp;quot;verbose&amp;quot;]:&lt;br /&gt;
     pass  # do nothing&lt;br /&gt;
 # print (&amp;quot;verbosity turned on&amp;quot;)&lt;br /&gt;
 if args[&amp;quot;number&amp;quot;]:&lt;br /&gt;
     print(&amp;quot;num=%i&amp;quot; % args[&amp;quot;number&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
==== match case statement ====&lt;br /&gt;
(new in python 3.10)&lt;br /&gt;
 match args:&lt;br /&gt;
   case {&amp;quot;sap&amp;quot;: &amp;quot;prod&amp;quot;, &amp;quot;version&amp;quot;: 1}:&lt;br /&gt;
     ...&lt;br /&gt;
   case {&amp;quot;sap&amp;quot;: &amp;quot;prod&amp;quot;, &amp;quot;version&amp;quot;: 2}:&lt;br /&gt;
     ...&lt;br /&gt;
   case _: # default case&lt;br /&gt;
&lt;br /&gt;
==File Access==&lt;br /&gt;
===Pathlib===&lt;br /&gt;
for migrating see table [https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module]&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 p = Path(&amp;quot;dir/test.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 log_file = Path(__file__).with_suffix(&amp;quot;.log&amp;quot;)  # __file__ is name of python script&lt;br /&gt;
 &lt;br /&gt;
 my_fname = p.name  #  alternative to basename&lt;br /&gt;
 my_ext = p.suffix&lt;br /&gt;
 (filepath_without_ext, file_ext) = (p.stem, p.suffix)&lt;br /&gt;
 my_dir = p.parent&lt;br /&gt;
 my_parent_dir = p.parents[1]&lt;br /&gt;
 p2 = p.with_suffix(&amp;quot;.json&amp;quot;)&lt;br /&gt;
 p3 = p.parent / (p.name + &amp;quot;-autofix.tex&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # checks&lt;br /&gt;
 Path(my_file_str).exists()&lt;br /&gt;
 Path(my_file_str).is_file()&lt;br /&gt;
 Path(my_file_str).is_dir()&lt;br /&gt;
 &lt;br /&gt;
 # loop over glob of files matching wildcard&lt;br /&gt;
 for file_out in Path(&amp;quot;mydir&amp;quot;).glob(&amp;quot;*-autofix.tex&amp;quot;):&lt;br /&gt;
 &lt;br /&gt;
 # loop over dirs&lt;br /&gt;
 p = Path() # cwd&lt;br /&gt;
 list_of_repos = [x for x in p.iterdir() if x.is_dir() and (x / &amp;quot;.git&amp;quot;).is_dir()]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
alternative using old os functions&lt;br /&gt;
Split path into folder, filename, ext&lt;br /&gt;
 import os&lt;br /&gt;
 (dirName, fileName) = os.path.split(f)&lt;br /&gt;
 (fileBaseName, fileExtension) = os.path.splitext(fileName)&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-out.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===File Manipulations: copy, move, delete, touch===&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 # copy&lt;br /&gt;
 from shutil import copyfile&lt;br /&gt;
 copyfile(Path(&amp;quot;file-source.txt&amp;quot;), Path(&amp;quot;dir&amp;quot;) / Path(&amp;quot;file-target.txt&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # move / rename&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).replace(Path(&amp;quot;file2.txt&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # delete&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).unlink()&lt;br /&gt;
 &lt;br /&gt;
 # touch&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).touch()&lt;br /&gt;
&lt;br /&gt;
===File size and timestamp===&lt;br /&gt;
 # size&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).stat().st_size&lt;br /&gt;
 &lt;br /&gt;
 # timestamp last modified&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).stat().st_mtime&lt;br /&gt;
&lt;br /&gt;
===Dir create/mkdir and delete===&lt;br /&gt;
 # create dir&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 Path(&amp;quot;myDir1/myDir2&amp;quot;).mkdir(parents=True, exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
 # delete dir&lt;br /&gt;
 import shutil&lt;br /&gt;
 shutil.rmtree(d)&lt;br /&gt;
&lt;br /&gt;
===Loop over Directories===&lt;br /&gt;
====Fetch Dir Contents / Loop over Files====&lt;br /&gt;
glob via pathlib&lt;br /&gt;
 for fileOut in Path(&amp;quot;mydir&amp;quot;).glob(&amp;quot;*-autofix.tex&amp;quot;):&lt;br /&gt;
&lt;br /&gt;
Get list of files in directory, filter dirs from list, filter by ext&lt;br /&gt;
 dir= &amp;quot;/path/to/some/dir&amp;quot;&lt;br /&gt;
 listoffiles = [ f for f in os.listdir(dir) if os.path.isfile(os.path.join(dir ,f)) and f.lower()[-4:] == &amp;quot;.gpx&amp;quot;]&lt;br /&gt;
 listoffiles.sort()&lt;br /&gt;
&lt;br /&gt;
====Traverse in Subdirs====&lt;br /&gt;
 # walk into path an fetch all files matching extension jpe?g&lt;br /&gt;
 files = []&lt;br /&gt;
 for (dirpath, dirnames, filenames) in os.walk(&amp;quot;.&amp;quot;):&lt;br /&gt;
     dirpath = dirpath.replace(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;)&lt;br /&gt;
     for file in filenames:&lt;br /&gt;
         if file.endswith(&amp;quot;.txt&amp;quot;):&lt;br /&gt;
             files.append(dirpath + &amp;quot;/&amp;quot; + file)&lt;br /&gt;
         elif re.search(r&amp;quot;\.jpe?g$&amp;quot;, file, re.IGNORECASE):&lt;br /&gt;
             files.append(dirpath + &amp;quot;/&amp;quot; + file)&lt;br /&gt;
&lt;br /&gt;
==File Parsing==&lt;br /&gt;
===File General===&lt;br /&gt;
====File Read====&lt;br /&gt;
 path_file_in = Path(&amp;quot;file.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # via pathlib&lt;br /&gt;
 cont = path_file_in.read_text(encoding=&amp;quot;utf-8&amp;quot;) # note: utf-8-sig for UTF-8-BOM&lt;br /&gt;
 &lt;br /&gt;
 # general&lt;br /&gt;
 with path_file_in.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
     cont = fh.read()&lt;br /&gt;
     # or&lt;br /&gt;
     lst = fh.readlines()&lt;br /&gt;
     # or&lt;br /&gt;
     line = fh.readline()&lt;br /&gt;
     # or&lt;br /&gt;
     for line in fh:&lt;br /&gt;
         print(line)&lt;br /&gt;
 &lt;br /&gt;
 fh = path_file_in.open(encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 ...&lt;br /&gt;
 fh.close()&lt;br /&gt;
&lt;br /&gt;
====File Write====&lt;br /&gt;
 path_file_out = Path(&amp;quot;out/1/out.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # write via pathlib&lt;br /&gt;
 path_file_out.write_text(&amp;quot;some text&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # write general&lt;br /&gt;
 with path_file_out.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     # w = overWrite file ; a = append to file&lt;br /&gt;
     # If running Python in Windows, &amp;quot;\n&amp;quot; is automatically replaced by &amp;quot;\r\n&amp;quot;.&lt;br /&gt;
     # To prevent this use newline=&#039;\n&#039;&lt;br /&gt;
     fh.writelines(lst)  # no linebreaks&lt;br /&gt;
     # or&lt;br /&gt;
     fh.write(&amp;quot;\n&amp;quot;.join(lst))&lt;br /&gt;
     # or&lt;br /&gt;
     for line in lst:&lt;br /&gt;
         fh.write(line)&lt;br /&gt;
 &lt;br /&gt;
     # Force update of file contents without closing it&lt;br /&gt;
     fh.flush()&lt;br /&gt;
 &lt;br /&gt;
 # alternative&lt;br /&gt;
 fh = path_file_out.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;)&lt;br /&gt;
 ...&lt;br /&gt;
 fh.close()&lt;br /&gt;
&lt;br /&gt;
===CSV===&lt;br /&gt;
====CSV Read====&lt;br /&gt;
 import csv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 with Path(&amp;quot;data.csv&amp;quot;).open(mode=&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as fh:  # for Excel use ANSI&lt;br /&gt;
     csv_reader = csv.DictReader(fh, dialect=&amp;quot;excel&amp;quot;, delimiter=&amp;quot;\t&amp;quot;)&lt;br /&gt;
     for row in csv_reader:&lt;br /&gt;
         print(f&#039;\t{row[&amp;quot;name&amp;quot;]} works in the {row[&amp;quot;department&amp;quot;]} department&#039;)&lt;br /&gt;
&lt;br /&gt;
====CSV Write====&lt;br /&gt;
 import csv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 # simple&lt;br /&gt;
 with Path(&amp;quot;data.tsv&amp;quot;).open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     csvwriter = csv.writer(fh, delimiter=&amp;quot;\t&amp;quot;)&lt;br /&gt;
     csvwriter.writerow((&amp;quot;Date&amp;quot;, &amp;quot;Confirmed&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # dictwriter&lt;br /&gt;
 with Path(&amp;quot;data.tsv&amp;quot;).open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     csvwriter = csv.DictWriter(&lt;br /&gt;
         fh,&lt;br /&gt;
         delimiter=&amp;quot;\t&amp;quot;,&lt;br /&gt;
         extrasaction=&amp;quot;ignore&amp;quot;,&lt;br /&gt;
         fieldnames=[&amp;quot;date&amp;quot;, &amp;quot;occupied_percent&amp;quot;, &amp;quot;occupied&amp;quot;, &amp;quot;total&amp;quot;],&lt;br /&gt;
     )&lt;br /&gt;
     csvwriter.writeheader()&lt;br /&gt;
     for d in my_list_of_dicts:&lt;br /&gt;
         d[&amp;quot;occupied_percent&amp;quot;] = round(100 * d[&amp;quot;occupied&amp;quot;] / d[&amp;quot;total&amp;quot;], 1)&lt;br /&gt;
         csvwriter.writerow(d)&lt;br /&gt;
&lt;br /&gt;
===JSON===&lt;br /&gt;
====JSON Read====&lt;br /&gt;
 import json&lt;br /&gt;
 # from file&lt;br /&gt;
 with path_to_download_file.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
     d_json = json.load(fh)&lt;br /&gt;
 # from string&lt;br /&gt;
 d_json = json.loads(response_text)&lt;br /&gt;
&lt;br /&gt;
 def json_read(file_path: Path) -&amp;gt; list[dict[str, str]]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Read JSON data from file.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     with file_path.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
         json_data = json.load(fh)&lt;br /&gt;
     return json_data&lt;br /&gt;
&lt;br /&gt;
====JSON Write====&lt;br /&gt;
Write dict to file in JSON format, keeping utf-8 encoding&lt;br /&gt;
 import json&lt;br /&gt;
 &lt;br /&gt;
 with path_to_download_file.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     json.dump(my_dict, fh, ensure_ascii=False, sort_keys=False, indent=2)&lt;br /&gt;
&lt;br /&gt;
 def json_write(file_path: Path, json_data: list[dict[str, str]]) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Write JSON data to file.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     with file_path.open(&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
         json.dump(json_data, fh, ensure_ascii=False, sort_keys=False, indent=2)&lt;br /&gt;
&lt;br /&gt;
===Excel===&lt;br /&gt;
====Excel Read====&lt;br /&gt;
 import openpyxl&lt;br /&gt;
 # or xlsxwriter for write-only&lt;br /&gt;
 &lt;br /&gt;
 workbook = openpyxl.load_workbook(&lt;br /&gt;
     pathToMyExcelFile,&lt;br /&gt;
     data_only=True,  # read values instead of formulas&lt;br /&gt;
     read_only=True,  # suppresses: &amp;quot;UserWarning: wmf image format is not supported so the image is being dropped&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 sheet = workbook[&amp;quot;mySheetName&amp;quot;]&lt;br /&gt;
 # or fetch active sheet&lt;br /&gt;
 sheet = workbook.active&lt;br /&gt;
 cell = sheet[&amp;quot;A34&amp;quot;]&lt;br /&gt;
 # or&lt;br /&gt;
 cell = sheet.cell(row=34, column=1)  # index start here with 1&lt;br /&gt;
 print(cell.value)&lt;br /&gt;
 # or&lt;br /&gt;
 print(sheet.cell(column=col, row=row).value)&lt;br /&gt;
&lt;br /&gt;
====Excel Write====&lt;br /&gt;
 import openpyxl&lt;br /&gt;
 &lt;br /&gt;
 workbook = openpyxl.Workbook()&lt;br /&gt;
 sheet = workbook.active&lt;br /&gt;
 cell = sheet[&amp;quot;A34&amp;quot;]&lt;br /&gt;
 # or&lt;br /&gt;
 cell = sheet.cell(row=i, column=j)  # index starts at 1&lt;br /&gt;
 cell.value = &amp;quot;asdf&amp;quot;&lt;br /&gt;
 workbook.save(&amp;quot;out.xlsx&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Word===&lt;br /&gt;
====Word Read====&lt;br /&gt;
 from io import BytesIO&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import pandas as pd&lt;br /&gt;
 from docx import Document&lt;br /&gt;
 from docx.document import Document as DocxDocument&lt;br /&gt;
 &lt;br /&gt;
 def read_doc_to_df(p_doc: Path | BytesIO) -&amp;gt; pd.DataFrame:  # noqa: C901, PLR0912&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Read Word document to DataFrame.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if isinstance(p_doc, Path):&lt;br /&gt;
         doc = Document(str(p_doc))&lt;br /&gt;
     elif isinstance(p_doc, BytesIO):&lt;br /&gt;
         doc = Document(p_doc)&lt;br /&gt;
 &lt;br /&gt;
     # list of multiple paragraphs per key&lt;br /&gt;
     data: dict[str, dict[str, list[str]]] = {}&lt;br /&gt;
 &lt;br /&gt;
     sections1: list[str] = []&lt;br /&gt;
     section1 = &amp;quot;&amp;quot;&lt;br /&gt;
     key = &amp;quot;&amp;quot;&lt;br /&gt;
     section2 = &amp;quot;&amp;quot;&lt;br /&gt;
     score = &amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     for paragraph in doc.paragraphs:&lt;br /&gt;
         assert paragraph.style is not None&lt;br /&gt;
         text = paragraph.text.strip()&lt;br /&gt;
         if paragraph.style.name == &amp;quot;Title&amp;quot;:  # doc title&lt;br /&gt;
             continue&lt;br /&gt;
         if paragraph.style.name == &amp;quot;Heading 1&amp;quot;:  # sheet name&lt;br /&gt;
             section1 = text&lt;br /&gt;
             if section1 not in sections1:&lt;br /&gt;
                 sections1.append(section1)&lt;br /&gt;
             key = &amp;quot;&amp;quot;&lt;br /&gt;
             section2 = &amp;quot;&amp;quot;&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Heading 2&amp;quot;:  # Key of the question&lt;br /&gt;
             key = text.split(&amp;quot; | &amp;quot;)[0]&lt;br /&gt;
             key = section1 + &amp;quot;|&amp;quot; + key&lt;br /&gt;
             section2 = &amp;quot;&amp;quot;&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Normal&amp;quot; and text.startswith(&amp;quot;[&amp;quot;) and &amp;quot;]&amp;quot; in text:&lt;br /&gt;
             section2, t2 = text[1:].split(&amp;quot;]&amp;quot;, maxsplit=1)&lt;br /&gt;
             if section2 == &amp;quot;Score&amp;quot;:&lt;br /&gt;
                 score = t2.strip()&lt;br /&gt;
                 data[key][section2] = [score]&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Normal&amp;quot;:&lt;br /&gt;
             text = paragraph.text.strip()&lt;br /&gt;
             # skip requirements leftovers and empty paragraphs&lt;br /&gt;
             if (section2 == &amp;quot;&amp;quot; and text.startswith(&amp;quot;...&amp;quot;)) or text == &amp;quot;&amp;quot;:&lt;br /&gt;
                 continue&lt;br /&gt;
             lst = data.setdefault(key, {}).setdefault(section2, [])&lt;br /&gt;
             lst.append(text)&lt;br /&gt;
             data[key][section2] = lst&lt;br /&gt;
         else:&lt;br /&gt;
             msg = f&amp;quot;Unexpected style: {paragraph.style.name} in paragraph: {text}&amp;quot;&lt;br /&gt;
             raise ValueError(msg)&lt;br /&gt;
 &lt;br /&gt;
     # flatten the text from list to str&lt;br /&gt;
     data2: dict[str, dict[str, str]] = {}&lt;br /&gt;
     for key, sections in data.items():&lt;br /&gt;
         for section2, lst in sections.items():&lt;br /&gt;
             s = &amp;quot;\n&amp;quot;.join(lst)&lt;br /&gt;
             if key not in data2:&lt;br /&gt;
                 data2[key] = {}&lt;br /&gt;
             data2[key][section2] = s.strip()&lt;br /&gt;
 &lt;br /&gt;
     df1 = pd.DataFrame.from_dict(data2, orient=&amp;quot;index&amp;quot;)&lt;br /&gt;
     df1.index.name = &amp;quot;Key&amp;quot;&lt;br /&gt;
     df1 = df1.reset_index()&lt;br /&gt;
     df1[ [ &amp;quot;Sheet&amp;quot;, &amp;quot;Key&amp;quot; ] ] = df1[&amp;quot;Key&amp;quot;].str.split(&amp;quot;|&amp;quot;, n=1, expand=True)&lt;br /&gt;
 &lt;br /&gt;
     return df1&lt;br /&gt;
&lt;br /&gt;
====Word Write====&lt;br /&gt;
 from io import BytesIO&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import pandas as pd from docx import Document&lt;br /&gt;
 from docx.document import Document as DocxDocument&lt;br /&gt;
 from docx.shared import Cm&lt;br /&gt;
 &lt;br /&gt;
 LAYOUT_SMALL_MARGINS = True&lt;br /&gt;
 if LAYOUT_SMALL_MARGINS:&lt;br /&gt;
     sections = doc.sections&lt;br /&gt;
     for section in sections:&lt;br /&gt;
         section.top_margin = Cm(1)&lt;br /&gt;
         section.bottom_margin = Cm(1)&lt;br /&gt;
         section.left_margin = Cm(1)&lt;br /&gt;
         section.right_margin = Cm(1)&lt;br /&gt;
 &lt;br /&gt;
 doc.add_heading(&amp;quot;My Title&amp;quot;, level=0)&lt;br /&gt;
 doc.add_heading(&amp;quot;My Section&amp;quot;, level=1)&lt;br /&gt;
 doc.add_paragraph(&amp;quot;Some Text)&lt;br /&gt;
 p = doc.add_paragraph()&lt;br /&gt;
 runner = p.add_run(bold text&amp;quot;)&lt;br /&gt;
 runner.bold = True  # runner.italic = True&lt;br /&gt;
&lt;br /&gt;
==Regular Expressions==&lt;br /&gt;
See [http://docs.python.org/library/re.html]&lt;br /&gt;
&lt;br /&gt;
See [https://pythex.org] for an online tester&lt;br /&gt;
&lt;br /&gt;
multiple flags are joined via pipe |&lt;br /&gt;
 s = re.sub(&amp;quot;asdf.*&amp;quot;, r&amp;quot;qwertz&amp;quot;, s, flags=re.DOTALL | re.IGNORECASE)&lt;br /&gt;
&lt;br /&gt;
====Lookahead and Lookbehind====&lt;br /&gt;
 pos lookahead: (?=...)&lt;br /&gt;
 neg lookahead: (?!...)&lt;br /&gt;
 pos lookbehind (?&amp;lt;=...)&lt;br /&gt;
 neg lookbehind (?&amp;lt;!...)&lt;br /&gt;
&lt;br /&gt;
====matching====&lt;br /&gt;
 import re&lt;br /&gt;
 &lt;br /&gt;
 # V0: simple 1&lt;br /&gt;
 myPattern = &amp;quot;(/\*\*\* 0097_210000_0192539580000_2898977_0050 \*\*\*/.*?)($|/\*\*\*)&amp;quot;&lt;br /&gt;
 myRegExp = re.compile(myPattern, re.DOTALL)&lt;br /&gt;
 myMatch = myRegExp.search(cont)&lt;br /&gt;
 assert myMatch != None, f&amp;quot;golden file not found in file {filename}&amp;quot;&lt;br /&gt;
 cont_golden = myMatch.group(1)&lt;br /&gt;
 &lt;br /&gt;
 # V1: simple 2&lt;br /&gt;
 assert (&lt;br /&gt;
     re.match(&amp;quot;^[a-z]{2}$&amp;quot;, d_settings[&amp;quot;country&amp;quot;]) != None&lt;br /&gt;
 ), f&#039;Error: county must be 2 digit lower case. We got: {d_settings[&amp;quot;country&amp;quot;]}&#039;&lt;br /&gt;
 &lt;br /&gt;
 # V2: find and count&lt;br /&gt;
 was = r&amp;quot;&amp;quot;&amp;quot;Part: ([^&amp;lt;+])&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 cnt_parts = len(re.findall(was, cont))&lt;br /&gt;
 cont = re.sub(was, r&amp;quot;\n\nTeil: \1\n\n&amp;quot;, cont)&lt;br /&gt;
 assert cnt_parts == 4, f&amp;quot;{cnt_parts} == 4&amp;quot;&lt;br /&gt;
 assert &amp;quot;Part:&amp;quot; not in cont&lt;br /&gt;
&lt;br /&gt;
=====Match email=====&lt;br /&gt;
 def checkValidEMail(email: str) -&amp;gt; bool:&lt;br /&gt;
     # from https://stackoverflow.com/posts/719543/timeline bottom edit&lt;br /&gt;
     if not re.fullmatch(r&amp;quot;^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$&amp;quot;, email):&lt;br /&gt;
         print(&amp;quot;Error: invalid email&amp;quot;)&lt;br /&gt;
         quit()&lt;br /&gt;
     return True&lt;br /&gt;
&lt;br /&gt;
====Find all====&lt;br /&gt;
 myMatches = re.findall(&#039;href=&amp;quot;([^&amp;quot;]+)&amp;quot;&#039;, cont)&lt;br /&gt;
 for myMatch in myMatches:&lt;br /&gt;
     print(myMatch)&lt;br /&gt;
&lt;br /&gt;
====substring====&lt;br /&gt;
 import re&lt;br /&gt;
 &lt;br /&gt;
 # simple via search&lt;br /&gt;
 lk_id = re.search(&#039;^.*timeseries\-(\d+)\.json$&#039;, f).group(1)&lt;br /&gt;
 &lt;br /&gt;
 # simple via sub&lt;br /&gt;
 myPattern = &amp;quot;^.*&amp;quot; + s1 + &amp;quot;(.*)&amp;quot; + s2 + &amp;quot;.*$&amp;quot;&lt;br /&gt;
 out = re.sub(myPattern, r&amp;quot;\1&amp;quot;, s)&lt;br /&gt;
 &lt;br /&gt;
 # more robust including an assert&lt;br /&gt;
 def substr_between(s: str, s1: str, s2: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     returns substring of s, found between strings s1 and s2&lt;br /&gt;
     s1 and s2 can be regular expressions&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     myPattern = s1 + &#039;(.*)&#039; + s2&lt;br /&gt;
     myRegExp = re.compile(myPattern)&lt;br /&gt;
     myMatches = myRegExp.search(s)&lt;br /&gt;
     assert myMatches != None, f&amp;quot;E: can&#039;t find &#039;{s1}&#039;...&#039;{s2}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     out = myMatches.group(1)&lt;br /&gt;
     return out&lt;br /&gt;
&lt;br /&gt;
 matchObj = re.search(r&amp;quot;(\d+\.\d+)&amp;quot;, text&lt;br /&gt;
 if matchObj:&lt;br /&gt;
   price = float( &#039;%s&#039; % (matchObj).group(0) )&lt;br /&gt;
&lt;br /&gt;
====Naming of match groups====&lt;br /&gt;
(?P&amp;lt;name&amp;gt;...), see [https://docs.python.org/3/library/re.html]&lt;br /&gt;
&lt;br /&gt;
====Search and Replace====&lt;br /&gt;
From [http://www.regular-expressions.info/python.html]&amp;lt;br&amp;gt;&lt;br /&gt;
re.sub(regex, replacement, str) performs a search-and-replace across subject, replacing all matches of regex in str with replacement. The result is returned by the sub() function. The str string you pass is not modified.&lt;br /&gt;
&lt;br /&gt;
 s = re.sub(&amp;quot;  +&amp;quot;, &amp;quot; &amp;quot;, s)&lt;br /&gt;
&lt;br /&gt;
====Splitting====&lt;br /&gt;
From [http://docs.python.org/library/re.html]&amp;lt;br&amp;gt;&lt;br /&gt;
split() splits a string into a list delimited by the passed pattern. The method is invaluable for converting textual data into data structures that can be easily read and modified by Python as demonstrated in the following example that creates a phonebook.&lt;br /&gt;
&lt;br /&gt;
First, here is the input. Normally it may come from a file, here we are using triple-quoted string syntax:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; input = &amp;quot;&amp;quot;&amp;quot;Ross McFluff: 834.345.1254 155 Elm Street&lt;br /&gt;
 ...&lt;br /&gt;
 ... Ronald Heathmore: 892.345.3428 436 Finley Avenue&lt;br /&gt;
 ... Frank Burger: 925.541.7625 662 South Dogwood Way&lt;br /&gt;
 ...&lt;br /&gt;
 ...&lt;br /&gt;
 ... Heather Albrecht: 548.326.4584 919 Park Place&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The entries are separated by one or more newlines. Now we convert the string into a list with each nonempty line having its own entry:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; entries = re.split(&amp;quot;\n+&amp;quot;, input)&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; entries&lt;br /&gt;
 [&#039;Ross McFluff: 834.345.1254 155 Elm Street&#039;,&lt;br /&gt;
 &#039;Ronald Heathmore: 892.345.3428 436 Finley Avenue&#039;,&lt;br /&gt;
 &#039;Frank Burger: 925.541.7625 662 South Dogwood Way&#039;,&lt;br /&gt;
 &#039;Heather Albrecht: 548.326.4584 919 Park Place&#039;]&lt;br /&gt;
&lt;br /&gt;
Finally, split each entry into a list with first name, last name, telephone number, and address. We use the maxsplit parameter of split() because the address has spaces, our splitting pattern, in it:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; [re.split(&amp;quot;:? &amp;quot;, entry, 3) for entry in entries]&lt;br /&gt;
 [[&#039;Ross&#039;, &#039;McFluff&#039;, &#039;834.345.1254&#039;, &#039;155 Elm Street&#039;],&lt;br /&gt;
 [&#039;Ronald&#039;, &#039;Heathmore&#039;, &#039;892.345.3428&#039;, &#039;436 Finley Avenue&#039;],&lt;br /&gt;
 [&#039;Frank&#039;, &#039;Burger&#039;, &#039;925.541.7625&#039;, &#039;662 South Dogwood Way&#039;],&lt;br /&gt;
 [&#039;Heather&#039;, &#039;Albrecht&#039;, &#039;548.326.4584&#039;, &#039;919 Park Place&#039;]]&lt;br /&gt;
&lt;br /&gt;
==== replace cont by linebreaks====&lt;br /&gt;
 def replace_cont_by_linebreaks(s: str, regex: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Replace regex in s by the number of linebreaks it originally contained.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     myMatches = re.findall(regex, s, flags=re.DOTALL)&lt;br /&gt;
     for match in myMatches:&lt;br /&gt;
         linebreaks = match.count(&amp;quot;\n&amp;quot;)&lt;br /&gt;
         s = s.replace(match, &amp;quot;\n&amp;quot; * linebreaks, 1)&lt;br /&gt;
     return s&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Image/Picture/Photo==&lt;br /&gt;
 from PIL import Image, ImageFilter  # pip install Pillow&lt;br /&gt;
 &lt;br /&gt;
 fileIn = &amp;quot;2018-02-09 13.56.25.jpg&amp;quot;&lt;br /&gt;
 # Read image&lt;br /&gt;
 img = Image.open(fileIn)&lt;br /&gt;
PROBLEM:&amp;lt;br/&amp;gt;&lt;br /&gt;
PIL Image.save() drops the IPTC data like tags, keywords, copywrite, ...&amp;lt;br/&amp;gt;&lt;br /&gt;
better using https://imagemagick.org instead when tags shall be kept&lt;br /&gt;
&lt;br /&gt;
====Resize====&lt;br /&gt;
 # Resize keeping aspect ration -&amp;gt; img.thumbnail&lt;br /&gt;
 # drops exif data, exif can be added from source file via exif= in save, see below&lt;br /&gt;
 size = 1920, 1920&lt;br /&gt;
 img.thumbnail(size, Image.ANTIALIAS)&lt;br /&gt;
&lt;br /&gt;
====Export file====&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-edit.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img = Image.open(fileIn)&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=&#039;keep&#039;)  # exif=dict_exif_bytes&lt;br /&gt;
     # JPEG Parameters&lt;br /&gt;
     # * qualitiy : &#039;keep&#039; or 1 (worst) to 95 (best), default = 75. Values above 95 should be avoided.&lt;br /&gt;
     # * dpi : tuple of integers representing the pixel density, (x,y)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     print(&amp;quot;cannot write file &#039;%s&#039;&amp;quot; % fileOut)&lt;br /&gt;
&lt;br /&gt;
====Export Progressive / web optimized JPEG====&lt;br /&gt;
 from PIL import ImageFile  # for MAXBLOCK for progressive export&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-progressive.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=80, optimize=True, progressive=True)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     ImageFile.MAXBLOCK = img.size[0] * img.size[1]&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=80, optimize=True, progressive=True)&lt;br /&gt;
&lt;br /&gt;
====JPEG Meta Data: EXIF and IPTC====&lt;br /&gt;
=====IPTC: Tags/Keywords=====&lt;br /&gt;
 from iptcinfo3 import IPTCInfo  # this works in pyhton 3!&lt;br /&gt;
 iptc = IPTCInfo(fileIn)&lt;br /&gt;
 if len(iptc[&#039;keywords&#039;]) &amp;gt; 0:  # or supplementalCategories or contacts&lt;br /&gt;
     print(&#039;====&amp;gt; Keywords&#039;)&lt;br /&gt;
     for key in sorted(iptc[&#039;keywords&#039;]):&lt;br /&gt;
         s = key.decode(&#039;ascii&#039;)  # decode binary strings&lt;br /&gt;
         print(s)&lt;br /&gt;
&lt;br /&gt;
=====EXIF via piexif=====&lt;br /&gt;
 import piexif  # pip install piexif&lt;br /&gt;
 exif_dict = piexif.load(img.info[&#039;exif&#039;])&lt;br /&gt;
 print(exif_dict[&#039;GPS&#039;][piexif.GPSIFD.GPSAltitude])&lt;br /&gt;
 # returns list of 2 integers: value and donator  -&amp;gt; v / d&lt;br /&gt;
 # (340000, 1000) =&amp;gt; 340m&lt;br /&gt;
 # (51, 2) =&amp;gt; 25.5m&lt;br /&gt;
 &lt;br /&gt;
 # Modify altitude&lt;br /&gt;
 exif_dict[&#039;GPS&#039;][piexif.GPSIFD.GPSAltitude] = (140, 1)  # 140m&lt;br /&gt;
 &lt;br /&gt;
 # write to file&lt;br /&gt;
 exif_bytes = piexif.dump(exif_dict)&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-modExif.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;jpeg&amp;quot;, exif=exif_bytes, quality=&#039;keep&#039;)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     print(&amp;quot;cannot write file &#039;%s&#039;&amp;quot; % fileOut)&lt;br /&gt;
or&lt;br /&gt;
 exif_dict = piexif.load(fileIn)&lt;br /&gt;
 for ifd in (&amp;quot;0th&amp;quot;, &amp;quot;Exif&amp;quot;, &amp;quot;GPS&amp;quot;, &amp;quot;1st&amp;quot;):&lt;br /&gt;
     print(&amp;quot;===&amp;quot; + ifd)&lt;br /&gt;
     for tag in exif_dict[ifd]:&lt;br /&gt;
         print(piexif.TAGS[ifd][tag][&amp;quot;name&amp;quot;], &amp;quot;\t&amp;quot;,&lt;br /&gt;
               tag, &amp;quot;\t&amp;quot;, exif_dict[ifd][tag])&lt;br /&gt;
 print(exif_dict[&#039;0th&#039;][306]) # 306 = DateTime&lt;br /&gt;
&lt;br /&gt;
=====EXIF via exifread=====&lt;br /&gt;
 # Open image file for reading (binary mode)&lt;br /&gt;
 fh = open(fileIn, &amp;quot;rb&amp;quot;)&lt;br /&gt;
 # Return Exif tags&lt;br /&gt;
 exif = exifread.process_file(fh)&lt;br /&gt;
 fh.close()&lt;br /&gt;
 # for tag in exif.keys():&lt;br /&gt;
 #     if tag not in (&#039;JPEGThumbnail&#039;, &#039;TIFFThumbnail&#039;, &#039;Filename&#039;, &#039;EXIF MakerNote&#039;):&lt;br /&gt;
 #         print(&amp;quot;%s\t%s&amp;quot; % (tag, exif[tag]))&lt;br /&gt;
 print(exif[&amp;quot;Image DateTime&amp;quot;])&lt;br /&gt;
 print(exif[&amp;quot;GPS GPSLatitude&amp;quot;])&lt;br /&gt;
 print(exif[&amp;quot;GPS GPSLongitude&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
=====EXIF GPS via PIL=====&lt;br /&gt;
 # from https://developer.here.com/blog/getting-started-with-geocoding-exif-image-metadata-in-python3&lt;br /&gt;
 def get_exif(filename):&lt;br /&gt;
     image = Image.open(filename)&lt;br /&gt;
     image.verify()&lt;br /&gt;
     image.close()&lt;br /&gt;
     return image._getexif()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_labeled_exif(exif):&lt;br /&gt;
     labeled = {}&lt;br /&gt;
     for (key, val) in exif.items():&lt;br /&gt;
         labeled[TAGS.get(key)] = val&lt;br /&gt;
     return labeled&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_geotagging(exif):&lt;br /&gt;
     if not exif:&lt;br /&gt;
         raise ValueError(&amp;quot;No EXIF metadata found&amp;quot;)&lt;br /&gt;
     geotagging = {}&lt;br /&gt;
     for (idx, tag) in TAGS.items():&lt;br /&gt;
         if tag == &amp;quot;GPSInfo&amp;quot;:&lt;br /&gt;
             if idx not in exif:&lt;br /&gt;
                 raise ValueError(&amp;quot;No EXIF geotagging found&amp;quot;)&lt;br /&gt;
             for (key, val) in GPSTAGS.items():&lt;br /&gt;
                 if key in exif[idx]:&lt;br /&gt;
                     geotagging[val] = exif[idx][key]&lt;br /&gt;
     return geotagging&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_decimal_from_dms(dms, ref):&lt;br /&gt;
     degrees = dms[0][0] / dms[0][1]&lt;br /&gt;
     minutes = dms[1][0] / dms[1][1] / 60.0&lt;br /&gt;
     seconds = dms[2][0] / dms[2][1] / 3600.0&lt;br /&gt;
     if ref in [&amp;quot;S&amp;quot;, &amp;quot;W&amp;quot;]:&lt;br /&gt;
         degrees = -degrees&lt;br /&gt;
         minutes = -minutes&lt;br /&gt;
         seconds = -seconds&lt;br /&gt;
     return round(degrees + minutes + seconds, 5)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_coordinates(geotags):&lt;br /&gt;
     lat = get_decimal_from_dms(geotags[&amp;quot;GPSLatitude&amp;quot;], geotags[&amp;quot;GPSLatitudeRef&amp;quot;])&lt;br /&gt;
     lon = get_decimal_from_dms(geotags[&amp;quot;GPSLongitude&amp;quot;], geotags[&amp;quot;GPSLongitudeRef&amp;quot;])&lt;br /&gt;
     return (lat, lon)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 exif = get_exif(fileIn)&lt;br /&gt;
 exif_labeled = get_labeled_exif(exif)&lt;br /&gt;
 print(exif_labeled[&amp;quot;DateTime&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
 geotags = get_geotagging(exif)&lt;br /&gt;
 print(get_coordinates(geotags))&lt;br /&gt;
&lt;br /&gt;
====Template Matching / Image Regocnition Using CV2 / OpenCV====&lt;br /&gt;
see [[Python - CV2]]&lt;br /&gt;
&lt;br /&gt;
====Optical Character Recognition (OCR) ====&lt;br /&gt;
see [[Python - OCR]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Templates/Snippets==&lt;br /&gt;
===Retry on Exception===&lt;br /&gt;
 for retry_no in range(MAX_RETRIES):&lt;br /&gt;
     try:&lt;br /&gt;
         if retry_no &amp;gt; 0:&lt;br /&gt;
             logger.warning(&amp;quot;retrying %d/%d...&amp;quot;, retry_no + 1, MAX_RETRIES)&lt;br /&gt;
         doit()&lt;br /&gt;
     except json.JSONDecodeError:&lt;br /&gt;
         logger.exception(&amp;quot;JSONDecodeError&amp;quot;)&lt;br /&gt;
         if retry_no == MAX_RETRIES - 1:&lt;br /&gt;
             # end of retries&lt;br /&gt;
             raise&lt;br /&gt;
&lt;br /&gt;
===Diff of 2 files===&lt;br /&gt;
 import difflib&lt;br /&gt;
 &lt;br /&gt;
 fh1 = p_file1.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 fh2 = p_file1.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 diff = difflib.ndiff(fh1.readlines(), fh2.readlines())&lt;br /&gt;
 delta = &amp;quot;&amp;quot;.join(line for line in diff if line.startswith((&amp;quot;+ &amp;quot;, &amp;quot;- &amp;quot;)))&lt;br /&gt;
 print(delta)&lt;br /&gt;
&lt;br /&gt;
===GPX parsing===&lt;br /&gt;
 import gpxpy&lt;br /&gt;
 import gpxpy.gpx&lt;br /&gt;
 # Elevation data by NASA: see lib at https://github.com/tkrajina/srtm.py&lt;br /&gt;
 fh_gpx_file = open(gpx_file_path, &#039;r&#039;)&lt;br /&gt;
 gpx = gpxpy.parse(fh_gpx_file)&lt;br /&gt;
 #  Loops for accessing the data&lt;br /&gt;
 for track in gpx.tracks:&lt;br /&gt;
     for segment in track.segments:&lt;br /&gt;
         for point in segment.points:&lt;br /&gt;
 for waypoint in gpx.waypoints:&lt;br /&gt;
 for route in gpx.routes:&lt;br /&gt;
     for point in route.points: &lt;br /&gt;
 # interesting properties of point / waypoint objects:&lt;br /&gt;
 point.time&lt;br /&gt;
 point.latitude&lt;br /&gt;
 point.longitude&lt;br /&gt;
 point.source&lt;br /&gt;
 waypoint.name&lt;br /&gt;
&lt;br /&gt;
===TypeHints===&lt;br /&gt;
 from typing import Any, Dict, cast&lt;br /&gt;
 creds = cast(dict[str, str], tomllib.load(f))  # type: ignore&lt;br /&gt;
 o = cast(Dict[str, Any], tomllib.load(f))  # type: ignore&lt;br /&gt;
 o[&amp;quot;sap&amp;quot;] = cast(Dict[str, str], o[&amp;quot;sap&amp;quot;])&lt;br /&gt;
 o[&amp;quot;settings&amp;quot;] = cast(Dict[str, str | int | bool], o[&amp;quot;settings&amp;quot;])&lt;br /&gt;
 o[&amp;quot;settings&amp;quot;][&amp;quot;sleep_time&amp;quot;] = cast(int, o[&amp;quot;settings&amp;quot;][&amp;quot;sleep_time&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
===Pylance/Pyright===&lt;br /&gt;
 import tomllib&lt;br /&gt;
 # shows warning: Import &amp;quot;xyz&amp;quot; could not be resolved&lt;br /&gt;
 # fix by &lt;br /&gt;
 import tomllib # pyright: ignore&lt;br /&gt;
&lt;br /&gt;
===asserts function argument validation===&lt;br /&gt;
aus [https://bildungsportal.sachsen.de/opal/auth/RepositoryEntry/12518195218/CourseNode/94506982489539?0 Python Kurs von Carsten Knoll]&lt;br /&gt;
 def eine_funktion(satz, ganzzahl, zahl2, liste):&lt;br /&gt;
   if not type(satz) == str:&lt;br /&gt;
     print &amp;quot;Datentpyfehler: satz&amp;quot;&lt;br /&gt;
     return -1&lt;br /&gt;
   if not isinstance(ganzzahl, int):&lt;br /&gt;
     print &amp;quot;Datentpyfehler: ganzzahl&amp;quot;&lt;br /&gt;
     return -2&lt;br /&gt;
   if not isinstance(liste, (tuple, list)):&lt;br /&gt;
     print &amp;quot;Datentpyfehler: liste&amp;quot;&lt;br /&gt;
     return -3&lt;br /&gt;
   # Kompakteste Variante (empfohlen): &lt;br /&gt;
   assert zahl2 &amp;gt; 0, &amp;quot;Error: zahl2 ist nicht &amp;gt; 0&amp;quot; # Assertation-Error bei Nichterfuellung&lt;br /&gt;
&lt;br /&gt;
 def F(x):&lt;br /&gt;
   if not isinstance(x, (float, int)):&lt;br /&gt;
     msg = &amp;quot;Zahl erwartet, %s bekommen&amp;quot; % type(x)&lt;br /&gt;
     raise ValueError(msg)&lt;br /&gt;
   return x**2&lt;br /&gt;
better:&lt;br /&gt;
 def F(x):&lt;br /&gt;
   assert isinstance(x, (float, int)), &amp;quot;Error: x is not of type float or int&amp;quot;&lt;br /&gt;
   return x**2&lt;br /&gt;
&lt;br /&gt;
 assert variant in [&lt;br /&gt;
     &amp;quot;normal&amp;quot;,&lt;br /&gt;
     &amp;quot;gray&amp;quot;,&lt;br /&gt;
     &amp;quot;cannyedge&amp;quot;,&lt;br /&gt;
 ], &amp;quot;Error: variant is not in &#039;normal&#039;, &#039;gray&#039;, &#039;cannyedge&#039;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===Exceptions===&lt;br /&gt;
see [https://medium.com/@saadjamilakhtar/5-best-practices-for-python-exception-handling-5e54b876a20]&lt;br /&gt;
&lt;br /&gt;
====Catching====&lt;br /&gt;
Catch keyboard interrupt and do a &amp;quot;save exit&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     i = 0&lt;br /&gt;
     while 1:&lt;br /&gt;
         i += 1&lt;br /&gt;
         print(i)&lt;br /&gt;
 except KeyboardInterrupt:&lt;br /&gt;
     print(&amp;quot;stopped&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
Catch &#039;&#039;&#039;all&#039;&#039;&#039; exceptions&lt;br /&gt;
 try:&lt;br /&gt;
   [...]&lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     print(&amp;quot;Exception: &amp;quot;, e)&lt;br /&gt;
&lt;br /&gt;
====Raising Exceptions====&lt;br /&gt;
 if resp.status_code != 200:  # noqa: PLR2004&lt;br /&gt;
     msg = f&amp;quot;Bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
     raise ValueError(msg) from None&lt;br /&gt;
&lt;br /&gt;
Custom Exceptions&lt;br /&gt;
 try: &lt;br /&gt;
   raise Exception(&amp;quot;HiHo&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
 raise ValueError¶&lt;br /&gt;
&lt;br /&gt;
===perl grep and map===&lt;br /&gt;
from [https://stackoverflow.com/questions/12845288/grep-on-elements-of-a-list]&lt;br /&gt;
 def grep(list, pattern):&lt;br /&gt;
     expr = re.compile(pattern)&lt;br /&gt;
     return [elem for elem in list if expr.match(elem)]&lt;br /&gt;
 or&lt;br /&gt;
 filteredList = filter(lambda x: x &amp;lt; 7 and x &amp;gt; 2, unfilteredList)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def map(list, was, womit):&lt;br /&gt;
     return list(map(lambda i: re.sub(was, womit, i), list))&lt;br /&gt;
     # was = &#039;.*&amp;quot;(\d+)&amp;quot;.*&#039;&lt;br /&gt;
     # womit = r&amp;quot;\1&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===unit testing using pytest===&lt;br /&gt;
install via&lt;br /&gt;
 pip install pytest&lt;br /&gt;
activate in vscode, see [https://code.visualstudio.com/docs/python/testing#_enable-a-test-framework]: To enable testing, use the Python: Configure Tests command on the Command Palette.&lt;br /&gt;
&lt;br /&gt;
see https://docs.pytest.org/en/6.2.x/assert.html#assert&lt;br /&gt;
&lt;br /&gt;
test_1.py:&lt;br /&gt;
 import myLib # my custom lib to test&lt;br /&gt;
 &lt;br /&gt;
 class TestClass:&lt;br /&gt;
     def test_one(self):&lt;br /&gt;
         x = &amp;quot;this&amp;quot;&lt;br /&gt;
         assert &amp;quot;h&amp;quot; in x&lt;br /&gt;
 &lt;br /&gt;
     def test_two(self):&lt;br /&gt;
         assert myLib.multiply(1, 2) == 2&lt;br /&gt;
 &lt;br /&gt;
     def test_three(self):&lt;br /&gt;
         assert myLib.multiply(2, 2) == 2&lt;br /&gt;
&lt;br /&gt;
====project structure====&lt;br /&gt;
=====simplest structore: test next to script=====&lt;br /&gt;
Alternative without tests dir:&lt;br /&gt;
 ./main.py&lt;br /&gt;
 ./helper.py &lt;br /&gt;
 ./helper_test.py&lt;br /&gt;
with helper_test.py:&lt;br /&gt;
 from helper import fnc1&lt;br /&gt;
&lt;br /&gt;
=====standalone model with src and tests dirs=====&lt;br /&gt;
for this dir structure&lt;br /&gt;
 src/helper.py &lt;br /&gt;
 tests/helper_test.py&lt;br /&gt;
helper_test.py needs&lt;br /&gt;
 import sys&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 sys.path.insert(0, (Path(__file__).parent.parent / &amp;quot;src&amp;quot;).as_posix())&lt;br /&gt;
&lt;br /&gt;
=====standalone model with tests dir=====&lt;br /&gt;
if the test files are placed inside a ./tests/ dir&lt;br /&gt;
 ./main.py&lt;br /&gt;
 ./helper.py &lt;br /&gt;
 tests/helper_test.py&lt;br /&gt;
helper_test.py needs &lt;br /&gt;
 sys.path.insert(0, Path(__file__).parent.parent.as_posix())&lt;br /&gt;
 from helper import fnc1&lt;br /&gt;
&lt;br /&gt;
====parametrize====&lt;br /&gt;
 import pytest&lt;br /&gt;
 @pytest.mark.parametrize(&lt;br /&gt;
     (&amp;quot;test_input&amp;quot;, &amp;quot;expected&amp;quot;),&lt;br /&gt;
     [(&amp;quot;&amp;quot;, None), (&amp;quot;PT30M&amp;quot;, 30), (&amp;quot;PT3H&amp;quot;, 3 * 60), (&amp;quot;PT2H30M&amp;quot;, 2 * 60 + 30)],&lt;br /&gt;
 )&lt;br /&gt;
 def test_task_est_to_minutes(test_input: str, expected: int) -&amp;gt; None:&lt;br /&gt;
     assert task_est_to_minutes(test_input) == expected&lt;br /&gt;
&lt;br /&gt;
=====fixures: prepare and cleanup test_data=====&lt;br /&gt;
to automatically run before and after each testcase&lt;br /&gt;
 @pytest.fixture(autouse=True)&lt;br /&gt;
 def _setup_tests():  # noqa: ANN202&lt;br /&gt;
     cache_prepare_lists()&lt;br /&gt;
     cache_prepare_tasks()&lt;br /&gt;
 &lt;br /&gt;
     yield&lt;br /&gt;
 &lt;br /&gt;
     cache_cleanup_test_data()&lt;br /&gt;
&lt;br /&gt;
====Test coverage report====&lt;br /&gt;
 pip install pytest-cov&lt;br /&gt;
 # console output&lt;br /&gt;
 pytest --cov&lt;br /&gt;
 &lt;br /&gt;
 # html report in coverage_report/index.html with details per file&lt;br /&gt;
 pytest --cov --cov-report=html:coverage_report&lt;br /&gt;
to exclude a code block from coverage report, add&lt;br /&gt;
 # pragma: no cover&lt;br /&gt;
&lt;br /&gt;
=== Sleep / Wait for input ===&lt;br /&gt;
sleep for a while&lt;br /&gt;
 import time&lt;br /&gt;
 time.sleep(60)&lt;br /&gt;
&lt;br /&gt;
wait for user input&lt;br /&gt;
 input(&amp;quot;press Enter to close&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Suppress Warnings===&lt;br /&gt;
see [https://docs.python.org/3/library/warnings.html#temporarily-suppressing-warnings]&lt;br /&gt;
 import warnings&lt;br /&gt;
 warnings.filterwarnings(&amp;quot;ignore&amp;quot;, message=&amp;quot;.*native_field_num.*not found in message.*&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Logging===&lt;br /&gt;
====V4 minimal stdout logging====&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.addLevelName(logging.DEBUG, &amp;quot;D&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.INFO, &amp;quot;I&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.WARNING, &amp;quot;W&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.ERROR, &amp;quot;E&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.CRITICAL, &amp;quot;C&amp;quot;)&lt;br /&gt;
 logging.basicConfig(level=logging.INFO, format=&amp;quot;%(levelname)s: %(name)s: %(message)s&amp;quot;)&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
 LOGGER.setLevel(logging.INFO)&lt;br /&gt;
 logging.getLogger(&amp;quot;httpx&amp;quot;).setLevel(logging.WARNING)&lt;br /&gt;
 &lt;br /&gt;
 # usage of variables and rounding of numbers. (%d also rounds floats)&lt;br /&gt;
 LOGGER.info(&amp;quot;file %s has %d lines with avg %.1f char/line&amp;quot;, filename, lines, c_p_l)&lt;br /&gt;
&lt;br /&gt;
====V3 simple logging====&lt;br /&gt;
 # main file:&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.basicConfig(&lt;br /&gt;
     level=logging.INFO,&lt;br /&gt;
     format=&amp;quot;%(asctime)s\t%(levelname)s\t%(name)s\t%(message)s&amp;quot;,&lt;br /&gt;
     handlers=[&lt;br /&gt;
         RotatingFileHandler(&lt;br /&gt;
             Path(__file__).with_suffix(&amp;quot;.log&amp;quot;),&lt;br /&gt;
             maxBytes=10485760,  # 10 MB = 10*1024*1024,&lt;br /&gt;
             backupCount=1,&lt;br /&gt;
             encoding=&amp;quot;utf-8&amp;quot;,&lt;br /&gt;
         )&lt;br /&gt;
     ],&lt;br /&gt;
     datefmt=&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
 logger = logging.getLogger(Path(__file__).stem)&lt;br /&gt;
 logger.info(&amp;quot;Start&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # other files:&lt;br /&gt;
 import logging&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
&lt;br /&gt;
====V2: rotating file and STDOUT====&lt;br /&gt;
 # 1. setup&lt;br /&gt;
 import logging&lt;br /&gt;
 from logging.handlers import RotatingFileHandler&lt;br /&gt;
 &lt;br /&gt;
 logfile = &amp;quot;myApp.log&amp;quot;&lt;br /&gt;
 maxBytes = 20 * 1024 * 1024&lt;br /&gt;
 backupCount = 5&lt;br /&gt;
 loglevel_console = logging.INFO&lt;br /&gt;
 loglevel_file = logging.DEBUG&lt;br /&gt;
 &lt;br /&gt;
 # create logger&lt;br /&gt;
 logger = logging.getLogger(&amp;quot;root&amp;quot;)&lt;br /&gt;
 logger.setLevel(loglevel_file)&lt;br /&gt;
 &lt;br /&gt;
 # console handler&lt;br /&gt;
 ch = logging.StreamHandler()&lt;br /&gt;
 ch.setLevel(loglevel_console)&lt;br /&gt;
 &lt;br /&gt;
 # rotating file handler&lt;br /&gt;
 # fh = logging.FileHandler(logfile)&lt;br /&gt;
 fh = RotatingFileHandler(logfile, maxBytes=maxBytes, backupCount=backupCount)&lt;br /&gt;
 fh.setLevel(loglevel_file)&lt;br /&gt;
 &lt;br /&gt;
 # create formatter and add it to the handlers&lt;br /&gt;
 # %(name)s = LoggerName, %(threadName)s = TreadName&lt;br /&gt;
 formatter = logging.Formatter(&lt;br /&gt;
     &amp;quot;%(asctime)s - %(levelname)s - %(name)s - %(threadName)s - %(message)s &amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 fh.setFormatter(formatter)&lt;br /&gt;
 ch.setFormatter(formatter)&lt;br /&gt;
 &lt;br /&gt;
 # add the handlers to the logger&lt;br /&gt;
 logger.addHandler(ch)&lt;br /&gt;
 logger.addHandler(fh)&lt;br /&gt;
 &lt;br /&gt;
 logger.debug(&amp;quot;DebugMe&amp;quot;)&lt;br /&gt;
 logger.info(&amp;quot;Starting&amp;quot;)&lt;br /&gt;
 logger.warning(&amp;quot;Attention&amp;quot;)&lt;br /&gt;
 logger.error(&amp;quot;Something went wrong&amp;quot;)&lt;br /&gt;
 logger.critical(&amp;quot;Something seriously went wrong &amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # 2. in other files/modules now use&lt;br /&gt;
 import logging&lt;br /&gt;
 &lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 logger.info(&amp;quot;text&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 ...&lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     logger.exception(&amp;quot;Unhandeled exception&amp;quot;)&lt;br /&gt;
     quit()&lt;br /&gt;
&lt;br /&gt;
====V1: Simple====&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.basicConfig(&lt;br /&gt;
     level=logging.INFO,&lt;br /&gt;
     format=&amp;quot;%(asctime)s\t%(levelname)s\t%(message)s&amp;quot;,  # \t%(name)s&lt;br /&gt;
     filename=Path(__file__).with_suffix(&amp;quot;.log&amp;quot;), # uncomment to switch to stdout&lt;br /&gt;
     filemode=&amp;quot;a&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 logger.debug(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.info(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.warning(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.error(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.critical(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 try:&lt;br /&gt;
 ...&lt;br /&gt;
 except psycopg2.DatabaseError:&lt;br /&gt;
     logger.exception(&amp;quot;Database connection error: %s&amp;quot;)&lt;br /&gt;
     raise&lt;br /&gt;
&lt;br /&gt;
===Process Bar===&lt;br /&gt;
see [https://tqdm.github.io tqdm]&lt;br /&gt;
 from tqdm import tqdm&lt;br /&gt;
 for i in tqdm(range(10000)):&lt;br /&gt;
     ....&lt;br /&gt;
or&lt;br /&gt;
 with tqdm(total=total_iterations, desc=&amp;quot;Processing&amp;quot;) as progress_bar:&lt;br /&gt;
     ...&lt;br /&gt;
     progress_bar.update(1)&lt;br /&gt;
&lt;br /&gt;
===CGI Web development===&lt;br /&gt;
 # Print necessary headers.&lt;br /&gt;
 print(&amp;quot;Content-Type: text/html&amp;quot;)&lt;br /&gt;
 print()&lt;br /&gt;
 &lt;br /&gt;
 # errors and debugging info to browser&lt;br /&gt;
 import cgitb&lt;br /&gt;
 cgitb.enable()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Access URL or Form Parameters&lt;br /&gt;
 # V2 from https://www.tutorialspoint.com/python/python_cgi_programming.htm&lt;br /&gt;
 import cgi&lt;br /&gt;
 form = cgi.FieldStorage()&lt;br /&gt;
 username = form.getvalue(&#039;username&#039;)&lt;br /&gt;
 print(username)&lt;br /&gt;
&lt;br /&gt;
 # V1&lt;br /&gt;
 import sys&lt;br /&gt;
 import urllib.parse&lt;br /&gt;
 query = os.environ.get(&#039;QUERY_STRING&#039;)&lt;br /&gt;
 query = urllib.parse.unquote(query, errors=&amp;quot;surrogateescape&amp;quot;)&lt;br /&gt;
 d = dict(qc.split(&amp;quot;=&amp;quot;) for qc in query.split(&amp;quot;&amp;amp;&amp;quot;))&lt;br /&gt;
 print(d)&lt;br /&gt;
&lt;br /&gt;
====CGI Backend Returning JSONs====&lt;br /&gt;
 #!/usr/local/bin/python3.6&lt;br /&gt;
 # -*- coding: utf-8 -*-&lt;br /&gt;
 &lt;br /&gt;
 import cgi&lt;br /&gt;
 import json&lt;br /&gt;
 &lt;br /&gt;
 # Print necessary headers.&lt;br /&gt;
 print(&amp;quot;Content-type: application/json&amp;quot;)&lt;br /&gt;
 print()&lt;br /&gt;
 &lt;br /&gt;
 def get_form_parameter(para: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;asserts that a given parameter is set and returns its value&amp;quot;&lt;br /&gt;
     value = form.getvalue(para)&lt;br /&gt;
     assert value, f&amp;quot;Error: parameter {para} missing&amp;quot;&lt;br /&gt;
     assert value != &amp;quot;&amp;quot;, f&amp;quot;Error: parameter {para} missing&amp;quot;&lt;br /&gt;
     return value&lt;br /&gt;
  &lt;br /&gt;
 response = {}&lt;br /&gt;
 response[&#039;status&#039;] = &amp;quot;ok&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 try:&lt;br /&gt;
     action = get_form_parameter(&amp;quot;action&amp;quot;)&lt;br /&gt;
     response[&#039;action&#039;] = action&lt;br /&gt;
     if action == &amp;quot;myAction&amp;quot;:&lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     response[&#039;status&#039;] = &amp;quot;error&amp;quot;&lt;br /&gt;
     d = {&amp;quot;type&amp;quot;: str(type(e)), &amp;quot;text&amp;quot;: str(e)}&lt;br /&gt;
     response[&amp;quot;exception&amp;quot;] = d&lt;br /&gt;
 &lt;br /&gt;
 finally:&lt;br /&gt;
     print(json.dumps(response))&lt;br /&gt;
&lt;br /&gt;
==Databases==&lt;br /&gt;
===PostgreSQL===&lt;br /&gt;
====Improved helper function====&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Helper functions for DB access.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 import datetime as dt&lt;br /&gt;
 import logging&lt;br /&gt;
 &lt;br /&gt;
 import psycopg2&lt;br /&gt;
 import psycopg2.extras&lt;br /&gt;
 from creds_db import credentials as creds_db&lt;br /&gt;
 &lt;br /&gt;
 # Configure logging&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 &lt;br /&gt;
 connection: psycopg2.extensions.connection = None  # type: ignore&lt;br /&gt;
 cursor_query: psycopg2.extensions.cursor = None  # type: ignore&lt;br /&gt;
 cursor_write: psycopg2.extensions.cursor = None  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def connect() -&amp;gt; (&lt;br /&gt;
     tuple[&lt;br /&gt;
         psycopg2.extensions.connection,&lt;br /&gt;
         psycopg2.extensions.cursor,&lt;br /&gt;
         psycopg2.extensions.cursor,&lt;br /&gt;
     ]&lt;br /&gt;
 ):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Connect to the database.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     try:&lt;br /&gt;
         connection = psycopg2.connect(**creds_db)&lt;br /&gt;
         cursor_query = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)&lt;br /&gt;
         cursor_write = connection.cursor()&lt;br /&gt;
         logger.info(&lt;br /&gt;
             &amp;quot;Connected to database %s on host %s&amp;quot;,&lt;br /&gt;
             creds_db[&amp;quot;database&amp;quot;],&lt;br /&gt;
             creds_db[&amp;quot;host&amp;quot;],&lt;br /&gt;
         )&lt;br /&gt;
     except psycopg2.DatabaseError:&lt;br /&gt;
         logger.exception(&amp;quot;Database connection error: %s&amp;quot;)&lt;br /&gt;
         raise&lt;br /&gt;
     else:&lt;br /&gt;
         return connection, cursor_query, cursor_write&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def disconnect() -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Disconnect from the database.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     try:&lt;br /&gt;
         if cursor_query:&lt;br /&gt;
             cursor_query.close()&lt;br /&gt;
         if cursor_write:&lt;br /&gt;
             cursor_write.close()&lt;br /&gt;
         if connection:&lt;br /&gt;
             connection.close()&lt;br /&gt;
         logger.info(&amp;quot;Disconnected from the database&amp;quot;)&lt;br /&gt;
     except psycopg2.DatabaseError:&lt;br /&gt;
         logger.exception(&amp;quot;Error during disconnection: %s&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def exists(url: str, valid_hours: float) -&amp;gt; bool | None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Check if a record exists.&lt;br /&gt;
 &lt;br /&gt;
     Returns&lt;br /&gt;
     -------&lt;br /&gt;
     None for missing record&lt;br /&gt;
     True for valid record&lt;br /&gt;
     False for expired record&lt;br /&gt;
 &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=valid_hours)&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT delete_at &amp;gt; %s AS &amp;quot;valid&amp;quot;&lt;br /&gt;
 FROM my_table&lt;br /&gt;
 WHERE url = %s LIMIT 1;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_query.execute(sql, (delete_at, url))&lt;br /&gt;
     res = cursor_query.fetchone()&lt;br /&gt;
     return res[0] if res else None&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def query1(url: str) -&amp;gt; dict | None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Execute a query.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT *&lt;br /&gt;
 FROM my_table&lt;br /&gt;
 WHERE url = %s LIMIT 1&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_query.execute(sql, (url,))&lt;br /&gt;
     return cursor_query.fetchone()  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def insert(url: str, response: str, delete_in_hours: float) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Insert a record.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=delete_in_hours)&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 INSERT INTO my_table (url, response, delete_at)&lt;br /&gt;
 VALUES (%s, %s, %s);&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (url, response, delete_at))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def delete(url: str) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Delete a record.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 DELETE FROM my_table&lt;br /&gt;
 WHERE url = %s;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (url,))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def delete_expired(in_hours: float) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Delete records that expire in less than in_hours from now.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=in_hours)&lt;br /&gt;
 &lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 DELETE FROM my_table&lt;br /&gt;
 WHERE delete_at &amp;lt; %s;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (delete_at,))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 connection, cursor_query, cursor_write = connect()&lt;br /&gt;
&lt;br /&gt;
====Basics====&lt;br /&gt;
 import psycopg2&lt;br /&gt;
 import psycopg2.extras&lt;br /&gt;
 &lt;br /&gt;
 credentials = {&lt;br /&gt;
     &amp;quot;host&amp;quot;: &amp;quot;localhost&amp;quot;,&lt;br /&gt;
     &amp;quot;port&amp;quot;: 5432,&lt;br /&gt;
     &amp;quot;database&amp;quot;: &amp;quot;myDB&amp;quot;,&lt;br /&gt;
     &amp;quot;user&amp;quot;: &amp;quot;myUser&amp;quot;,&lt;br /&gt;
     &amp;quot;password&amp;quot;: &amp;quot;myPwd&amp;quot;,&lt;br /&gt;
 }&lt;br /&gt;
 connection = psycopg2.connect(**credentials)&lt;br /&gt;
 cursor = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)&lt;br /&gt;
 &lt;br /&gt;
 l_bind_vars = [[&amp;quot;A1&amp;quot;, &amp;quot;A2&amp;quot;], [&amp;quot;B1&amp;quot;, &amp;quot;B2&amp;quot;]]&lt;br /&gt;
 &lt;br /&gt;
 sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT * FROM myTable&lt;br /&gt;
 WHERE 1=1 &lt;br /&gt;
 AND status NOT IN (&#039;CLOSED&#039;) &lt;br /&gt;
 AND ColA = %s &lt;br /&gt;
 AND ColB = %s &lt;br /&gt;
 ORDER BY created DESC&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 cursor.execute(sql, l_bind_vars)&lt;br /&gt;
 d_data = dict(cursor.fetchone())&lt;br /&gt;
&lt;br /&gt;
====export result to csv file====&lt;br /&gt;
 sql1 = &amp;quot;SELECT * FROM table&amp;quot;&lt;br /&gt;
 sql2 = &amp;quot;COPY (&amp;quot; + sql1 + &amp;quot;) TO STDOUT WITH CSV HEADER DELIMITER &#039;\t&#039;&amp;quot;&lt;br /&gt;
         with open(&amp;quot;out.csv&amp;quot;, &amp;quot;w&amp;quot;) as file:&lt;br /&gt;
             cursor.copy_expert(sql2, file)&lt;br /&gt;
&lt;br /&gt;
====ReadConfigFile to connect to PostgreSQL====&lt;br /&gt;
from [http://www.postgresqltutorial.com/postgresql-python/connect/]&lt;br /&gt;
database.ini&lt;br /&gt;
 [postgresql]&lt;br /&gt;
 host=dbhost&lt;br /&gt;
 port=5432&lt;br /&gt;
 database=dbname&lt;br /&gt;
 user=dbuser&lt;br /&gt;
 password=dbpass&lt;br /&gt;
&lt;br /&gt;
 from configparser import ConfigParser&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def config(filename=&amp;quot;database.ini&amp;quot;, section=&amp;quot;postgresql&amp;quot;):&lt;br /&gt;
     parser = ConfigParser()&lt;br /&gt;
     parser.read(filename)&lt;br /&gt;
     # get section, default to postgresql&lt;br /&gt;
     db = {}&lt;br /&gt;
     if parser.has_section(section):&lt;br /&gt;
         params = parser.items(section)&lt;br /&gt;
         for param in params:&lt;br /&gt;
             db[param[0]] = param[1]&lt;br /&gt;
     else:&lt;br /&gt;
         raise Exception(&lt;br /&gt;
             &amp;quot;Section {0} not found in the {1} file&amp;quot;.format(section, filename)&lt;br /&gt;
         )&lt;br /&gt;
     return db&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
main.py&lt;br /&gt;
 import psycopg2&lt;br /&gt;
 from config import config&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def connect():&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Connect to the PostgreSQL database server&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     conn = None&lt;br /&gt;
     try:&lt;br /&gt;
         params = config()  # read connection parameters&lt;br /&gt;
         print(&amp;quot;Connecting to the PostgreSQL database...&amp;quot;)&lt;br /&gt;
         conn = psycopg2.connect(**params)&lt;br /&gt;
         cur = conn.cursor()  # create a cursor&lt;br /&gt;
         print(&amp;quot;PostgreSQL database version:&amp;quot;)&lt;br /&gt;
         cur.execute(&amp;quot;SELECT version()&amp;quot;)  # execute a statement&lt;br /&gt;
         db_version = cur.fetchone()&lt;br /&gt;
         print(db_version)&lt;br /&gt;
         cur.close()&lt;br /&gt;
     except (Exception, psycopg2.DatabaseError) as error:&lt;br /&gt;
         print(error)&lt;br /&gt;
     finally:&lt;br /&gt;
         if conn is not None:&lt;br /&gt;
             conn.close()&lt;br /&gt;
             print(&amp;quot;Database connection closed.&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     connect()&lt;br /&gt;
&lt;br /&gt;
===SQL Lite / SQLite===&lt;br /&gt;
see page [[SQLite]]&lt;br /&gt;
&lt;br /&gt;
===InfluxDB===&lt;br /&gt;
see [[InfluxDB]]&lt;br /&gt;
&lt;br /&gt;
==Internet Access==&lt;br /&gt;
===Send E-Mails===&lt;br /&gt;
see [[Python - eMail]]&lt;br /&gt;
&lt;br /&gt;
===Download data using browser UA / REST===&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 headers = {&lt;br /&gt;
     &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0&amp;quot;,&lt;br /&gt;
 }&lt;br /&gt;
 resp = requests.get(&lt;br /&gt;
     url,&lt;br /&gt;
     headers=headers,&lt;br /&gt;
     timeout=3,  # timeout in sec, requests should always have a timeout!&lt;br /&gt;
     # timeout=(1,3), # 1s connect-timout, 3s read-timeout&lt;br /&gt;
 )&lt;br /&gt;
 if resp.status_code != 200:  # noqa: PLR2004&lt;br /&gt;
     msg = f&amp;quot;E: bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
     raise Exception(msg)  # noqa: TRY002&lt;br /&gt;
&lt;br /&gt;
====Download only if cache is too old====&lt;br /&gt;
 import time&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 def fetch_url_or_cache(file_cache: Path, url) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Fetch URL and cache in a file.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if check_cache_file_available_and_recent(&lt;br /&gt;
         file_path=file_cache, max_age=3600, verbose=False&lt;br /&gt;
     ):&lt;br /&gt;
         with file_cache.open(mode=&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
             s = fh.read()&lt;br /&gt;
     else:&lt;br /&gt;
         s = fetch(url=url)&lt;br /&gt;
         with file_cache.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
             fh.writelines(s)&lt;br /&gt;
     return s&lt;br /&gt;
 &lt;br /&gt;
 def check_cache_file_available_and_recent(&lt;br /&gt;
     file_path: Path,&lt;br /&gt;
     max_age: int = 3500,&lt;br /&gt;
 ) -&amp;gt; bool:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Check if cache file exists and is recent.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cache_good = False&lt;br /&gt;
     if file_path.exists() and (time.time() - file_path.stat().st_mtime &amp;lt; max_age):&lt;br /&gt;
         cache_good = True&lt;br /&gt;
     return cache_good&lt;br /&gt;
 &lt;br /&gt;
 # or&lt;br /&gt;
 def check_cache_file_available_and_recent_verbose(&lt;br /&gt;
     file_path: Path, max_age: int = 3600, *, verbose: bool = False&lt;br /&gt;
 ) -&amp;gt; bool:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Check if cache file exists and is recent.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cache_good = True&lt;br /&gt;
     if not file_path.exists():&lt;br /&gt;
         if verbose:&lt;br /&gt;
             print(f&amp;quot;No Cache available: {file_path}&amp;quot;)&lt;br /&gt;
         cache_good = False&lt;br /&gt;
     if cache_good and time.time() - (time.time() - file_path.stat().st_mtime &amp;lt; max_age):&lt;br /&gt;
         if verbose:&lt;br /&gt;
             print(f&amp;quot;Cache too old: {file_path}&amp;quot;)&lt;br /&gt;
         cache_good = False&lt;br /&gt;
     return cache_good&lt;br /&gt;
 &lt;br /&gt;
 def fetch(url) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Fetch URL.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     headers = {&lt;br /&gt;
         &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0&amp;quot;,  # noqa: E501&lt;br /&gt;
     }&lt;br /&gt;
     resp = requests.get(url, headers=headers, timeout=5)&lt;br /&gt;
     if resp.status_code == 200:  # noqa: PLR2004&lt;br /&gt;
         return resp.content.decode(&amp;quot;ascii&amp;quot;)&lt;br /&gt;
     else:  # noqa: RET505&lt;br /&gt;
         msg = f&amp;quot;E: bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
         raise Exception(msg)  # noqa: TRY002&lt;br /&gt;
&lt;br /&gt;
====Download using urllib.request====&lt;br /&gt;
 from urllib.request import urlopen&lt;br /&gt;
 &lt;br /&gt;
 url = &amp;quot;https://pomber.github.io/covid19/timeseries.json&amp;quot;&lt;br /&gt;
 with urlopen(url) as response:  # noqa: S310&lt;br /&gt;
     response_text = response.read()&lt;br /&gt;
&lt;br /&gt;
===HTML parsing and extracting of elements===&lt;br /&gt;
V2: via BeautifulSoup&lt;br /&gt;
 from bs4 import BeautifulSoup  # pip install beautifulsoup4&lt;br /&gt;
 &lt;br /&gt;
 soup = BeautifulSoup(cont, features=&amp;quot;html.parser&amp;quot;)&lt;br /&gt;
 my_element = soup.find(&amp;quot;div&amp;quot;, {&amp;quot;class&amp;quot;: &amp;quot;user-formatted-inner&amp;quot;})&lt;br /&gt;
 my_body = my_element.prettify()&lt;br /&gt;
 # my_body = my_element.encode()&lt;br /&gt;
 # my_body = str(my_element)&lt;br /&gt;
&lt;br /&gt;
V1: via lxml and xpath&lt;br /&gt;
 from lxml import html&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 page = requests.get(url)&lt;br /&gt;
 tree = html.fromstring(page.content)&lt;br /&gt;
 tbody_trs = tree.xpath(&amp;quot;//*/tbody/tr&amp;quot;)&lt;br /&gt;
 l_rows = []&lt;br /&gt;
 for tr in tbody_trs:&lt;br /&gt;
     l_columns = []&lt;br /&gt;
     if len(tr) != 15:&lt;br /&gt;
         continue&lt;br /&gt;
     for td in tr:&lt;br /&gt;
         l_columns.append(td.text_content())&lt;br /&gt;
         l_rows.append(list(l_columns))&lt;br /&gt;
&lt;br /&gt;
===HTML entities to unicode===&lt;br /&gt;
 import html&lt;br /&gt;
 cont = html.unescape(cont)&lt;br /&gt;
&lt;br /&gt;
==GUI Interactions==&lt;br /&gt;
===Take Screenshot===&lt;br /&gt;
 import pyautogui # (c:\Python\Scripts\)pip install pyautogui&lt;br /&gt;
 # pyautogui does only support screenshots on monitor #1&lt;br /&gt;
 ...&lt;br /&gt;
 screenshot = pyautogui.screenshot()&lt;br /&gt;
 # screenshot = pyautogui.screenshot(region=(screenshotX,screenshotY, screenshotW, screenshotH))&lt;br /&gt;
 screenshot = np.array(screenshot) &lt;br /&gt;
 # Convert RGB to BGR &lt;br /&gt;
 screenshot = screenshot[:, :, ::-1].copy()&lt;br /&gt;
&lt;br /&gt;
===Mouse Actions===&lt;br /&gt;
 def clickIt(x,y,key=&amp;quot;&amp;quot;) :&lt;br /&gt;
   x0, y0 = pyautogui.position()&lt;br /&gt;
   if key != &amp;quot;&amp;quot;: # crtl, shift&lt;br /&gt;
     pyautogui.keyDown(key)&lt;br /&gt;
   pyautogui.moveTo(x, y, duration=0.2)&lt;br /&gt;
   pyautogui.click(x=x , y=y, button=&#039;left&#039;, clicks=1, interval=0.1)&lt;br /&gt;
   if key != &amp;quot;&amp;quot;: # crtl, shift&lt;br /&gt;
     pyautogui.keyUp(key)&lt;br /&gt;
   pyautogui.moveTo(x0, y0)&lt;br /&gt;
&lt;br /&gt;
===Web Automation===&lt;br /&gt;
 from selenium import webdriver&lt;br /&gt;
 from selenium.webdriver.common.keys import Keys&lt;br /&gt;
 &lt;br /&gt;
 # from selenium.webdriver import Firefox&lt;br /&gt;
 from selenium.webdriver.firefox.options import Options&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 import glob&lt;br /&gt;
 &lt;br /&gt;
 class StravaUserMapDL():&lt;br /&gt;
     def __init__(self):&lt;br /&gt;
         self.driver = webdriver.Firefox()&lt;br /&gt;
 &lt;br /&gt;
     def login(self):&lt;br /&gt;
         driver = self.driver&lt;br /&gt;
         url = &amp;quot;https://www.somewebpage.com&amp;quot;&lt;br /&gt;
         email = &amp;quot;myemail&amp;quot;&lt;br /&gt;
         password = &amp;quot;mypassword&amp;quot;&lt;br /&gt;
         driver.get(url)&lt;br /&gt;
 &lt;br /&gt;
         title = driver.title&lt;br /&gt;
         urlIs = driver.current_url&lt;br /&gt;
         cont = driver.page_source #  as string&lt;br /&gt;
         FILE = open(filename,&amp;quot;w&amp;quot;) # w = overWrite file ; a = append to file&lt;br /&gt;
         FILE.write(cont)&lt;br /&gt;
         FILE.close()         &lt;br /&gt;
 &lt;br /&gt;
         # handle login if urlIs != url&lt;br /&gt;
         if (urlIs != url): &lt;br /&gt;
             # activate checkbox &#039;remember_me&#039;&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;remember_me&#039;)&lt;br /&gt;
             if (elem.is_selected() == False):&lt;br /&gt;
                 elem.click()&lt;br /&gt;
             assert elem.is_selected() == True&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;email&#039;)&lt;br /&gt;
             elem.send_keys(email)&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;password&#039;)&lt;br /&gt;
             elem.send_keys(password)&lt;br /&gt;
             elem.send_keys(Keys.RETURN)&lt;br /&gt;
             # Wait until login pages is replaced by real page&lt;br /&gt;
             urlIs = driver.current_url&lt;br /&gt;
             while (urlIs != url):&lt;br /&gt;
                 time.sleep(1)&lt;br /&gt;
                 urlIs = driver.current_url&lt;br /&gt;
             print (urlIs)&lt;br /&gt;
 &lt;br /&gt;
             # results = driver.find_elements_by_class_name(&#039;following&#039;)&lt;br /&gt;
             # results = driver.find_elements_by_tag_name(&#039;li&#039;)&lt;br /&gt;
 &lt;br /&gt;
             # print(results[0].text)&lt;br /&gt;
         assert (urlIs == url)&lt;br /&gt;
&lt;br /&gt;
===Unit Tests using Web Automation===&lt;br /&gt;
 import unittest&lt;br /&gt;
 from selenium import webdriver&lt;br /&gt;
 from selenium.webdriver.common.keys import Keys&lt;br /&gt;
 &lt;br /&gt;
 #from selenium.webdriver import Firefox&lt;br /&gt;
 from selenium.webdriver.firefox.options import Options&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 class PythonOrgSearch(unittest.TestCase):&lt;br /&gt;
 #    def __init__(self,asdf):&lt;br /&gt;
 #        self.driver = webdriver.Firefox() &lt;br /&gt;
 &lt;br /&gt;
     def setUp(self):&lt;br /&gt;
         print (&amp;quot;setUp&amp;quot;)&lt;br /&gt;
         # headless mode:&lt;br /&gt;
         # opts = Options()&lt;br /&gt;
         # opts.set_headless()&lt;br /&gt;
         # assert opts.headless  # Operating in headless mode&lt;br /&gt;
         # self.driver = webdriver.Firefox(options=opts)&lt;br /&gt;
 &lt;br /&gt;
         self.driver = webdriver.Firefox()&lt;br /&gt;
 &lt;br /&gt;
     def test_search_in_python_org(self):&lt;br /&gt;
         driver = self.driver&lt;br /&gt;
         driver.get(&amp;quot;http://www.python.org&amp;quot;)&lt;br /&gt;
         self.assertIn(&amp;quot;Python&amp;quot;, driver.title)&lt;br /&gt;
         elem = driver.find_element_by_name(&amp;quot;q&amp;quot;)&lt;br /&gt;
         elem.send_keys(&amp;quot;pycon&amp;quot;)&lt;br /&gt;
         elem.send_keys(Keys.RETURN)&lt;br /&gt;
         assert &amp;quot;No results found.&amp;quot; not in driver.page_source&lt;br /&gt;
         print (&amp;quot;fertig: python_org&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     def tearDown(self):&lt;br /&gt;
         print (&amp;quot;tearDown&amp;quot;)&lt;br /&gt;
         print (&amp;quot;close Firefox&amp;quot;)&lt;br /&gt;
         self.driver.close() # close tab&lt;br /&gt;
         self.driver.quit() # quit browser&lt;br /&gt;
         # os._exit(1) # exit unittest without Exception&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     try:&lt;br /&gt;
         unittest.main()&lt;br /&gt;
     except SystemExit as e:&lt;br /&gt;
         os._exit(1)&lt;br /&gt;
&lt;br /&gt;
==Cryptography and Hashing==&lt;br /&gt;
====Hashing via SHA256====&lt;br /&gt;
 def gen_SHA256_string(s: str) -&amp;gt; str:&lt;br /&gt;
     m = hashlib.sha256()&lt;br /&gt;
     m.update(s.encode(&amp;quot;ascii&amp;quot;))&lt;br /&gt;
     return m.hexdigest()&lt;br /&gt;
&lt;br /&gt;
====Hashing via MD5====&lt;br /&gt;
(MD5 is not secure, better use SHA256)&lt;br /&gt;
 def gen_MD5_string(s: str) -&amp;gt; str:&lt;br /&gt;
     m = hashlib.md5()&lt;br /&gt;
     m.update(s.encode(&amp;quot;ascii&amp;quot;))&lt;br /&gt;
     return m.hexdigest()&lt;br /&gt;
&lt;br /&gt;
====Password hashing via bcrypt====&lt;br /&gt;
 import bcrypt&lt;br /&gt;
 pwd = &#039;geheim&#039;&lt;br /&gt;
 pwd = pwd.encode(&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 # or &lt;br /&gt;
 pwd = b&#039;geheim&#039;&lt;br /&gt;
 &lt;br /&gt;
 hashed = bcrypt.hashpw(pwd, bcrypt.gensalt())&lt;br /&gt;
 if bcrypt.checkpw(pwd, hashed):&lt;br /&gt;
     print(&amp;quot;It Matches!&amp;quot;)&lt;br /&gt;
     print(hashed.decode(&amp;quot;utf-8&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
To use version 2a instead of 2b (default):&lt;br /&gt;
 bcrypt.gensalt(prefix=b&amp;quot;2a&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Multiprocessing, subprocesses and Threading==&lt;br /&gt;
see [[Python_-_Multithreading]] as well&lt;br /&gt;
&lt;br /&gt;
use processes for CPU limited work &amp;lt;br/&amp;gt;&lt;br /&gt;
use threads for I/O limited work&lt;br /&gt;
&lt;br /&gt;
===Simple single process===&lt;br /&gt;
 import subprocess&lt;br /&gt;
 process = subprocess.run([&amp;quot;sudo&amp;quot;, &amp;quot;du&amp;quot;, &amp;quot;--max-depth=1&amp;quot;, mydir], capture_output=True, text=True)&lt;br /&gt;
 print (process.stdout)&lt;br /&gt;
old, depricated way:&lt;br /&gt;
 os.system( &amp;quot;gnuplot &amp;quot; + gpfile)&lt;br /&gt;
&lt;br /&gt;
===Multiprocessing===&lt;br /&gt;
see [[Python - Multithreading]] as well&lt;br /&gt;
&lt;br /&gt;
V2 using pool and starmap&lt;br /&gt;
 import multiprocessing&lt;br /&gt;
 import os&lt;br /&gt;
 &lt;br /&gt;
 def worker(i: int, s: str) -&amp;gt; list:&lt;br /&gt;
     result = (i, s, os.getpid())&lt;br /&gt;
     return result&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     # gen. pile of work&lt;br /&gt;
     l_pile_of_work = []&lt;br /&gt;
     for i in range(1_000):&lt;br /&gt;
         tup = (i, &amp;quot;n&amp;quot; + str(i))&lt;br /&gt;
         l_pile_of_work.append((tup))&lt;br /&gt;
     # gen pool of processes&lt;br /&gt;
     num_processes = min(multiprocessing.cpu_count(), len(l_pile_of_work))&lt;br /&gt;
     pool = multiprocessing.Pool(processes=num_processes)&lt;br /&gt;
     # start processes on pile of work&lt;br /&gt;
     l_results_unsorted = pool.starmap(&lt;br /&gt;
         func=worker, iterable=l_pile_of_work  # each item is a list of 2 parameters&lt;br /&gt;
     )&lt;br /&gt;
     &lt;br /&gt;
     # or if only one parameter:&lt;br /&gt;
     # l_results_unsorted = pool.map(doit_de_district, l_pile_of_work)&lt;br /&gt;
     &lt;br /&gt;
     l_results = sorted(l_results_unsorted)  # sort by i&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
V1&lt;br /&gt;
 import subprocess&lt;br /&gt;
 l_subprocesses = []  # queue list of subprocesses&lt;br /&gt;
 max_processes = 4&lt;br /&gt;
 &lt;br /&gt;
 def process_enqueue(new_process_parameters):&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     # wait for free slot&lt;br /&gt;
     while len(l_subprocesses) &amp;gt;= max_processes:&lt;br /&gt;
         process_remove_finished_from_queue()&lt;br /&gt;
         time.sleep(0.1)  # sleep 0.1s&lt;br /&gt;
     process = subprocess.Popen(new_process_parameters,&lt;br /&gt;
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE,&lt;br /&gt;
                                universal_newlines=True)&lt;br /&gt;
     l_subprocesses.append(process)&lt;br /&gt;
 &lt;br /&gt;
 def process_remove_finished_from_queue():&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     i = 0&lt;br /&gt;
     while i &amp;lt;= len(l_subprocesses) - 1:&lt;br /&gt;
         process = l_subprocesses[i]&lt;br /&gt;
         if process.poll != None:  # has already finished&lt;br /&gt;
             process_print_output(process)&lt;br /&gt;
             l_subprocesses.pop(i)&lt;br /&gt;
         else:  # still running&lt;br /&gt;
             i += 1&lt;br /&gt;
 &lt;br /&gt;
 def process_print_output(process):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;waits for process to finish and prints process output&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     stdout, stderr = process.communicate()&lt;br /&gt;
     if stdout != &#039;&#039;:&lt;br /&gt;
         print(f&#039;Out: {stdout}&#039;)&lt;br /&gt;
     if stderr != &#039;&#039;:&lt;br /&gt;
         print(f&#039;ERROR: {stderr}&#039;)&lt;br /&gt;
 &lt;br /&gt;
 def process_wait_for_all_finished():&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     for process in l_subprocesses:&lt;br /&gt;
         process_print_output(process)&lt;br /&gt;
     l_subprocesses = []  # empty list of done subprocesses&lt;br /&gt;
 &lt;br /&gt;
 process_enqueue(l_parameters1)&lt;br /&gt;
 ...&lt;br /&gt;
 process_enqueue(l_parameters999)&lt;br /&gt;
 process_wait_for_all_finished()&lt;br /&gt;
&lt;br /&gt;
===Threading===&lt;br /&gt;
 import threading&lt;br /&gt;
 import queue&lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 def worker(q_work: queue.Queue, results: dict):&lt;br /&gt;
     while not q_work.empty():&lt;br /&gt;
         i, s = q_work.get()&lt;br /&gt;
         time.sleep(.1)&lt;br /&gt;
         result = (i, s, os.getpid())&lt;br /&gt;
         results[i] = result&lt;br /&gt;
         q_work.task_done()&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &#039;__main__&#039;:&lt;br /&gt;
     d_results = {}  # threads can write into dict&lt;br /&gt;
     # gen. pile of work&lt;br /&gt;
     l_pile_of_work = []&lt;br /&gt;
     for i in range(1_000):&lt;br /&gt;
         tup = (i, &amp;quot;n&amp;quot;+str(i))&lt;br /&gt;
         l_pile_of_work.append((tup))&lt;br /&gt;
     # convert list of work to queue&lt;br /&gt;
     q_pile_of_work = queue.Queue(&lt;br /&gt;
         maxsize=len(l_pile_of_work))  # maxsize=0 -&amp;gt; unlimited&lt;br /&gt;
     for params in l_pile_of_work:&lt;br /&gt;
         q_pile_of_work.put(params)&lt;br /&gt;
     # gen threads&lt;br /&gt;
     num_threads = 100&lt;br /&gt;
     l_threads = []  # List of threads, not used here&lt;br /&gt;
     for i in range(num_threads):&lt;br /&gt;
         t = threading.Thread(name=&#039;myThread-&#039;+str(i),&lt;br /&gt;
                              target=worker,&lt;br /&gt;
                              args=(q_pile_of_work, d_results),&lt;br /&gt;
                              daemon=True)&lt;br /&gt;
         l_threads.append(t)&lt;br /&gt;
         t.start()&lt;br /&gt;
     q_pile_of_work.join()  # wait for all threas to complete&lt;br /&gt;
     l_results_unsorted = d_results.values()&lt;br /&gt;
     l_results = sorted(l_results_unsorted)  # sort by i&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===asyncio — Asynchronous I/O===&lt;br /&gt;
see https://docs.python.org/3/library/asyncio-task.html&lt;br /&gt;
 import asyncio&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 # basics&lt;br /&gt;
 # task = asyncio.create_task(coro())&lt;br /&gt;
 # Wrap the coro coroutine into a Task and schedule its execution. Return the Task object.&lt;br /&gt;
 &lt;br /&gt;
 # sleep&lt;br /&gt;
 # await asyncio.sleep(1)&lt;br /&gt;
 &lt;br /&gt;
 # Running Tasks Concurrently and gathers the return values in list L&lt;br /&gt;
 # L = await asyncio.gather(coro(x1,y1), coro(x2,y2), coro(x3,y3))&lt;br /&gt;
 &lt;br /&gt;
 async def say_after(delay, what):&lt;br /&gt;
     # Coroutines declared with the async/await syntax&lt;br /&gt;
     await asyncio.sleep(delay)&lt;br /&gt;
     print(what)&lt;br /&gt;
 &lt;br /&gt;
 async def main():&lt;br /&gt;
     # The asyncio.create_task() function to run coroutines concurrently as asyncio Tasks.&lt;br /&gt;
     task1 = asyncio.create_task(&lt;br /&gt;
         say_after(1, &#039;hello&#039;))&lt;br /&gt;
 &lt;br /&gt;
     task2 = asyncio.create_task(&lt;br /&gt;
         say_after(1, &#039;world&#039;))&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;started at {time.strftime(&#039;%X&#039;)}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     # Wait until both tasks are completed&lt;br /&gt;
     await task1&lt;br /&gt;
     await task2&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;finished at {time.strftime(&#039;%X&#039;)}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 asyncio.run(main())&lt;br /&gt;
&lt;br /&gt;
==Pandas==&lt;br /&gt;
see [[Pandas]]&lt;br /&gt;
&lt;br /&gt;
==Matplotlib==&lt;br /&gt;
see [[Matplotlib]]&lt;br /&gt;
&lt;br /&gt;
== GUI via tkinter==&lt;br /&gt;
 import tkinter as tk  # no need to install via pip&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 class App(tk.Tk):&lt;br /&gt;
     def __init__(self):&lt;br /&gt;
         super().__init__()&lt;br /&gt;
         self.title(&amp;quot;robot-CClicker&amp;quot;)&lt;br /&gt;
         self.geometry(&amp;quot;200x200+0+1080&amp;quot;)&lt;br /&gt;
         self.resizable(width=False, height=False)&lt;br /&gt;
 &lt;br /&gt;
         self.l_buttons = []&lt;br /&gt;
         self.__create_widgets()&lt;br /&gt;
 &lt;br /&gt;
     def __create_widgets(self):&lt;br /&gt;
         self.btn_click500 = tk.Button(&lt;br /&gt;
             master=self,&lt;br /&gt;
             text=&amp;quot;500 clicks&amp;quot;,&lt;br /&gt;
             width=20,&lt;br /&gt;
             command=lambda: self.clickBigCookie(500),&lt;br /&gt;
         )&lt;br /&gt;
         self.l_buttons.append(self.btn_click500)&lt;br /&gt;
 &lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button.pack(anchor=tk.W)&lt;br /&gt;
 &lt;br /&gt;
     def clickBigCookie(self, num):&lt;br /&gt;
         # TODO: the disabling of the buttons is not working&lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button[&amp;quot;state&amp;quot;] = tk.DISABLED&lt;br /&gt;
         helper.clickIt(self.posBigCockie[0], self.posBigCockie[1], num=num)&lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button[&amp;quot;state&amp;quot;] = tk.NORMAL&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     app = App()&lt;br /&gt;
     app.mainloop()&lt;br /&gt;
&lt;br /&gt;
==Protobuf==&lt;br /&gt;
===convert .proto file to python class===&lt;br /&gt;
see https://www.datascienceblog.net/post/programming/essential-protobuf-guide-python/&lt;br /&gt;
 protoc my_message.proto --python_out ./ &lt;br /&gt;
&lt;br /&gt;
===read/decode protobuf message===&lt;br /&gt;
parse message from file&lt;br /&gt;
 import machine_message_pb2&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.bin&amp;quot;, &amp;quot;rb&amp;quot;) as f:&lt;br /&gt;
     my_message = my_message_pb2.my_message()&lt;br /&gt;
     my_message.ParseFromString(f.read())&lt;br /&gt;
 print(machine_message)&lt;br /&gt;
&lt;br /&gt;
===create/encode protobuf message===&lt;br /&gt;
 import machine_message_pb2&lt;br /&gt;
 &lt;br /&gt;
 my_message = my_message_pb2.my_message()&lt;br /&gt;
 my_message.data.field1 = 1&lt;br /&gt;
 my_message.data.field2 = &amp;quot;asdf&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.bin&amp;quot;, &amp;quot;wb&amp;quot;) as f:&lt;br /&gt;
     f.write(my_message.data.SerializeToString())&lt;br /&gt;
&lt;br /&gt;
==venv / virtual environments==&lt;br /&gt;
 pip install --upgrade pip&lt;br /&gt;
 python -m venv .venv --prompt $(basename $(pwd))&lt;br /&gt;
 source .venv/bin/activate&lt;br /&gt;
 pip install -r requirements.txt&lt;br /&gt;
 ...&lt;br /&gt;
 deactivate&lt;br /&gt;
&lt;br /&gt;
==Pydantic data validation==&lt;br /&gt;
===Function input parameter validation===&lt;br /&gt;
see https://docs.pydantic.dev/usage/validation_decorator/&lt;br /&gt;
 @validate_arguments&lt;br /&gt;
 def sum(x: int, y: float) -&amp;gt; float:&lt;br /&gt;
     print(x, y)&lt;br /&gt;
     return x + y&lt;br /&gt;
  &lt;br /&gt;
 print(sum(1.1, 1.1))&lt;br /&gt;
 # -&amp;gt; 2.1&lt;br /&gt;
&lt;br /&gt;
==Read config file==&lt;br /&gt;
===TOML===&lt;br /&gt;
see https://learnxinyminutes.com/docs/toml/&lt;br /&gt;
&lt;br /&gt;
minimal example:&lt;br /&gt;
config.toml&lt;br /&gt;
 # SAP API endpoint&lt;br /&gt;
 [general]&lt;br /&gt;
 sleep_time = 60&lt;br /&gt;
 use_proxy = true&lt;br /&gt;
tool.py&lt;br /&gt;
 try:&lt;br /&gt;
     import tomllib  # comes with python3.11&lt;br /&gt;
 except ModuleNotFoundError:&lt;br /&gt;
     import tomli as tomllib  # pip install tomli&lt;br /&gt;
 with open(&amp;quot;config.toml&amp;quot;, &amp;quot;rb&amp;quot;) as f:&lt;br /&gt;
    o = tomllib.load(f)  # type: ignore&lt;br /&gt;
 if o[&amp;quot;settings&amp;quot;][&amp;quot;use_proxy&amp;quot;]:&lt;br /&gt;
 ...&lt;br /&gt;
for validation, see https://realpython.com/python-toml/&lt;br /&gt;
&lt;br /&gt;
====TOML Write====&lt;br /&gt;
In for deployments I sometime want to modify a TOML config file for production, based on the dev version.&lt;br /&gt;
 import tomli_w  # pip install tomli-w&lt;br /&gt;
 p_in = Path(__file__).parent.parent / &amp;quot;.streamlit/config.toml&amp;quot;&lt;br /&gt;
 p_out = p_in.parent / &amp;quot;config-prod.toml&amp;quot; &lt;br /&gt;
 with p_in.open(&amp;quot;rb&amp;quot;) as fh:&lt;br /&gt;
     o = tomllib.load(fh)&lt;br /&gt;
 o[&amp;quot;server&amp;quot;][&amp;quot;fileWatcherType&amp;quot;] = &amp;quot;none&amp;quot;&lt;br /&gt;
 del o[&amp;quot;server&amp;quot;][&amp;quot;address&amp;quot;]&lt;br /&gt;
  with p_out.open(&amp;quot;wb&amp;quot;) as fh:&lt;br /&gt;
     tomli_w.dump(o, fh)&lt;br /&gt;
&lt;br /&gt;
===ConfigParser: INI Config File Reading===&lt;br /&gt;
better use TOML&lt;br /&gt;
&lt;br /&gt;
Config.ini&lt;br /&gt;
 [Section1]&lt;br /&gt;
 Cursor         = 205E18&lt;br /&gt;
 Grandma        =  18E18&lt;br /&gt;
 Farm           =  11E18&lt;br /&gt;
 Mine           = 514E18&lt;br /&gt;
 Factory        = 155E18&lt;br /&gt;
test.py&lt;br /&gt;
 from configparser import ConfigParser&lt;br /&gt;
 &lt;br /&gt;
 config = ConfigParser(&lt;br /&gt;
     interpolation=None&lt;br /&gt;
 )  # interpolation=None -&amp;gt; treats % in values as char % instead of interpreting it&lt;br /&gt;
 config.read(&amp;quot;Config.ini&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(config.getint(&amp;quot;Section1&amp;quot;, &amp;quot;key1&amp;quot;))&lt;br /&gt;
 print(config.getfloat(&amp;quot;Section1&amp;quot;, &amp;quot;key2&amp;quot;))&lt;br /&gt;
 print(config.get(&amp;quot;Section1&amp;quot;, &amp;quot;key3&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 for sec in config.sections():&lt;br /&gt;
     d_settings = {}&lt;br /&gt;
     for key in config.options(sec):&lt;br /&gt;
         value = config.get(sec, key)&lt;br /&gt;
         d_settings[key] = value&lt;br /&gt;
         print(&amp;quot;%15s : %s&amp;quot; % (key, value))&lt;br /&gt;
&lt;br /&gt;
==VCard / vcf==&lt;br /&gt;
 import codecs&lt;br /&gt;
 import vobject  # pip install vobject&lt;br /&gt;
 &lt;br /&gt;
 obj = vobject.readComponents(codecs.open(file_in, encoding=&amp;quot;utf-8&amp;quot;).read())  # type: ignore&lt;br /&gt;
 contacts: list[vobject.base.Component] = list(obj)  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 card = contacts[0]&lt;br /&gt;
 &lt;br /&gt;
 if &amp;quot;bday&amp;quot; not in card.contents:&lt;br /&gt;
     continue&lt;br /&gt;
 &lt;br /&gt;
 # bday: remove &#039;VALUE&#039;: [&#039;DATE&#039;]&lt;br /&gt;
 card.contents[&amp;quot;bday&amp;quot;][0].params = {}  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 # remove all fields but &amp;quot;bday&amp;quot;, &amp;quot;n&amp;quot;&lt;br /&gt;
 for key in card.contents.copy():  # loop over copy, to allow for deleting keys&lt;br /&gt;
     if key not in (&amp;quot;n&amp;quot;, &amp;quot;bday&amp;quot;):&lt;br /&gt;
         del card.contents[key]&lt;br /&gt;
 &lt;br /&gt;
 # recreate fn based on n&lt;br /&gt;
 card.add(&amp;quot;fn&amp;quot;)&lt;br /&gt;
 n = card.contents[&amp;quot;n&amp;quot;][0]&lt;br /&gt;
 fn = f&amp;quot;{n.value.given} {n.value.additional} {n.value.family}&amp;quot;  # type: ignore&lt;br /&gt;
 fn = re.sub(r&amp;quot;\s+&amp;quot;, &amp;quot; &amp;quot;, fn)&lt;br /&gt;
 card.fn.value = fn  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.vcf&amp;quot;, mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fhOut:&lt;br /&gt;
     fhOut.write(card.serialize())&lt;br /&gt;
&lt;br /&gt;
==Sentry Exception monitoring==&lt;br /&gt;
see [[Sentry]]&lt;br /&gt;
==MQTT==&lt;br /&gt;
 #!/usr/bin/env python3.12&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Read power data from Tasmota device via MQTT.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 import json&lt;br /&gt;
 from typing import Any&lt;br /&gt;
 &lt;br /&gt;
 import paho.mqtt.client as mqtt&lt;br /&gt;
 from mqtt_credentials import hostname, password, port, username&lt;br /&gt;
 from paho.mqtt.reasoncodes import ReasonCode&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_connect(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     flags: dict[str, int],&lt;br /&gt;
     reason_code: ReasonCode,&lt;br /&gt;
     properties: Any,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when the client connects MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     if reason_code == 0:&lt;br /&gt;
         print(&amp;quot;Connected to MQTT&amp;quot;)&lt;br /&gt;
         # print(f&amp;quot;MQTT protocol version: {mqtt_client._protocol}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         # Subscribing in on_connect() means that if we lose the connection and&lt;br /&gt;
         # reconnect then subscriptions will be renewed.&lt;br /&gt;
         mqtt_client.message_callback_add(&amp;quot;tele/tasmota_MT681/SENSOR&amp;quot;, on_message)&lt;br /&gt;
         mqtt_client.subscribe([(&amp;quot;tele/tasmota_MT681/SENSOR&amp;quot;, 0)])&lt;br /&gt;
 &lt;br /&gt;
     else:&lt;br /&gt;
         print(f&amp;quot;Connection to MQTT broker failed. Error: {reason_code}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_disconnect(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     reason_code: ReasonCode,&lt;br /&gt;
     properties: Any,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when the client is disconnected from the MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     if reason_code &amp;gt; 0:&lt;br /&gt;
         print(f&amp;quot;Unexpected disconnection. Error: {reason_code}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_message(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     message: mqtt.MQTTMessage,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when a Tasmota message is received from the MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     s = message.payload.decode()&lt;br /&gt;
     data = json.loads(s)&lt;br /&gt;
     # {&#039;Time&#039;: &#039;2024-03-16T06:44:08&#039;, &#039;MT681&#039;: {&#039;Total_in&#039;: 16.4396, &#039;Power_cur&#039;: 80, &#039;Total_out&#039;: 14.6403}}  # noqa: E501&lt;br /&gt;
 &lt;br /&gt;
     total_i = data[&amp;quot;MT681&amp;quot;][&amp;quot;Total_in&amp;quot;]&lt;br /&gt;
     total_o = data[&amp;quot;MT681&amp;quot;][&amp;quot;Total_out&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;In:\t{total_i}\nOut:\t{total_o}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     mqtt_client.disconnect()&lt;br /&gt;
     mqtt_client.loop_stop()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 try:&lt;br /&gt;
     # Create an MQTT client instance&lt;br /&gt;
     mqtt_client: mqtt.Client = mqtt.Client(&lt;br /&gt;
         mqtt.CallbackAPIVersion.VERSION2, &amp;quot;Python-Client-1&amp;quot;&lt;br /&gt;
     )  # type: ignore&lt;br /&gt;
     mqtt_client.username_pw_set(username, password)&lt;br /&gt;
 &lt;br /&gt;
     mqtt_client.on_connect = on_connect&lt;br /&gt;
 &lt;br /&gt;
     # Connect to the MQTT broker&lt;br /&gt;
     mqtt_client.connect(hostname, port, keepalive=60)&lt;br /&gt;
 &lt;br /&gt;
     # NOT: while True, as that eats the CPU !!!&lt;br /&gt;
     mqtt_client.loop_forever()&lt;br /&gt;
 except KeyboardInterrupt:&lt;br /&gt;
     print(&amp;quot;KeyboardInterrupt, disconnecting from MQTT broker.&amp;quot;)&lt;br /&gt;
     mqtt_client.disconnect()&lt;br /&gt;
     mqtt_client.loop_stop()&lt;br /&gt;
&lt;br /&gt;
==Caching Functions==&lt;br /&gt;
 from functools import lru_cache&lt;br /&gt;
 &lt;br /&gt;
 @lru_cache(maxsize=128)  # None equals to @cache declarator&lt;br /&gt;
 def fnc(n:int):&lt;br /&gt;
&lt;br /&gt;
==Performance/Resources==&lt;br /&gt;
===RAM/Memory consumption/bottleneck===&lt;br /&gt;
 import tracemalloc&lt;br /&gt;
 tracemalloc.start()&lt;br /&gt;
 &lt;br /&gt;
 # ...&lt;br /&gt;
 # Code&lt;br /&gt;
 # example:&lt;br /&gt;
 lst = list(range(1_000_000))&lt;br /&gt;
 # ...&lt;br /&gt;
 &lt;br /&gt;
 max_bytes = tracemalloc.get_traced_memory()[0]&lt;br /&gt;
 print(f&amp;quot;{round(max_bytes / 10**6)}MB&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 tracemalloc.stop()&lt;br /&gt;
&lt;br /&gt;
===Profiling / Count and Runtime per Function===&lt;br /&gt;
 call_stats = {}&lt;br /&gt;
 &lt;br /&gt;
 def track_function_usage(func: Callable) -&amp;gt; Callable:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Annotation for gathering runtime statistics.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     @wraps(func)&lt;br /&gt;
     def wrapper(*args: tuple, **kwargs: dict):  # noqa: ANN202&lt;br /&gt;
         start_time = time.time()&lt;br /&gt;
         result = func(*args, **kwargs)  # Call the original function&lt;br /&gt;
         end_time = time.time()&lt;br /&gt;
         call_stats[func.__name__][&amp;quot;calls&amp;quot;] += 1&lt;br /&gt;
         call_stats[func.__name__][&amp;quot;total_time&amp;quot;] += end_time - start_time&lt;br /&gt;
         return result&lt;br /&gt;
     return wrapper&lt;br /&gt;
&lt;br /&gt;
==JWT Token==&lt;br /&gt;
  try:&lt;br /&gt;
      decoded_token = jwt.decode(token, options={&amp;quot;verify_signature&amp;quot;: False})&lt;br /&gt;
  except jwt.ExpiredSignatureError:&lt;br /&gt;
      msg = &amp;quot;Token has expired.&amp;quot;&lt;br /&gt;
      logger.exception(msg)&lt;br /&gt;
  except jwt.InvalidTokenError:&lt;br /&gt;
      msg = &amp;quot;Invalid token.&amp;quot;&lt;br /&gt;
      logger.exception(msg)&lt;br /&gt;
&lt;br /&gt;
==Streamlit==&lt;br /&gt;
see https://github.com/entorb/streamlit-examples for a collection of my examples&lt;br /&gt;
&lt;br /&gt;
===Minimum Example===&lt;br /&gt;
 # src/app.py&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import streamlit as st&lt;br /&gt;
 from streamlit.logger import get_logger&lt;br /&gt;
 logger = get_logger(Path(__file__).stem)&lt;br /&gt;
 logger.info(&amp;quot;Start&amp;quot;)&lt;br /&gt;
 st.set_page_config(page_title=&amp;quot;AppTitle&amp;quot;, page_icon=None, layout=&amp;quot;wide&amp;quot;)&lt;br /&gt;
 st.title(&amp;quot;MyPageTitle&amp;quot;)&lt;br /&gt;
 st.header(&amp;quot;MyHeader&amp;quot;)&lt;br /&gt;
 st.subheader(&amp;quot;MySubHeader&amp;quot;)&lt;br /&gt;
 sel_param = st.selectbox(&amp;quot;Parameter&amp;quot;, (&amp;quot;count&amp;quot;, &amp;quot;sum&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
 # .streamlit/config.toml&lt;br /&gt;
 [browser]&lt;br /&gt;
 gatherUsageStats = false&lt;br /&gt;
 &lt;br /&gt;
 [server]&lt;br /&gt;
 port = 8501&lt;br /&gt;
&lt;br /&gt;
 # run it&lt;br /&gt;
 streamlit run src/app.py&lt;br /&gt;
&lt;br /&gt;
====Reduce Logging for K8s Deployed Docker Containers====&lt;br /&gt;
 # reduce log output&lt;br /&gt;
 for logger_name in [&lt;br /&gt;
     &amp;quot;streamlit.runtime.caching.cache_utils&amp;quot;,&lt;br /&gt;
     &amp;quot;streamlit.runtime.caching.storage.in_memory_cache_storage_wrapper&amp;quot;,&lt;br /&gt;
     &amp;quot;streamlit.runtime.runtime&amp;quot;,&lt;br /&gt;
     &amp;quot;urllib3.connectionpool&amp;quot;,&lt;br /&gt;
 ]:&lt;br /&gt;
     get_logger(logger_name).setLevel(logging.WARNING)&lt;br /&gt;
&lt;br /&gt;
===Chart via Altair===&lt;br /&gt;
====Line Chart====&lt;br /&gt;
 chart = st.line_chart(df[&amp;quot;value&amp;quot;])&lt;br /&gt;
 # corresponds to&lt;br /&gt;
 import altair as alt&lt;br /&gt;
 c = (&lt;br /&gt;
    alt.Chart(df)&lt;br /&gt;
    .mark_line()&lt;br /&gt;
    .encode(&lt;br /&gt;
        x=alt.X(&amp;quot;created&amp;quot;, title=None),&lt;br /&gt;
        y=alt.Y(&amp;quot;value&amp;quot;, title=None),&lt;br /&gt;
    )&lt;br /&gt;
 )&lt;br /&gt;
 st.altair_chart(c, use_container_width=True)&lt;br /&gt;
&lt;br /&gt;
====Bar Chart====&lt;br /&gt;
 chart = (&lt;br /&gt;
     alt.Chart(df)&lt;br /&gt;
     .mark_bar()&lt;br /&gt;
     .encode(&lt;br /&gt;
         x=alt.X(&amp;quot;yearmonth(date):T&amp;quot;, title=&amp;quot;Month&amp;quot;),&lt;br /&gt;
         y=alt.Y(f&amp;quot;count:Q&amp;quot;, title=None),&lt;br /&gt;
         color=&amp;quot;status:N&amp;quot;,&lt;br /&gt;
         tooltip=[&amp;quot;yearmonth(date):T&amp;quot;, &amp;quot;status:N&amp;quot;, &amp;quot;cnt:Q&amp;quot;],&lt;br /&gt;
     )&lt;br /&gt;
     # .properties(width=600, height=400)&lt;br /&gt;
 )&lt;br /&gt;
 st.altair_chart(chart, use_container_width=True)&lt;br /&gt;
&lt;br /&gt;
 :T stands for Temporal. It indicates that the field contains date or time values.&lt;br /&gt;
 :N stands for Nominal. It indicates that the field contains categorical data, which represents discrete categories or labels.&lt;br /&gt;
 :Q stands for Quantitative. It indicates that the field contains numerical data that can be measured and compared.&lt;br /&gt;
&lt;br /&gt;
===Excel Download===&lt;br /&gt;
 def excel_download_buttons(df: pd.DataFrame, file_name: str = &amp;quot;export.xlsx&amp;quot;) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Show prepare data and download buttons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if st.button(label=&amp;quot;Click to prepare download&amp;quot;):&lt;br /&gt;
         buffer = io.BytesIO()&lt;br /&gt;
         with pd.ExcelWriter(buffer, engine=&amp;quot;xlsxwriter&amp;quot;) as writer:&lt;br /&gt;
             df.to_excel(writer, sheet_name=&amp;quot;Sheet1&amp;quot;, index=False)&lt;br /&gt;
             writer.close()&lt;br /&gt;
 &lt;br /&gt;
             st.download_button(&lt;br /&gt;
                 label=&amp;quot;Download data as Excel&amp;quot;,&lt;br /&gt;
                 data=buffer,&lt;br /&gt;
                 file_name=file_name,&lt;br /&gt;
                 mime=&amp;quot;application/vnd.ms-excel&amp;quot;,&lt;br /&gt;
             )&lt;br /&gt;
&lt;br /&gt;
==UV Package Manager==&lt;br /&gt;
 # create/update lockfile&lt;br /&gt;
 uv lock --upgrade&lt;br /&gt;
 # sync venv&lt;br /&gt;
 uv sync --upgrade&lt;br /&gt;
 # extract package info to requirements.txt&lt;br /&gt;
 uv export --format requirements.txt --no-dev --no-hashes -o requirements.txt&lt;br /&gt;
&lt;br /&gt;
Update Python version for my current project&lt;br /&gt;
 uv venv --python=3.13.7&lt;br /&gt;
&lt;br /&gt;
===Update UV===&lt;br /&gt;
 uv self update&lt;br /&gt;
 # or&lt;br /&gt;
 brew update &amp;amp;&amp;amp; brew upgrade uv&lt;br /&gt;
&lt;br /&gt;
===Update Python Versions===&lt;br /&gt;
 uv python upgrade&lt;br /&gt;
&lt;br /&gt;
===UV + Docker on readOnlyRootFilesystem===&lt;br /&gt;
Example 1: https://github.com/SWE-Group-23/CM22007---Source/blob/10d02004f3a4208f7b30d37a0a87e89532a23575/create-service.sh#L117&lt;br /&gt;
&lt;br /&gt;
Dockerfile&lt;br /&gt;
 WORKDIR /app&lt;br /&gt;
 COPY . .&lt;br /&gt;
 &lt;br /&gt;
 ENV CASS_DRIVER_NO_EXTENSIONS=1&lt;br /&gt;
 &lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache/uv \\&lt;br /&gt;
     --mount=type=bind,source=uv.lock,target=uv.lock \\&lt;br /&gt;
     --mount=type=bind,source=pyproject.toml,target=pyproject.toml \\&lt;br /&gt;
     uv sync --frozen --no-install-project&lt;br /&gt;
     &lt;br /&gt;
 RUN uv pip install -e shared/&lt;br /&gt;
 &lt;br /&gt;
 RUN addgroup -S group1 &amp;amp;&amp;amp; adduser -S user1 -G group1 -u 1000&lt;br /&gt;
 RUN chown -R user1:group1 /app&lt;br /&gt;
 USER user1:group1&lt;br /&gt;
 &lt;br /&gt;
 CMD [&amp;quot;uv&amp;quot;, &amp;quot;run&amp;quot;, &amp;quot;main.py&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
Deployment&lt;br /&gt;
 securityContext:&lt;br /&gt;
   readOnlyRootFilesystem: true&lt;br /&gt;
   allowPrivilegeEscalation: false&lt;br /&gt;
   runAsNonRoot: true&lt;br /&gt;
   runAsUser: 1000&lt;br /&gt;
   capabilities:&lt;br /&gt;
     drop:&lt;br /&gt;
       - ALL&lt;br /&gt;
   seccompProfile:&lt;br /&gt;
     type: RuntimeDefault&lt;br /&gt;
 volumeMounts:&lt;br /&gt;
   - mountPath: /home/user1/.cache/uv&lt;br /&gt;
     name: uv&lt;br /&gt;
   - mountPath: /tmp&lt;br /&gt;
     name: temp&lt;br /&gt;
&lt;br /&gt;
Example 2: [https://hynek.me/articles/docker-uv/ Production-ready Python Docker Containers with uv]&lt;br /&gt;
&lt;br /&gt;
Dockerfile (without the comments)&lt;br /&gt;
 ENV UV_LINK_MODE=copy \&lt;br /&gt;
     UV_COMPILE_BYTECODE=1 \&lt;br /&gt;
     UV_PYTHON_DOWNLOADS=never \&lt;br /&gt;
     UV_PYTHON=python3.12 \&lt;br /&gt;
     UV_PROJECT_ENVIRONMENT=/app&lt;br /&gt;
 &lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache \&lt;br /&gt;
     --mount=type=bind,source=uv.lock,target=uv.lock \&lt;br /&gt;
     --mount=type=bind,source=pyproject.toml,target=pyproject.toml \&lt;br /&gt;
     uv sync \&lt;br /&gt;
         --locked \&lt;br /&gt;
         --no-dev \&lt;br /&gt;
         --no-install-project&lt;br /&gt;
 &lt;br /&gt;
 COPY . /src&lt;br /&gt;
 WORKDIR /src&lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache \&lt;br /&gt;
     uv sync \&lt;br /&gt;
         --locked \&lt;br /&gt;
         --no-dev \&lt;br /&gt;
         --no-editable&lt;br /&gt;
&lt;br /&gt;
=== Vulture: Search for dead code ===&lt;br /&gt;
see [https://github.com/jendrikseipp/vulture GitHub]&lt;br /&gt;
&lt;br /&gt;
pyproject.toml&lt;br /&gt;
 [tool.vulture]&lt;br /&gt;
 paths = [&amp;quot;src&amp;quot;]&lt;br /&gt;
 # exclude = [&amp;quot;src/models.py&amp;quot;]&lt;br /&gt;
 ignore_names = [&lt;br /&gt;
     &amp;quot;LOGGER&amp;quot;,&lt;br /&gt;
 ]&lt;br /&gt;
&lt;br /&gt;
.pre-commit-config.yaml&lt;br /&gt;
   # Vulture: find dead code&lt;br /&gt;
   - repo: https://github.com/jendrikseipp/vulture&lt;br /&gt;
     rev: v2.16&lt;br /&gt;
     hooks:&lt;br /&gt;
       - id: vulture&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Uberspace&amp;diff=5404</id>
		<title>Uberspace</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Uberspace&amp;diff=5404"/>
		<updated>2026-04-08T05:44:30Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Streamlit */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Webserver]][[Category:Linux]]&lt;br /&gt;
==Manuals==&lt;br /&gt;
https://manual.uberspace.de and https://lab.uberspace.de&lt;br /&gt;
&lt;br /&gt;
==PHP==&lt;br /&gt;
see https://manual.uberspace.de/lang-php/&lt;br /&gt;
&lt;br /&gt;
to restart&lt;br /&gt;
 uberspace tools restart php&lt;br /&gt;
&lt;br /&gt;
===php-fpm===&lt;br /&gt;
 less /opt/uberspace/etc/$USER/php-fpm.conf&lt;br /&gt;
&lt;br /&gt;
==Python FastAPI==&lt;br /&gt;
see https://lab.uberspace.de/guide_fastapi/&lt;br /&gt;
 &lt;br /&gt;
 # var 1: venv&lt;br /&gt;
 python3.11 -m venv venv&lt;br /&gt;
 source venv/bin/activate&lt;br /&gt;
 pip install fastapi uvicorn&lt;br /&gt;
 pip install fastapi.responses&lt;br /&gt;
 pip install gunicorn uvloop httptools&lt;br /&gt;
 deactivate&lt;br /&gt;
 &lt;br /&gt;
 # var 2: not use venv&lt;br /&gt;
 pip3.11 install --user fastapi uvicorn fastapi.responses gunicorn uvloop httptools&lt;br /&gt;
 &lt;br /&gt;
 # config backend&lt;br /&gt;
 uberspace web backend set /strava-be --http --port 9001&lt;br /&gt;
 # --remove-prefix&lt;br /&gt;
 uberspace web backend list&lt;br /&gt;
 &lt;br /&gt;
 supervisorctl restart fastapi&lt;br /&gt;
 &lt;br /&gt;
~/fastapi/conf.py&lt;br /&gt;
 #!/usr/bin/env python3.10&lt;br /&gt;
 &lt;br /&gt;
 # apply changes via&lt;br /&gt;
 # supervisorctl restart fastapi&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 &lt;br /&gt;
 app_path = os.environ[&amp;quot;HOME&amp;quot;] + &amp;quot;/fastapi&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # Gunicorn configuration&lt;br /&gt;
 wsgi_app = &amp;quot;main:api&amp;quot;&lt;br /&gt;
 bind = &amp;quot;:9001&amp;quot;&lt;br /&gt;
 chdir = app_path&lt;br /&gt;
 workers = 1&lt;br /&gt;
 worker_class = &amp;quot;uvicorn.workers.UvicornWorker&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # errorlog = app_path + &amp;quot;/errors.log&amp;quot;&lt;br /&gt;
 # accesslog = app_path + &amp;quot;/access.log&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
~/fastapi/main.py&lt;br /&gt;
 #!/usr/bin/env python3.10&lt;br /&gt;
 &lt;br /&gt;
 # apply changes via&lt;br /&gt;
 # supervisorctl restart fastapi&lt;br /&gt;
 &lt;br /&gt;
 from fastapi import FastAPI&lt;br /&gt;
 from fastapi.responses import JSONResponse&lt;br /&gt;
 from pydantic import BaseModel&lt;br /&gt;
 &lt;br /&gt;
 import subprocess&lt;br /&gt;
  &lt;br /&gt;
 class StravaSession(BaseModel):&lt;br /&gt;
     sessionId: str&lt;br /&gt;
 &lt;br /&gt;
 api = FastAPI()&lt;br /&gt;
  &lt;br /&gt;
 @api.post(&amp;quot;/strava-be/activityStats2/&amp;quot;)&lt;br /&gt;
 async def activityStats2(session: StravaSession):&lt;br /&gt;
     # return {session}&lt;br /&gt;
     response = {}&lt;br /&gt;
     # response = {&amp;quot;session&amp;quot;: session.sessionId}&lt;br /&gt;
     process = subprocess.run(&lt;br /&gt;
         [&lt;br /&gt;
             &amp;quot;python3.10&amp;quot;,&lt;br /&gt;
             &amp;quot;/var/www/virtual/entorb/html/strava/activityStats2.py&amp;quot;,&lt;br /&gt;
             session.sessionId,&lt;br /&gt;
         ],&lt;br /&gt;
         capture_output=True,&lt;br /&gt;
     )&lt;br /&gt;
 &lt;br /&gt;
     if process.returncode == 0:&lt;br /&gt;
         response[&amp;quot;status&amp;quot;] = &amp;quot;ok&amp;quot;&lt;br /&gt;
         response_code = 200&lt;br /&gt;
         # response[&amp;quot;process_stdout&amp;quot;] = process.stdout.decode()&lt;br /&gt;
     else:&lt;br /&gt;
         response[&amp;quot;status&amp;quot;] = &amp;quot;error&amp;quot;&lt;br /&gt;
         response_code = 400&lt;br /&gt;
         # response[&amp;quot;process_stdout&amp;quot;] = process.stdout.decode()&lt;br /&gt;
         response[&amp;quot;error_message&amp;quot;] = process.stderr.decode()&lt;br /&gt;
     return JSONResponse(content=response, status_code=response_code)&lt;br /&gt;
 &lt;br /&gt;
 # To test this app locally, uncomment:&lt;br /&gt;
 # import uvicorn&lt;br /&gt;
 # uvicorn.run(api, host=&amp;quot;localhost&amp;quot;, port=8001)&lt;br /&gt;
 &lt;br /&gt;
 # curl -i -X POST &amp;quot;https://entorb.net/strava-be/activityStats2/&amp;quot; -H &amp;quot;Content-Type: application/json&amp;quot; -d &#039;{&amp;quot;sessionId&amp;quot;: &amp;quot;1234&amp;quot;}&#039;&lt;br /&gt;
&lt;br /&gt;
==Checkliste neuer U7 Account==&lt;br /&gt;
 ~/.bashrc und ~/.bash_profile von altem angeglichen&lt;br /&gt;
Logs aktiviert&lt;br /&gt;
 uberspace web log access enable&lt;br /&gt;
 uberspace web log apache_error enable&lt;br /&gt;
 uberspace web log php_error enable&lt;br /&gt;
 uberspace web errorpage 500 disable&lt;br /&gt;
&lt;br /&gt;
==Probleme bei der Migration Uberspace U6 zu U7==&lt;br /&gt;
===0. E-Mails===&lt;br /&gt;
Es scheinen keine E-Mails mehr rein und raus zu gehen. (Account xxx@entorb.net). &lt;br /&gt;
&lt;br /&gt;
Neuer Webmailer ist https://webmail.uberspace.de , dieser zeigt aber erst nach Abschluss der Migration auf das neue Postfach. Solange die Migration nicht abgeschlossen ist, muss im Mail Client (wie Thunderbird) dieser Login für den U7 Mail Account verwendet werden:&lt;br /&gt;
statt xxx@entorb.net -&amp;gt; xxx@entorb.uber.space&lt;br /&gt;
&lt;br /&gt;
===1. HTTPS Zertifikat===&lt;br /&gt;
HTTPS Zertifikat war auf einmal nicht mehr gültig für https://www.entorb.net . Das hatte erst auch auf dem U7 funktioniert, nun plötzlich nicht mehr. UPDATE: Problem verschwand von alleine, hing vielleicht mit dem DNS Umzug zusammen.&lt;br /&gt;
&lt;br /&gt;
===2. bestehende ezmlm Mailinglisten===&lt;br /&gt;
ezmlm ist gemäß [https://lab.uberspace.de/guide_ezmlm.html Anleitung] installiert, allerdings sagt mir&lt;br /&gt;
 ezmlm-make -+ ~/ezmlm/oldlist ~/.qmail-oldlist oldlist entorb.uber.space&lt;br /&gt;
 &amp;gt; ezmlm-make: fatal: unable to stat /etc/ezmlm/de: file does not exist&lt;br /&gt;
&lt;br /&gt;
Wenn ich hingegen testweise einen neuen Verteiler anlege tut es:&lt;br /&gt;
 ezmlm-make -A -u -m -5 xxx@entorb.net ~/ezmlm/mylist ~/.qmail-mylist&lt;br /&gt;
mylist entorb.uber.space&lt;br /&gt;
und diesen kann ich dann auch bearbeiten via&lt;br /&gt;
 ezmlm-make -+ ~/ezmlm/mylist ~/.qmail-mylist mylist entorb.uber.space&lt;br /&gt;
&lt;br /&gt;
Lösung:&lt;br /&gt;
 in ezmlm/oldlist/ezmlmrc die entsprechende Zeile löschen&lt;br /&gt;
&lt;br /&gt;
===3. Zugriff auf Dateien außerhab von ~/html===&lt;br /&gt;
Ich hatte einige Dateien (wie zB eine sqlite DB) in meinem Home (~/) liegen. Auf diese sollen Skripte die sich unter&lt;br /&gt;
 ~/html-&amp;gt;/var/www/virtual/entorb/html &lt;br /&gt;
befinden zugreifen können. Das funktionierte nicht mehr, da SELinux hier rein grätscht &lt;br /&gt;
&lt;br /&gt;
Beste Lösung: Die Dateien auf die der httpd zugreifen soll nach&lt;br /&gt;
 /var/www/virtual/entorb/ &lt;br /&gt;
verschieben und im Home Symlink hinterlassen.&lt;br /&gt;
&lt;br /&gt;
Erster Ansatz: der SELinux Kontexte lässt sich so setzen (Siehe [https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/selinux_users_and_administrators_guide/sect-security-enhanced_linux-working_with_selinux-selinux_contexts_labeling_files]&lt;br /&gt;
).&lt;br /&gt;
 chcon -t httpd_sys_content_t file-name&lt;br /&gt;
half hier aber nicht:&lt;br /&gt;
 ls -laZ /home/ |grep ento&lt;br /&gt;
 drwx------. entorb      entorb      unconfined_u:object_r:user_home_dir_t:s0 entorb&lt;br /&gt;
&lt;br /&gt;
===4. Perl Module, wie zB Excel::Writer::XLSX===&lt;br /&gt;
Installiert via&lt;br /&gt;
 cpanm Excel::Writer::XLSX&lt;br /&gt;
&lt;br /&gt;
Führe ich das Skript ~/html/test.pl in der Shell aus, wird das Modul&lt;br /&gt;
gefunden und geladen. Im Browser via https://entorb.net/test.pl wird&lt;br /&gt;
das Modul nicht gefunden.&lt;br /&gt;
&lt;br /&gt;
Ursache 4.1 war dass SELinux den Zugriff des httpd auf /home/entorb/perl5 blockierte&lt;br /&gt;
&lt;br /&gt;
Lösung:&lt;br /&gt;
~/perl5 Verzeichnis nach &lt;br /&gt;
 /var/www/virtual/entorb/perl5&lt;br /&gt;
verschoben und im Home einen Symlink hinterlassen. &lt;br /&gt;
&lt;br /&gt;
Dann zuerst lib::local installiert, dann andere Module.&lt;br /&gt;
&lt;br /&gt;
Ursache 4.2 war dass ich in der .bashrc dies gesetzt habe:&lt;br /&gt;
 eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)&lt;br /&gt;
das musste ich im Perl Skript ebenfalls hinterlegen:&lt;br /&gt;
 use lib (&#039;/var/www/virtual/entorb/perl5/lib/perl5&#039;);&lt;br /&gt;
 # aber !kein! use local::lib; !!!&lt;br /&gt;
&lt;br /&gt;
===5. sendmail===&lt;br /&gt;
ich stellte fest, dass ich im U7 aus Python oder Perl CGI Skripten nicht mehr auf sendmail zugreifen kann. Über die Shell kommt die Mail sofort an. Beispiel:&lt;br /&gt;
8&amp;lt;---&lt;br /&gt;
 import os&lt;br /&gt;
 SENDMAIL = &amp;quot;/usr/sbin/sendmail&amp;quot;&lt;br /&gt;
 to = &amp;quot;xxx@entorb.net&amp;quot;&lt;br /&gt;
 subject   = &amp;quot;testmail&amp;quot;&lt;br /&gt;
 sender = &amp;quot;U7 &amp;lt;no-reply@entorb.net&amp;gt;&amp;quot;&lt;br /&gt;
 body = &amp;quot;leer&amp;quot;&lt;br /&gt;
 mail = f&amp;quot;To: {to}\nSubject: {subject}\nFrom: {sender}\nContent-Type:&lt;br /&gt;
 text/plain; charset=\&amp;quot;utf-8\&amp;quot;\n\n{body}&amp;quot;&lt;br /&gt;
 p = os.popen(f&amp;quot;{SENDMAIL} -t -i&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
 p.write(mail)&lt;br /&gt;
 p.close()&lt;br /&gt;
 8&amp;lt;---&lt;br /&gt;
Lösung: statt Versand aus Web-Skripten/Seiten via sendmail, besser aus den Skripten ein Insert in eine SQLite DB machen und diese via cronjob und php mail() Funktion periodisch abarbeiten.&lt;br /&gt;
&lt;br /&gt;
===6. Wiederholung der Migration===&lt;br /&gt;
Entweder einfach Migration auf dem U6 nochmal starten via&lt;br /&gt;
 uberspace-move-account -u entorb&lt;br /&gt;
oder in Dashboard den Umzug abbrechen und neu starten -&amp;gt; anderer Server.&lt;br /&gt;
&lt;br /&gt;
===7. MySQL DB Passwort angeblich zu kurz===&lt;br /&gt;
Das Migrationsskript hatte behauptet, dass mein MySQL Passwort zu kurz sei. Stimmte nicht, vermutlich kam es mit den Sonderzeichen durcheinander. Habe daher ein neues langes ohne Sonderzeichen vergeben.&lt;br /&gt;
&lt;br /&gt;
==Streamlit==&lt;br /&gt;
see also [https://github.com/entorb/strava-streamlit/blob/main/README.md strava-streamlit readme]&lt;br /&gt;
&lt;br /&gt;
Installation&lt;br /&gt;
 pip3.11 install --user streamlit&lt;br /&gt;
&lt;br /&gt;
Setup dir and config&lt;br /&gt;
 mkdir ~/strava-streamlit&lt;br /&gt;
 cd ~/strava-streamlit&lt;br /&gt;
 mkdir .streamlit&lt;br /&gt;
 vim .streamlit/config.toml&lt;br /&gt;
&lt;br /&gt;
 [browser]&lt;br /&gt;
 gatherUsageStats = false&lt;br /&gt;
 &lt;br /&gt;
 [server]&lt;br /&gt;
 headless = true&lt;br /&gt;
 port = 8501&lt;br /&gt;
 baseUrlPath = &amp;quot;/strava-streamlit&amp;quot;&lt;br /&gt;
&lt;br /&gt;
baseUrlPath is important when not running in web server document root &lt;br /&gt;
&lt;br /&gt;
create minimal Streamlit app&lt;br /&gt;
 vim src/app.py&lt;br /&gt;
&lt;br /&gt;
 import streamlit as st&lt;br /&gt;
 st.title(&amp;quot;Test&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
start it manually as testrun (stop by ctrl+c)&lt;br /&gt;
 streamlit run src/app.py&lt;br /&gt;
&lt;br /&gt;
Add web backend&lt;br /&gt;
 uberspace web backend set /strava-streamlit --http --port 8501&lt;br /&gt;
 maybe add --remove-prefix&lt;br /&gt;
&lt;br /&gt;
Access it via browser&lt;br /&gt;
https://entorb.net/strava-streamlit&lt;br /&gt;
&lt;br /&gt;
Create Service&lt;br /&gt;
 vim ~/etc/services.d/strava-streamlit.ini&lt;br /&gt;
&lt;br /&gt;
 [program:strava-streamlit]&lt;br /&gt;
 directory=%(ENV_HOME)s/strava-streamlit&lt;br /&gt;
 command=python3.11 -O -m streamlit run src/app.py&lt;br /&gt;
 # for env var:&lt;br /&gt;
 environment=MY_VAR=&amp;quot;asdf&amp;quot;&lt;br /&gt;
 loglevel=info&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Start Service&lt;br /&gt;
 supervisorctl reread&lt;br /&gt;
 supervisorctl update&lt;br /&gt;
 supervisorctl status&lt;br /&gt;
 supervisorctl restart strava-streamlit&lt;br /&gt;
&lt;br /&gt;
Check log&lt;br /&gt;
 supervisorctl tail -f strava-streamlit&lt;br /&gt;
 # as this is empty:&lt;br /&gt;
 tail -f ~/logs/supervisord.log&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Uberspace&amp;diff=5403</id>
		<title>Uberspace</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Uberspace&amp;diff=5403"/>
		<updated>2026-04-08T05:44:01Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Streamlit */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Webserver]][[Category:Linux]]&lt;br /&gt;
==Manuals==&lt;br /&gt;
https://manual.uberspace.de and https://lab.uberspace.de&lt;br /&gt;
&lt;br /&gt;
==PHP==&lt;br /&gt;
see https://manual.uberspace.de/lang-php/&lt;br /&gt;
&lt;br /&gt;
to restart&lt;br /&gt;
 uberspace tools restart php&lt;br /&gt;
&lt;br /&gt;
===php-fpm===&lt;br /&gt;
 less /opt/uberspace/etc/$USER/php-fpm.conf&lt;br /&gt;
&lt;br /&gt;
==Python FastAPI==&lt;br /&gt;
see https://lab.uberspace.de/guide_fastapi/&lt;br /&gt;
 &lt;br /&gt;
 # var 1: venv&lt;br /&gt;
 python3.11 -m venv venv&lt;br /&gt;
 source venv/bin/activate&lt;br /&gt;
 pip install fastapi uvicorn&lt;br /&gt;
 pip install fastapi.responses&lt;br /&gt;
 pip install gunicorn uvloop httptools&lt;br /&gt;
 deactivate&lt;br /&gt;
 &lt;br /&gt;
 # var 2: not use venv&lt;br /&gt;
 pip3.11 install --user fastapi uvicorn fastapi.responses gunicorn uvloop httptools&lt;br /&gt;
 &lt;br /&gt;
 # config backend&lt;br /&gt;
 uberspace web backend set /strava-be --http --port 9001&lt;br /&gt;
 # --remove-prefix&lt;br /&gt;
 uberspace web backend list&lt;br /&gt;
 &lt;br /&gt;
 supervisorctl restart fastapi&lt;br /&gt;
 &lt;br /&gt;
~/fastapi/conf.py&lt;br /&gt;
 #!/usr/bin/env python3.10&lt;br /&gt;
 &lt;br /&gt;
 # apply changes via&lt;br /&gt;
 # supervisorctl restart fastapi&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 &lt;br /&gt;
 app_path = os.environ[&amp;quot;HOME&amp;quot;] + &amp;quot;/fastapi&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # Gunicorn configuration&lt;br /&gt;
 wsgi_app = &amp;quot;main:api&amp;quot;&lt;br /&gt;
 bind = &amp;quot;:9001&amp;quot;&lt;br /&gt;
 chdir = app_path&lt;br /&gt;
 workers = 1&lt;br /&gt;
 worker_class = &amp;quot;uvicorn.workers.UvicornWorker&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # errorlog = app_path + &amp;quot;/errors.log&amp;quot;&lt;br /&gt;
 # accesslog = app_path + &amp;quot;/access.log&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
~/fastapi/main.py&lt;br /&gt;
 #!/usr/bin/env python3.10&lt;br /&gt;
 &lt;br /&gt;
 # apply changes via&lt;br /&gt;
 # supervisorctl restart fastapi&lt;br /&gt;
 &lt;br /&gt;
 from fastapi import FastAPI&lt;br /&gt;
 from fastapi.responses import JSONResponse&lt;br /&gt;
 from pydantic import BaseModel&lt;br /&gt;
 &lt;br /&gt;
 import subprocess&lt;br /&gt;
  &lt;br /&gt;
 class StravaSession(BaseModel):&lt;br /&gt;
     sessionId: str&lt;br /&gt;
 &lt;br /&gt;
 api = FastAPI()&lt;br /&gt;
  &lt;br /&gt;
 @api.post(&amp;quot;/strava-be/activityStats2/&amp;quot;)&lt;br /&gt;
 async def activityStats2(session: StravaSession):&lt;br /&gt;
     # return {session}&lt;br /&gt;
     response = {}&lt;br /&gt;
     # response = {&amp;quot;session&amp;quot;: session.sessionId}&lt;br /&gt;
     process = subprocess.run(&lt;br /&gt;
         [&lt;br /&gt;
             &amp;quot;python3.10&amp;quot;,&lt;br /&gt;
             &amp;quot;/var/www/virtual/entorb/html/strava/activityStats2.py&amp;quot;,&lt;br /&gt;
             session.sessionId,&lt;br /&gt;
         ],&lt;br /&gt;
         capture_output=True,&lt;br /&gt;
     )&lt;br /&gt;
 &lt;br /&gt;
     if process.returncode == 0:&lt;br /&gt;
         response[&amp;quot;status&amp;quot;] = &amp;quot;ok&amp;quot;&lt;br /&gt;
         response_code = 200&lt;br /&gt;
         # response[&amp;quot;process_stdout&amp;quot;] = process.stdout.decode()&lt;br /&gt;
     else:&lt;br /&gt;
         response[&amp;quot;status&amp;quot;] = &amp;quot;error&amp;quot;&lt;br /&gt;
         response_code = 400&lt;br /&gt;
         # response[&amp;quot;process_stdout&amp;quot;] = process.stdout.decode()&lt;br /&gt;
         response[&amp;quot;error_message&amp;quot;] = process.stderr.decode()&lt;br /&gt;
     return JSONResponse(content=response, status_code=response_code)&lt;br /&gt;
 &lt;br /&gt;
 # To test this app locally, uncomment:&lt;br /&gt;
 # import uvicorn&lt;br /&gt;
 # uvicorn.run(api, host=&amp;quot;localhost&amp;quot;, port=8001)&lt;br /&gt;
 &lt;br /&gt;
 # curl -i -X POST &amp;quot;https://entorb.net/strava-be/activityStats2/&amp;quot; -H &amp;quot;Content-Type: application/json&amp;quot; -d &#039;{&amp;quot;sessionId&amp;quot;: &amp;quot;1234&amp;quot;}&#039;&lt;br /&gt;
&lt;br /&gt;
==Checkliste neuer U7 Account==&lt;br /&gt;
 ~/.bashrc und ~/.bash_profile von altem angeglichen&lt;br /&gt;
Logs aktiviert&lt;br /&gt;
 uberspace web log access enable&lt;br /&gt;
 uberspace web log apache_error enable&lt;br /&gt;
 uberspace web log php_error enable&lt;br /&gt;
 uberspace web errorpage 500 disable&lt;br /&gt;
&lt;br /&gt;
==Probleme bei der Migration Uberspace U6 zu U7==&lt;br /&gt;
===0. E-Mails===&lt;br /&gt;
Es scheinen keine E-Mails mehr rein und raus zu gehen. (Account xxx@entorb.net). &lt;br /&gt;
&lt;br /&gt;
Neuer Webmailer ist https://webmail.uberspace.de , dieser zeigt aber erst nach Abschluss der Migration auf das neue Postfach. Solange die Migration nicht abgeschlossen ist, muss im Mail Client (wie Thunderbird) dieser Login für den U7 Mail Account verwendet werden:&lt;br /&gt;
statt xxx@entorb.net -&amp;gt; xxx@entorb.uber.space&lt;br /&gt;
&lt;br /&gt;
===1. HTTPS Zertifikat===&lt;br /&gt;
HTTPS Zertifikat war auf einmal nicht mehr gültig für https://www.entorb.net . Das hatte erst auch auf dem U7 funktioniert, nun plötzlich nicht mehr. UPDATE: Problem verschwand von alleine, hing vielleicht mit dem DNS Umzug zusammen.&lt;br /&gt;
&lt;br /&gt;
===2. bestehende ezmlm Mailinglisten===&lt;br /&gt;
ezmlm ist gemäß [https://lab.uberspace.de/guide_ezmlm.html Anleitung] installiert, allerdings sagt mir&lt;br /&gt;
 ezmlm-make -+ ~/ezmlm/oldlist ~/.qmail-oldlist oldlist entorb.uber.space&lt;br /&gt;
 &amp;gt; ezmlm-make: fatal: unable to stat /etc/ezmlm/de: file does not exist&lt;br /&gt;
&lt;br /&gt;
Wenn ich hingegen testweise einen neuen Verteiler anlege tut es:&lt;br /&gt;
 ezmlm-make -A -u -m -5 xxx@entorb.net ~/ezmlm/mylist ~/.qmail-mylist&lt;br /&gt;
mylist entorb.uber.space&lt;br /&gt;
und diesen kann ich dann auch bearbeiten via&lt;br /&gt;
 ezmlm-make -+ ~/ezmlm/mylist ~/.qmail-mylist mylist entorb.uber.space&lt;br /&gt;
&lt;br /&gt;
Lösung:&lt;br /&gt;
 in ezmlm/oldlist/ezmlmrc die entsprechende Zeile löschen&lt;br /&gt;
&lt;br /&gt;
===3. Zugriff auf Dateien außerhab von ~/html===&lt;br /&gt;
Ich hatte einige Dateien (wie zB eine sqlite DB) in meinem Home (~/) liegen. Auf diese sollen Skripte die sich unter&lt;br /&gt;
 ~/html-&amp;gt;/var/www/virtual/entorb/html &lt;br /&gt;
befinden zugreifen können. Das funktionierte nicht mehr, da SELinux hier rein grätscht &lt;br /&gt;
&lt;br /&gt;
Beste Lösung: Die Dateien auf die der httpd zugreifen soll nach&lt;br /&gt;
 /var/www/virtual/entorb/ &lt;br /&gt;
verschieben und im Home Symlink hinterlassen.&lt;br /&gt;
&lt;br /&gt;
Erster Ansatz: der SELinux Kontexte lässt sich so setzen (Siehe [https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/selinux_users_and_administrators_guide/sect-security-enhanced_linux-working_with_selinux-selinux_contexts_labeling_files]&lt;br /&gt;
).&lt;br /&gt;
 chcon -t httpd_sys_content_t file-name&lt;br /&gt;
half hier aber nicht:&lt;br /&gt;
 ls -laZ /home/ |grep ento&lt;br /&gt;
 drwx------. entorb      entorb      unconfined_u:object_r:user_home_dir_t:s0 entorb&lt;br /&gt;
&lt;br /&gt;
===4. Perl Module, wie zB Excel::Writer::XLSX===&lt;br /&gt;
Installiert via&lt;br /&gt;
 cpanm Excel::Writer::XLSX&lt;br /&gt;
&lt;br /&gt;
Führe ich das Skript ~/html/test.pl in der Shell aus, wird das Modul&lt;br /&gt;
gefunden und geladen. Im Browser via https://entorb.net/test.pl wird&lt;br /&gt;
das Modul nicht gefunden.&lt;br /&gt;
&lt;br /&gt;
Ursache 4.1 war dass SELinux den Zugriff des httpd auf /home/entorb/perl5 blockierte&lt;br /&gt;
&lt;br /&gt;
Lösung:&lt;br /&gt;
~/perl5 Verzeichnis nach &lt;br /&gt;
 /var/www/virtual/entorb/perl5&lt;br /&gt;
verschoben und im Home einen Symlink hinterlassen. &lt;br /&gt;
&lt;br /&gt;
Dann zuerst lib::local installiert, dann andere Module.&lt;br /&gt;
&lt;br /&gt;
Ursache 4.2 war dass ich in der .bashrc dies gesetzt habe:&lt;br /&gt;
 eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)&lt;br /&gt;
das musste ich im Perl Skript ebenfalls hinterlegen:&lt;br /&gt;
 use lib (&#039;/var/www/virtual/entorb/perl5/lib/perl5&#039;);&lt;br /&gt;
 # aber !kein! use local::lib; !!!&lt;br /&gt;
&lt;br /&gt;
===5. sendmail===&lt;br /&gt;
ich stellte fest, dass ich im U7 aus Python oder Perl CGI Skripten nicht mehr auf sendmail zugreifen kann. Über die Shell kommt die Mail sofort an. Beispiel:&lt;br /&gt;
8&amp;lt;---&lt;br /&gt;
 import os&lt;br /&gt;
 SENDMAIL = &amp;quot;/usr/sbin/sendmail&amp;quot;&lt;br /&gt;
 to = &amp;quot;xxx@entorb.net&amp;quot;&lt;br /&gt;
 subject   = &amp;quot;testmail&amp;quot;&lt;br /&gt;
 sender = &amp;quot;U7 &amp;lt;no-reply@entorb.net&amp;gt;&amp;quot;&lt;br /&gt;
 body = &amp;quot;leer&amp;quot;&lt;br /&gt;
 mail = f&amp;quot;To: {to}\nSubject: {subject}\nFrom: {sender}\nContent-Type:&lt;br /&gt;
 text/plain; charset=\&amp;quot;utf-8\&amp;quot;\n\n{body}&amp;quot;&lt;br /&gt;
 p = os.popen(f&amp;quot;{SENDMAIL} -t -i&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
 p.write(mail)&lt;br /&gt;
 p.close()&lt;br /&gt;
 8&amp;lt;---&lt;br /&gt;
Lösung: statt Versand aus Web-Skripten/Seiten via sendmail, besser aus den Skripten ein Insert in eine SQLite DB machen und diese via cronjob und php mail() Funktion periodisch abarbeiten.&lt;br /&gt;
&lt;br /&gt;
===6. Wiederholung der Migration===&lt;br /&gt;
Entweder einfach Migration auf dem U6 nochmal starten via&lt;br /&gt;
 uberspace-move-account -u entorb&lt;br /&gt;
oder in Dashboard den Umzug abbrechen und neu starten -&amp;gt; anderer Server.&lt;br /&gt;
&lt;br /&gt;
===7. MySQL DB Passwort angeblich zu kurz===&lt;br /&gt;
Das Migrationsskript hatte behauptet, dass mein MySQL Passwort zu kurz sei. Stimmte nicht, vermutlich kam es mit den Sonderzeichen durcheinander. Habe daher ein neues langes ohne Sonderzeichen vergeben.&lt;br /&gt;
&lt;br /&gt;
==Streamlit==&lt;br /&gt;
see also [https://github.com/entorb/strava-streamlit/blob/main/README.md strava-streamlit readme]&lt;br /&gt;
&lt;br /&gt;
Installation&lt;br /&gt;
 pip3.11 install --user streamlit&lt;br /&gt;
&lt;br /&gt;
Setup dir and config&lt;br /&gt;
 mkdir ~/strava-streamlit&lt;br /&gt;
 cd ~/strava-streamlit&lt;br /&gt;
 mkdir .streamlit&lt;br /&gt;
 vim .streamlit/config.toml&lt;br /&gt;
&lt;br /&gt;
 [browser]&lt;br /&gt;
 gatherUsageStats = false&lt;br /&gt;
 &lt;br /&gt;
 [server]&lt;br /&gt;
 headless = true&lt;br /&gt;
 port = 8501&lt;br /&gt;
 baseUrlPath = &amp;quot;/strava-streamlit&amp;quot;&lt;br /&gt;
&lt;br /&gt;
baseUrlPath is important when not running in web server document root &lt;br /&gt;
&lt;br /&gt;
create minimal Streamlit app&lt;br /&gt;
 vim src/app.py&lt;br /&gt;
&lt;br /&gt;
 import streamlit as st&lt;br /&gt;
 st.title(&amp;quot;Test&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
start it manually as testrun (stop by ctrl+c)&lt;br /&gt;
 streamlit run src/app.py&lt;br /&gt;
&lt;br /&gt;
Add web backend&lt;br /&gt;
 uberspace web backend set /strava-streamlit --http --port 8501&lt;br /&gt;
 maybe add --remove-prefix&lt;br /&gt;
&lt;br /&gt;
Access it via browser&lt;br /&gt;
https://entorb.net/strava-streamlit&lt;br /&gt;
&lt;br /&gt;
Create Service&lt;br /&gt;
 vim ~/etc/services.d/strava-streamlit.ini&lt;br /&gt;
&lt;br /&gt;
 [program:strava-streamlit]&lt;br /&gt;
 directory=%(ENV_HOME)s/strava-streamlit&lt;br /&gt;
 command=streamlit run src/app.py&lt;br /&gt;
 # or &lt;br /&gt;
 # command=python3.11 -O -m streamlit run src/app.py&lt;br /&gt;
 # for env var:&lt;br /&gt;
 environment=MY_VAR=&amp;quot;asdf&amp;quot;&lt;br /&gt;
 loglevel=info&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Start Service&lt;br /&gt;
 supervisorctl reread&lt;br /&gt;
 supervisorctl update&lt;br /&gt;
 supervisorctl status&lt;br /&gt;
 supervisorctl restart strava-streamlit&lt;br /&gt;
&lt;br /&gt;
Check log&lt;br /&gt;
 supervisorctl tail -f strava-streamlit&lt;br /&gt;
 # as this is empty:&lt;br /&gt;
 tail -f ~/logs/supervisord.log&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Apache&amp;diff=5402</id>
		<title>Apache</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Apache&amp;diff=5402"/>
		<updated>2026-03-17T05:31:30Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Token Query Parameter protection */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Webserver]]&lt;br /&gt;
==.htaccess==&lt;br /&gt;
===basics===&lt;br /&gt;
 # a different .htaccess file can be stored in each subfolder &lt;br /&gt;
 &lt;br /&gt;
 # set the timezone&lt;br /&gt;
 SetEnv TZ Europe/Berlin&lt;br /&gt;
 &lt;br /&gt;
 # do not generate a index.htm listing of the files&lt;br /&gt;
 Options -Indexes&lt;br /&gt;
 # can be turned on for a specifiv subfolder by using another .htaccess &lt;br /&gt;
 # in that folder containing &amp;quot;Options Indexes&amp;quot; without the &amp;quot;-&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # disable the display on index-pages for files matching * = all&lt;br /&gt;
 IndexIgnore *.jpg&lt;br /&gt;
 &lt;br /&gt;
 # ensure that http://entorb.net --&amp;gt; http://www.entorb.net&lt;br /&gt;
 RewriteCond %{HTTP_HOST} !^www\.entorb\.net$&lt;br /&gt;
 RewriteRule ^(.*)$ http://www.entorb.net/$1 [L,R=301]&lt;br /&gt;
&lt;br /&gt;
===Password===&lt;br /&gt;
Ask for a password when accessing a folder&lt;br /&gt;
 &lt;br /&gt;
[http://httpd.apache.org/docs/1.3/howto/htaccess.html]&lt;br /&gt;
Create a new passwd file&lt;br /&gt;
 htpasswd -c -m /some/path/passwd.file myUser&lt;br /&gt;
Add another user&lt;br /&gt;
 htpasswd -m /some/path/passwd.file myUser2&lt;br /&gt;
&lt;br /&gt;
( at Uberspace the &amp;lt;passwd.file&amp;gt; has to be located below /var/www/virtual/&amp;lt;USER&amp;gt;/ )&lt;br /&gt;
&lt;br /&gt;
.htaccess:&lt;br /&gt;
 AuthType Basic&lt;br /&gt;
 AuthName &amp;quot;some Text&amp;quot;&lt;br /&gt;
 AuthUserFile /some/path/passwd.file&lt;br /&gt;
 AuthBasicProvider file&lt;br /&gt;
 Require valid-user&lt;br /&gt;
&lt;br /&gt;
===Token Query Parameter protection===&lt;br /&gt;
 # require a token query parameter to be set&lt;br /&gt;
 RewriteEngine On&lt;br /&gt;
 RewriteCond %{QUERY_STRING} !token=asdf&lt;br /&gt;
 RewriteRule ^ - [F]&lt;br /&gt;
&lt;br /&gt;
===URL Rewrite for wiki===&lt;br /&gt;
see&lt;br /&gt;
[http://www.stephan-hertz.de/blog/modrewrite-und-suchmaschinen-optimierung-fur-ein-wiki-via-htaccess/]&lt;br /&gt;
, [http://www.mediawiki.org/wiki/Manual:Short_URL/wiki/Page_title_--_DreamHost_Shared_Hosting]&lt;br /&gt;
or [http://www.mediawiki.org/wiki/Manual:Short_URL/wiki/Page_title_--_no_root_access]&lt;br /&gt;
&lt;br /&gt;
my case: wiki stored unter folder wiki, using virtual folder wickie&lt;br /&gt;
 # Wiki: convert request using /index.php?title=XYZ -&amp;gt;/XYZ&lt;br /&gt;
 # only apply the following to wiki urls&lt;br /&gt;
 RewriteCond %{REQUEST_URI} .*(wickie|wiki).* [nocase]&lt;br /&gt;
 RewriteCond %{THE_REQUEST} \?title=(.+)\ HTTP&lt;br /&gt;
 RewriteRule ^.*$ http://www\.entorb\.net/wickie/%1? [R=301,L]&lt;br /&gt;
 &lt;br /&gt;
 # Short URLs for Wiki&lt;br /&gt;
 #http://www.mediawiki.org/wiki/Manual:Short_URL/wiki/Page_title_--_DreamHost_Shared_Hosting&lt;br /&gt;
 RewriteEngine On&lt;br /&gt;
 # only apply the following to wiki urls&lt;br /&gt;
 RewriteCond %{REQUEST_URI} .*(wickie|wiki).* [nocase]&lt;br /&gt;
 RewriteCond %{REQUEST_FILENAME} !-f&lt;br /&gt;
 RewriteCond %{REQUEST_FILENAME} !-d&lt;br /&gt;
 RewriteRule ^(.+)$ /wiki/index.php?title=$1 [L,QSA]&lt;br /&gt;
 # in LocalSettings.php&lt;br /&gt;
 $wgUsePathInfo = true;&lt;br /&gt;
 $wgScriptPath       = &amp;quot;/wiki&amp;quot;; # real path&lt;br /&gt;
 $wgScriptExtension  = &amp;quot;.php&amp;quot;;&lt;br /&gt;
 $wgScript           = &amp;quot;$wgScriptPath/index$wgScriptExtension&amp;quot;;&lt;br /&gt;
 $wgArticlePath = &amp;quot;/wickie/$1&amp;quot;; # virtual path&lt;br /&gt;
&lt;br /&gt;
==robots.txt==&lt;br /&gt;
Place in webservers-root-dir. To keep search robots out of some of your folders. Attention: this file my be a hint for intruders...&lt;br /&gt;
 User-agent: *&lt;br /&gt;
 Disallow: /privateStuff/&lt;br /&gt;
 Disallow: /Gallery/&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Pandas&amp;diff=5400</id>
		<title>Pandas</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Pandas&amp;diff=5400"/>
		<updated>2026-03-08T10:30:38Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Basics */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Coding]][[Category:Python]]&lt;br /&gt;
==Getting started==&lt;br /&gt;
 import pandas as pd&lt;br /&gt;
&lt;br /&gt;
===Docu===&lt;br /&gt;
nice cheat sheets can be found here&lt;br /&gt;
* https://www.datacamp.com/cheat-sheet/pandas-cheat-sheet-for-data-science-in-python&lt;br /&gt;
* https://www.datacamp.com/cheat-sheet/pandas-cheat-sheet-data-wrangling-in-python&lt;br /&gt;
&lt;br /&gt;
==Create DF==&lt;br /&gt;
 import pandas as pd&lt;br /&gt;
 # empty&lt;br /&gt;
 df = pd.DataFrame()&lt;br /&gt;
===random data===&lt;br /&gt;
 df = pd.DataFrame({&amp;quot;x&amp;quot;: range(100)})&lt;br /&gt;
 df[&amp;quot;y&amp;quot;] = np.random.rand(len(df)) * 10&lt;br /&gt;
 &lt;br /&gt;
 # y = sin(x) + noise&lt;br /&gt;
 noise = np.random.rand(len(df)) * 0.1&lt;br /&gt;
 noise = np.random.default_rng().random(10) # newer syntax&lt;br /&gt;
 df[&amp;quot;y&amp;quot;] = np.sin(df[&amp;quot;x&amp;quot;] / 25 * np.pi) + noise&lt;br /&gt;
  &lt;br /&gt;
 # 20 rows and 3 columns of random values&lt;br /&gt;
 df = pd.DataFrame(np.random.rand(20, 3), columns=[&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===from Lists===&lt;br /&gt;
 &lt;br /&gt;
 # from 1 dim list&lt;br /&gt;
 df = pd.DataFrame(data={&amp;quot;Column Name&amp;quot;: lst})&lt;br /&gt;
 # per column&lt;br /&gt;
 df = pd.DataFrame(&lt;br /&gt;
     data={&amp;quot;col1&amp;quot;:lst1, &amp;quot;col2&amp;quot;:lst2, &amp;quot;col3&amp;quot;:lst3})&lt;br /&gt;
 )&lt;br /&gt;
 # from N dim list, and set name of columns&lt;br /&gt;
 df = pd.DataFrame(&lt;br /&gt;
     data=lst, columns=(&amp;quot;col1&amp;quot;, &amp;quot;col2&amp;quot;, &amp;quot;col3)&lt;br /&gt;
 )&lt;br /&gt;
 # from multiple lists&lt;br /&gt;
 data = zip(&lt;br /&gt;
     l_days,&lt;br /&gt;
     l_2016,&lt;br /&gt;
     l_2017,&lt;br /&gt;
     l_2018,&lt;br /&gt;
     l_2019,&lt;br /&gt;
     l_2020,&lt;br /&gt;
     strict=True,&lt;br /&gt;
 )&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
===from csv===&lt;br /&gt;
see [https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html]&lt;br /&gt;
 df = pd.read_csv(&amp;quot;data.tsv&amp;quot;, sep=&amp;quot;\t&amp;quot;)&lt;br /&gt;
 # only selected columns and parse date&lt;br /&gt;
 df = pd.read_csv(&lt;br /&gt;
     &amp;quot;data.tsv&amp;quot;,&lt;br /&gt;
     sep=&amp;quot;\t&amp;quot;,&lt;br /&gt;
     decimal=&amp;quot;,&amp;quot;,&lt;br /&gt;
     usecols=[&lt;br /&gt;
         &amp;quot;Date&amp;quot;,&lt;br /&gt;
         &amp;quot;Deaths_New&amp;quot;,&lt;br /&gt;
     ],  # only load these columns&lt;br /&gt;
     parse_dates=[&lt;br /&gt;
         &amp;quot;Date&amp;quot;,&lt;br /&gt;
     ],  # convert to date object if format is yyyy-mm-dd pr dd.mm.yyyy&lt;br /&gt;
     index_col=&amp;quot;Date&amp;quot;,  # choose this column as index&lt;br /&gt;
     na_values=[&amp;quot;&amp;lt;4&amp;quot;],  # values to treat as NA&lt;br /&gt;
 )&lt;br /&gt;
 df = df.rename(columns={&amp;quot;Deaths_New&amp;quot;: &amp;quot;Deaths_Covid&amp;quot;})&lt;br /&gt;
 &lt;br /&gt;
 # convert data upon loading&lt;br /&gt;
 # read only first 10 chars from 2021-04-29T12:15:00+02:00 -&amp;gt; 2021-04-29&lt;br /&gt;
 pd_date_converter = lambda x: (x[0:10])  # noqa: E731&lt;br /&gt;
 df = pd.read_csv(&lt;br /&gt;
     &amp;quot;data.tsv&amp;quot;,&lt;br /&gt;
     sep=&amp;quot;,&amp;quot;,&lt;br /&gt;
     converters={&amp;quot;Datum&amp;quot;: pd_date_converter},&lt;br /&gt;
     parse_dates=[&lt;br /&gt;
         &amp;quot;Datum&amp;quot;,  # # convert to date object if format is yyyy-mm-dd&lt;br /&gt;
     ],&lt;br /&gt;
 )&lt;br /&gt;
 # convert datetime to date after loading&lt;br /&gt;
 for c in (&amp;quot;Buchungstag&amp;quot;, &amp;quot;Valutadatum&amp;quot;):&lt;br /&gt;
     df2[c] = pd.to_datetime(df2[c], format=&amp;quot;%d.%m.%Y&amp;quot;).dt.date&lt;br /&gt;
&lt;br /&gt;
===from list[str] via read_csv===&lt;br /&gt;
 from io import StringIO&lt;br /&gt;
 &lt;br /&gt;
 csv_string_io = StringIO(&amp;quot;\n&amp;quot;.join(lines))&lt;br /&gt;
 df = pd.read_csv(&lt;br /&gt;
     csv_string_io,&lt;br /&gt;
     sep=&amp;quot;;&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 data_str = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 datetime_de	value&lt;br /&gt;
 30.09.2022 10:00	214516.10&lt;br /&gt;
 13.10.2022 00:20	214563.85&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 df = pd.read_csv(StringIO(data_str), sep=&amp;quot;\t&amp;quot;, lineterminator=&amp;quot;\n&amp;quot;)&lt;br /&gt;
 df[&amp;quot;value&amp;quot;] = df[&amp;quot;value&amp;quot;].astype(float)&lt;br /&gt;
&lt;br /&gt;
===from Excel===&lt;br /&gt;
There are 2 libs: openpyxl (for read and write) and XlsxWriter (write)&lt;br /&gt;
see [https://pandas.pydata.org/docs/reference/api/pandas.read_excel.html]&lt;br /&gt;
 import openpyxl  # pip install openpyxl&lt;br /&gt;
 &lt;br /&gt;
 df = pd.read_excel(&amp;quot;file.xlsx&amp;quot;, sheet_name=&amp;quot;Sheet1&amp;quot;, engine=&amp;quot;openpyxl&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 df = pd.read_excel(&lt;br /&gt;
     &amp;quot;file.xlsx&amp;quot;,&lt;br /&gt;
     skiprows=4,&lt;br /&gt;
     usecols=[&lt;br /&gt;
         &amp;quot;ArbPlatz&amp;quot;,&lt;br /&gt;
         &amp;quot;BezArt&amp;quot;,&lt;br /&gt;
     ],&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
===from dict===&lt;br /&gt;
see [https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.from_dict.html]&lt;br /&gt;
 df = pd.DataFrame.from_dict(d, orient=&amp;quot;index&amp;quot;, columns=[&amp;quot;Col1&amp;quot;, Col2])&lt;br /&gt;
&lt;br /&gt;
flatten dict in list of dict&lt;br /&gt;
 # l1 is list of dict, each dict has a sub-dict called metadata&lt;br /&gt;
 l2 = []&lt;br /&gt;
 for item in l1:&lt;br /&gt;
     flat = item.copy()&lt;br /&gt;
     meta = item.pop(&amp;quot;metadata&amp;quot;)&lt;br /&gt;
     flat.update(meta)&lt;br /&gt;
     l2.append(flat)&lt;br /&gt;
 del l1&lt;br /&gt;
 &lt;br /&gt;
 df = pd.DataFrame.from_dict(l2)&lt;br /&gt;
&lt;br /&gt;
===from array/record/list===&lt;br /&gt;
see [https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.from_records.html]&lt;br /&gt;
 data = [ {&amp;quot;id&amp;quot;:1, &amp;quot;name&amp;quot;:&amp;quot;asdf&amp;quot;}, {&amp;quot;id&amp;quot;:2, &amp;quot;name&amp;quot;:&amp;quot;qwertz&amp;quot;}, ]&lt;br /&gt;
 df = pd.DataFrame.from_records(data)&lt;br /&gt;
&lt;br /&gt;
===from JSON===&lt;br /&gt;
see [https://pandas.pydata.org/docs/reference/api/pandas.read_json.html]&lt;br /&gt;
 df = pd.read_json(&amp;quot;file.json&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===from SQLite===&lt;br /&gt;
 import sqlite3&lt;br /&gt;
 sql = &amp;quot;SELECT * FROM tab WHERE col2 = ? ORDER BY id&amp;quot;;&lt;br /&gt;
 df = pd.read_sql_query(sql, con, params=(MY_FILTER_VALUE,))&lt;br /&gt;
&lt;br /&gt;
==Export Data==&lt;br /&gt;
===to csv===&lt;br /&gt;
 df.to_csv(&lt;br /&gt;
     &amp;quot;data.tsv&amp;quot;,&lt;br /&gt;
     sep=&amp;quot;\t&amp;quot;,&lt;br /&gt;
     lineterminator=&amp;quot;\n&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
 # selected columns only&lt;br /&gt;
 df[ [&amp;quot;Col1&amp;quot;, &amp;quot;Col2&amp;quot;] ].to_csv(&lt;br /&gt;
     &amp;quot;data.tsv&amp;quot;,&lt;br /&gt;
     sep=&amp;quot;\t&amp;quot;,&lt;br /&gt;
     lineterminator=&amp;quot;\n&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
to string in CSV format&lt;br /&gt;
 l_csv = (&lt;br /&gt;
     df.drop(columns=[&amp;quot;hour&amp;quot;]).to_csv(&lt;br /&gt;
         index=False, sep=&amp;quot;\t&amp;quot;, lineterminator=&amp;quot;\n&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;&lt;br /&gt;
     ),&lt;br /&gt;
 )&lt;br /&gt;
 s_csv = &amp;quot;&amp;quot;.join(l_csv)&lt;br /&gt;
&lt;br /&gt;
===to Excel===&lt;br /&gt;
see [https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_excel.html]&lt;br /&gt;
 df.to_excel(&amp;quot;data.xlsx&amp;quot;, index=False)&lt;br /&gt;
&lt;br /&gt;
Export 2 df as sheets in 1 file&lt;br /&gt;
 with pd.ExcelWriter(path=file_in.with_suffix(&amp;quot;.xlsx&amp;quot;)) as writer:  &lt;br /&gt;
     df1.to_excel(writer, sheet_name=&#039;Sheet_name_1&#039;)&lt;br /&gt;
     df2.to_excel(writer, sheet_name=&#039;Sheet_name_2&#039;)&lt;br /&gt;
&lt;br /&gt;
===to HTML===&lt;br /&gt;
see [https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_html.html]&lt;br /&gt;
 df.to_html(&lt;br /&gt;
     &amp;quot;out.html&amp;quot;, index=False, render_links=False, escape=False, justify=&amp;quot;center&amp;quot;&lt;br /&gt;
     )&lt;br /&gt;
&lt;br /&gt;
 # html encoding of column name only&lt;br /&gt;
 df[&amp;quot;name&amp;quot;] = df[&amp;quot;name&amp;quot;].str.encode(&amp;quot;ascii&amp;quot;, &amp;quot;xmlcharrefreplace&amp;quot;).str.decode(&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 # add link to name&lt;br /&gt;
 df[&amp;quot;name&amp;quot;] = &amp;quot;&amp;lt;a href=&#039;&amp;quot; + df[&amp;quot;url&amp;quot;] + &amp;quot;&#039; target=&#039;_blank&#039; &amp;gt;&amp;quot; + df[&amp;quot;name&amp;quot;] + &amp;quot;&amp;lt;/a&amp;gt;&amp;quot;&lt;br /&gt;
 # export to html&lt;br /&gt;
 df[ [&amp;quot;name&amp;quot;, &amp;quot;due&amp;quot;, &amp;quot;overdue&amp;quot;, &amp;quot;priority&amp;quot;, &amp;quot;overdue priority&amp;quot;] ].to_html(&lt;br /&gt;
     &amp;quot;out.html&amp;quot;, index=False, render_links=False, escape=False, justify=&amp;quot;center&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
===to JSON===&lt;br /&gt;
 json_data = df.values.tolist()&lt;br /&gt;
 &lt;br /&gt;
 with Path(pathStatsExport / &amp;quot;ts_types_month.json&amp;quot;).open(&lt;br /&gt;
     &amp;quot;w&amp;quot;, encoding=&amp;quot;UTF-8&amp;quot;&lt;br /&gt;
 ) as fh:&lt;br /&gt;
     json.dump(&lt;br /&gt;
         json_data,&lt;br /&gt;
         fp=fh,&lt;br /&gt;
         ensure_ascii=False,&lt;br /&gt;
         sort_keys=False,&lt;br /&gt;
         indent=2,&lt;br /&gt;
     )&lt;br /&gt;
&lt;br /&gt;
==Modification==&lt;br /&gt;
===Add Row===&lt;br /&gt;
add row from list to end&lt;br /&gt;
 idx = df.index[-1] + 1&lt;br /&gt;
 list_of_values = (...)&lt;br /&gt;
 df.loc[idx] = list_of_values&lt;br /&gt;
&lt;br /&gt;
add dummy row for missing 1.1.2020 and reindex&lt;br /&gt;
 df.loc[-1] = &amp;quot;2020-01-01&amp;quot;, 0&lt;br /&gt;
 df.index = df.index + 1  # shifting index&lt;br /&gt;
 df = df.sort_index()  # sorting by index&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Add column based on other columns===&lt;br /&gt;
 df[&amp;quot;URL&amp;quot;] = df.apply(&lt;br /&gt;
     lambda row: f&amp;quot;https://www.openstreetmap.org/?mlat={row[&#039;Lat&#039;]}&amp;amp;mlon={row[&#039;Lng&#039;]}#map={zoom}/{row[&#039;Lat&#039;]}/{row[&#039;Lng&#039;]}&amp;quot;,&lt;br /&gt;
     axis=1,&lt;br /&gt;
&lt;br /&gt;
===Apply map/dict id-&amp;gt;name===&lt;br /&gt;
 df[&amp;quot;x_gear_name&amp;quot;] = df[&amp;quot;gear_id&amp;quot;].map(d_id_name)&lt;br /&gt;
&lt;br /&gt;
===Replace na Values===&lt;br /&gt;
 df = df.dropna() # removes missing values&lt;br /&gt;
 # only in certain columns&lt;br /&gt;
 df = df.dropna(subset=[&amp;quot;power&amp;quot;, &amp;quot;heart_rate&amp;quot;, &amp;quot;cadence&amp;quot;])&lt;br /&gt;
 df = df[df[&amp;quot;value&amp;quot;].notna()]&lt;br /&gt;
&lt;br /&gt;
===Interpolate missing values===&lt;br /&gt;
 # interpolate missing data, e.g. after df.groupby([pd.Grouper(key=&amp;quot;time&amp;quot;, freq=&amp;quot;1min&amp;quot;)])&lt;br /&gt;
 df[&amp;quot;kWh_total_in&amp;quot;] = df[&amp;quot;kWh_total_in&amp;quot;].interpolate(method=&amp;quot;linear&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Calc Diff / Delta of rows===&lt;br /&gt;
 df[&amp;quot;delta_prev&amp;quot;] = df[&amp;quot;kWh&amp;quot;].diff()&lt;br /&gt;
 df[&amp;quot;delta_next&amp;quot;] = df[&amp;quot;kWh&amp;quot;].shift(-1) - df[&amp;quot;kWh&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
===Overwrite Data===&lt;br /&gt;
 # overwrite values &amp;gt; 123 by 123 etc.&lt;br /&gt;
 df[&amp;quot;Lat&amp;quot;] = df[&amp;quot;Lat&amp;quot;].clip(lower=-180, upper=180)&lt;br /&gt;
 df[&amp;quot;Lng&amp;quot;] = df[&amp;quot;Lng&amp;quot;].clip(lower=-90,  upper=90 )&lt;br /&gt;
 &lt;br /&gt;
 # set value of columns based on condition&lt;br /&gt;
 # set &amp;quot;type&amp;quot; = &amp;quot;Ride&amp;quot; where &amp;quot;type&amp;quot; == &amp;quot;VirtualRide&amp;quot;&lt;br /&gt;
 df.loc[df[&amp;quot;type&amp;quot;] == &amp;quot;VirtualRide&amp;quot;, &amp;quot;type&amp;quot;] = &amp;quot;Ride&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # negative -&amp;gt; 0&lt;br /&gt;
 df[df &amp;lt; 0] = 0&lt;br /&gt;
 &lt;br /&gt;
 # overwrite column data of last 3 weeks by None&lt;br /&gt;
 df[&amp;quot;DateAsDate&amp;quot;] = pd.to_datetime(df[&amp;quot;Date&amp;quot;], format=&amp;quot;%Y-%m-%d&amp;quot;)&lt;br /&gt;
 date_3w = dt.date.today() - dt.timedelta(weeks=3)&lt;br /&gt;
 df.loc[df[&amp;quot;DateAsDate&amp;quot;].dt.date &amp;gt;= date_3w, &amp;quot;MyColumn&amp;quot;] = None&lt;br /&gt;
  &lt;br /&gt;
  # rolling takes NAN values into account, so I need to overwrite them as well&lt;br /&gt;
 df3[&amp;quot;Deaths_Covid_roll&amp;quot;] = np.where(&lt;br /&gt;
     df3[&amp;quot;Deaths_Covid&amp;quot;].isnull(), np.nan, df3[&amp;quot;Deaths_Covid_roll&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
 df.loc[(df[&amp;quot;Kat2&amp;quot;] == &amp;quot;&amp;quot;) &amp;amp; (df[&amp;quot;IBAN&amp;quot;] == &amp;quot;DE02100100100152517108&amp;quot;), &amp;quot;Kat2&amp;quot;] = &amp;quot;Bahn&amp;quot;&lt;br /&gt;
 df.loc[(df[&amp;quot;Kat1&amp;quot;] == &amp;quot;&amp;quot;) &amp;amp; (df[&amp;quot;Kat2&amp;quot;] == &amp;quot;Bahn&amp;quot;), &amp;quot;Kat1&amp;quot;] = &amp;quot;Mobilität&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # assigning multiple values&lt;br /&gt;
 df.loc[&lt;br /&gt;
     (df[&amp;quot;Kat1&amp;quot;] == &amp;quot;&amp;quot;) &amp;amp; (df[&amp;quot;IBAN&amp;quot;] == &amp;quot;DE02100100100152517108&amp;quot;),&lt;br /&gt;
     [&amp;quot;Kat1&amp;quot;, &amp;quot;Kat2&amp;quot;, &amp;quot;Kat3&amp;quot;],&lt;br /&gt;
 ] = [&amp;quot;Mobilität], &amp;quot;Reisen&amp;quot;, &amp;quot;Bahn&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
 # str endswith&lt;br /&gt;
 df.loc[&lt;br /&gt;
     (df[&amp;quot;RespTime&amp;quot;] == 0.0)&lt;br /&gt;
     &amp;amp; (df[&amp;quot;Text&amp;quot;].str.endswith(&amp;quot;(read timeout=30)&amp;quot;)),&lt;br /&gt;
     &amp;quot;RespTime&amp;quot;,&lt;br /&gt;
 ] = 30.0&lt;br /&gt;
&lt;br /&gt;
==Read-out Data==&lt;br /&gt;
===Columns===&lt;br /&gt;
 df2 = df [ [ &amp;quot;col1&amp;quot;, &amp;quot;col2&amp;quot; ] ]&lt;br /&gt;
&lt;br /&gt;
===Rows===&lt;br /&gt;
 for index, row in df.iterrows():&lt;br /&gt;
&lt;br /&gt;
===extract cell based on Index===&lt;br /&gt;
 col1_first = df2[&amp;quot;col1&amp;quot;].iloc[0]&lt;br /&gt;
 col1_last = float(df[&amp;quot;col1&amp;quot;].iloc[-1]) &lt;br /&gt;
 de_sum = df[&amp;quot;col&amp;quot;].loc[&amp;quot;Summe&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
===Loop over DF===&lt;br /&gt;
 for row in df.itertuples():&lt;br /&gt;
     assert type(row.start_date) is dt.datetime&lt;br /&gt;
     assert type(row.utc_offset) is int&lt;br /&gt;
     start_date = row.start_date - dt.timedelta(seconds=row.utc_offset)&lt;br /&gt;
&lt;br /&gt;
==Aggregation==&lt;br /&gt;
===Sum Column or Row===&lt;br /&gt;
 # sum of 1 column&lt;br /&gt;
 sum_cases = df[&amp;quot;Cases&amp;quot;].sum()&lt;br /&gt;
 # sum per column&lt;br /&gt;
 df_sums = df.sum(axis=&amp;quot;columns&amp;quot;)&lt;br /&gt;
 # sum per row / index&lt;br /&gt;
 df_sums = df.sum(axis=&amp;quot;index&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Filtering==&lt;br /&gt;
===Basics===&lt;br /&gt;
 # new notation&lt;br /&gt;
 var = 40&lt;br /&gt;
 df = df.query(&amp;quot;power &amp;gt;= 80 &amp;amp; cadence &amp;gt;= @var&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # old notation&lt;br /&gt;
 df = df[df[&amp;quot;x_start_locality&amp;quot;].isna() &amp;amp; df[&amp;quot;start_latlng&amp;quot;].notna()]&lt;br /&gt;
&lt;br /&gt;
 # Remove some cat&lt;br /&gt;
 df = df[~df[&amp;quot;Kategorie&amp;quot;].isin(KAT_REMOVE)]&lt;br /&gt;
 &lt;br /&gt;
 # Remove missing date&lt;br /&gt;
 df = df[df[&amp;quot;Datum&amp;quot;].notna()]&lt;br /&gt;
 &lt;br /&gt;
 df = df[ df[&amp;quot;power&amp;quot;] &amp;gt;= 80 ]&lt;br /&gt;
 df = df[ (df[&amp;quot;power&amp;quot;] &amp;gt;= 80) &amp;amp; (df[&amp;quot;cadence&amp;quot;] &amp;gt;= 40) ]&lt;br /&gt;
 # ~ inverts&lt;br /&gt;
 # list of multiple values&lt;br /&gt;
 df = df[ ~df[&amp;quot;col1&amp;quot;].isin((&amp;quot;A&amp;quot;, &amp;quot;B&amp;quot;, &amp;quot;C&amp;quot;)) ]&lt;br /&gt;
&lt;br /&gt;
===filter on index===&lt;br /&gt;
 df = df[df.index &amp;gt;= start_yearweek]&lt;br /&gt;
 &lt;br /&gt;
 df = df.drop(&amp;quot;Summe&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Filtering Dates=== &lt;br /&gt;
 # filter a date column via year&lt;br /&gt;
 df = df[df[&amp;quot;Date&amp;quot;].dt.year == 2020]&lt;br /&gt;
 # filter a date via str&lt;br /&gt;
 df = df[df[&amp;quot;Date&amp;quot;] == &amp;quot;2021-11-13&amp;quot;]&lt;br /&gt;
 # filter on date via dt&lt;br /&gt;
 df = df[&lt;br /&gt;
     df[&amp;quot;RECEIVE_DATE&amp;quot;].dt.date # convert pandas datetime64[ns] to date&lt;br /&gt;
     &amp;gt;= (dt.date.today() - dt.timedelta(days=30))&lt;br /&gt;
 ]&lt;br /&gt;
&lt;br /&gt;
===Drop Duplicate Rows===&lt;br /&gt;
 df = df.drop_duplicates()&lt;br /&gt;
&lt;br /&gt;
===Distinct values of column as list===&lt;br /&gt;
 gear_ids = df[&amp;quot;gear_id&amp;quot;].unique()&lt;br /&gt;
&lt;br /&gt;
==Date Handling==&lt;br /&gt;
===Dates===&lt;br /&gt;
====Create Date Range====&lt;br /&gt;
 df = pd.DataFrame({&amp;quot;date&amp;quot;: pd.date_range(start=&amp;quot;2023-01&amp;quot;, end=&amp;quot;2024-12&amp;quot;, freq=&amp;quot;D&amp;quot;)})&lt;br /&gt;
 df = pd.DataFrame({&amp;quot;date&amp;quot;: pd.date_range(start=&amp;quot;2023-01&amp;quot;, end=&amp;quot;2024-12&amp;quot;, freq=&amp;quot;W&amp;quot;)})&lt;br /&gt;
&lt;br /&gt;
====Convert Str Column to Date====&lt;br /&gt;
 # str to datetime&lt;br /&gt;
 df[&amp;quot;Date&amp;quot;] = pd.to_datetime(df[&amp;quot;Date&amp;quot;], format=&amp;quot;%Y-%m-%d&amp;quot;)&lt;br /&gt;
 # datetime to date&lt;br /&gt;
 df[&amp;quot;Date&amp;quot;] = df[&amp;quot;Date&amp;quot;].dt.date&lt;br /&gt;
&lt;br /&gt;
====Extract: year, month, quarter, week, weekstart====&lt;br /&gt;
 df[&amp;quot;year&amp;quot;] = df[&amp;quot;date&amp;quot;].dt.year&lt;br /&gt;
 df[&amp;quot;month&amp;quot;] = df[&amp;quot;date&amp;quot;].dt.month&lt;br /&gt;
 df[&amp;quot;quarter&amp;quot;] = df[&amp;quot;date&amp;quot;].dt.quarter&lt;br /&gt;
 df[&amp;quot;week&amp;quot;] = df[&amp;quot;date&amp;quot;].dt.isocalendar().week&lt;br /&gt;
 df[&amp;quot;weekyear&amp;quot;] = df[&amp;quot;date&amp;quot;].dt.isocalendar().year&lt;br /&gt;
 # fix week 53 (when not using weekyear)&lt;br /&gt;
 # df[&amp;quot;x_week&amp;quot;] = df[&amp;quot;x_week&amp;quot;].clip(upper=52)&lt;br /&gt;
 df.loc[(df[&amp;quot;week&amp;quot;] == 53) &amp;amp; (df[&amp;quot;month&amp;quot;] == 1), &amp;quot;x_week&amp;quot;] = 1&lt;br /&gt;
 df.loc[(df[&amp;quot;week&amp;quot;] == 53) &amp;amp; (df[&amp;quot;month&amp;quot;] == 12), &amp;quot;x_week&amp;quot;] = 52&lt;br /&gt;
 &lt;br /&gt;
 # week_start from date&lt;br /&gt;
 df[&amp;quot;week_start&amp;quot;] = df[&amp;quot;date&amp;quot;].dt.to_period(&amp;quot;W&amp;quot;).apply(lambda r: r.start_time)&lt;br /&gt;
 # week_start from week number&lt;br /&gt;
 df[&amp;quot;week_start&amp;quot;] = pd.to_datetime(&lt;br /&gt;
     df.apply(lambda row: dt.date.fromisocalendar(row[&amp;quot;year&amp;quot;], row[&amp;quot;week&amp;quot;], 1), axis=1)&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
===DateTime===&lt;br /&gt;
====Convert String to Datetime and Timezone Handling====&lt;br /&gt;
 # string to datetime, converting to UTC for future compatibility&lt;br /&gt;
 df[&amp;quot;date&amp;quot;] = pd.to_datetime(df[&amp;quot;date&amp;quot;], format=&amp;quot;ISO8601&amp;quot;, utc=True)&lt;br /&gt;
 # convert from utc to local timezone&lt;br /&gt;
 df[&amp;quot;date&amp;quot;] = df[&amp;quot;date&amp;quot;].dt.tz_convert(tz=&amp;quot;Europe/Berlin&amp;quot;)&lt;br /&gt;
 # drop timezone info, since Excel can not handle it&lt;br /&gt;
 df[&amp;quot;date&amp;quot;] = df[&amp;quot;date&amp;quot;].dt.tz_localize(None)&lt;br /&gt;
&lt;br /&gt;
====DateTime: remove miliseconds====&lt;br /&gt;
 df[&amp;quot;DateTime&amp;quot;] = df[&amp;quot;DateTime&amp;quot;].dt.ceil(freq=&amp;quot;s&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
====DateTime: extract date and add offset, depending on time====&lt;br /&gt;
 # values before 15:00 shall be treated end of last day value&lt;br /&gt;
 df[&amp;quot;day&amp;quot;] = df[&amp;quot;dt&amp;quot;].dt.date&lt;br /&gt;
 df.loc[df[&amp;quot;dt&amp;quot;].dt.hour &amp;lt; 15, &amp;quot;day&amp;quot;] = df[&amp;quot;dt&amp;quot;].dt.date - pd.Timedelta(days=1)  # noqa: PLR2004&lt;br /&gt;
&lt;br /&gt;
===TimeStamps===&lt;br /&gt;
===Convert Timestamp to Datetime and use as Index===&lt;br /&gt;
 df[&amp;quot;datetime&amp;quot;] = pd.to_datetime(  # convert to datetime&lt;br /&gt;
     df[&amp;quot;start_ts&amp;quot;],&lt;br /&gt;
     unit=&amp;quot;s&amp;quot;,  # timestamp is in seconds&lt;br /&gt;
     utc=True,  # timestamp is in UTC&lt;br /&gt;
 ).dt.tz_convert(  # convert to local TZ&lt;br /&gt;
     &amp;quot;Europe/Berlin&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 df = df.set_index(&amp;quot;datetime&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
====Convert Datetime to Timestamp====&lt;br /&gt;
 df[&amp;quot;timestamp&amp;quot;] = df[&amp;quot;datetime&amp;quot;].values.astype(np.int64) // 10**9&lt;br /&gt;
 # or&lt;br /&gt;
 df[&amp;quot;timestamp&amp;quot;] = df[&amp;quot;datetime&amp;quot;].astype(int) // 10**9&lt;br /&gt;
 # // is integer division&lt;br /&gt;
&lt;br /&gt;
====Timestamp: substract first====&lt;br /&gt;
 # calc elapsed time&lt;br /&gt;
 df[&amp;quot;seconds&amp;quot;] = df[&amp;quot;timestamp&amp;quot;] - df[&amp;quot;timestamp&amp;quot;].iloc[0]&lt;br /&gt;
&lt;br /&gt;
==String Modifications==&lt;br /&gt;
===Replace===&lt;br /&gt;
 df[&amp;quot;name&amp;quot;] = df[&amp;quot;name&amp;quot;].str.strip() # trim whitespaces&lt;br /&gt;
 df[&amp;quot;time&amp;quot;] = df[&amp;quot;time&amp;quot;].str.replace(r&amp;quot;\..*$&amp;quot;, &amp;quot;Z&amp;quot;, regex=True)&lt;br /&gt;
&lt;br /&gt;
 # remove word &amp;quot;Probable&amp;quot;&lt;br /&gt;
 df[&amp;quot;col&amp;quot;] = df[&amp;quot;col&amp;quot;].replace(&lt;br /&gt;
     to_replace=r&amp;quot;^Probable &amp;quot;, value=&amp;quot;&amp;quot;, regex=True&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
 df[&amp;quot;Text&amp;quot;] = df[&amp;quot;Text&amp;quot;].str.replace(&lt;br /&gt;
     r&amp;quot;^Menke, *Torben *&amp;quot;,&lt;br /&gt;
     &amp;quot;Torben Menke&amp;quot;,&lt;br /&gt;
     regex=True,&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
===split string===&lt;br /&gt;
 df [ [ &amp;quot;week&amp;quot;, &amp;quot;year&amp;quot; ] ] = df[&amp;quot;Kalenderwoche&amp;quot;].str.split(&amp;quot;/&amp;quot;, expand=True)&lt;br /&gt;
&lt;br /&gt;
===string &amp;lt;-&amp;gt; number===&lt;br /&gt;
string to float: 1.234,56 -&amp;gt; 1234.56&lt;br /&gt;
 df[&amp;quot;Euro&amp;quot;] = (&lt;br /&gt;
     df[&amp;quot;Euro&amp;quot;]&lt;br /&gt;
     .str.replace(&amp;quot;.&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
     .str.replace(&amp;quot;,&amp;quot;, &amp;quot;.&amp;quot;)&lt;br /&gt;
     .astype(float)&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
convert int to str adding leading zeros&lt;br /&gt;
 df[&amp;quot;Sterbewoche&amp;quot;].astype(str).str.zfill(2)&lt;br /&gt;
&lt;br /&gt;
capitalization&lt;br /&gt;
 df[&amp;quot;Wer&amp;quot;] = df[&amp;quot;Wer&amp;quot;].apply(lambda x: string.capwords(x))&lt;br /&gt;
&lt;br /&gt;
trim spaces&lt;br /&gt;
 df[&amp;quot;Wer&amp;quot;] = df[&amp;quot;Wer&amp;quot;].str.strip()&lt;br /&gt;
&lt;br /&gt;
===count word per row and check if more than one===&lt;br /&gt;
str.count()&lt;br /&gt;
 df[&amp;quot;cnt_Buchungstext&amp;quot;] = df[&amp;quot;Text&amp;quot;].str.count(&amp;quot;word&amp;quot;)&lt;br /&gt;
 df_search = df[df[&amp;quot;cnt_Buchungstext&amp;quot;] != 1]&lt;br /&gt;
 if len(df_search) &amp;gt; 0:&lt;br /&gt;
     print(df_search)&lt;br /&gt;
 del df_search&lt;br /&gt;
 df = df.drop(columns=[&amp;quot;cnt_Buchungstext&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
==Sorting / order by==&lt;br /&gt;
 # sort by column&lt;br /&gt;
 df = df.sort_values(by=[&#039;betten_belegt&#039;], ascending=False)&lt;br /&gt;
 df = df.sort_values(by=[&amp;quot;count&amp;quot;, &amp;quot;query&amp;quot;], ascending=[False, True])&lt;br /&gt;
 df = df.sort_values(by=[&amp;quot;Buchungstag&amp;quot;, &amp;quot;Text&amp;quot;], ignore_index=True)&lt;br /&gt;
&lt;br /&gt;
===Top10===&lt;br /&gt;
 df_top_ten = (&lt;br /&gt;
     df.sort_values(by=&amp;quot;count&amp;quot;, ascending=False)&lt;br /&gt;
     .head(10)&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
==Group By / Top10==&lt;br /&gt;
 df2 = (&lt;br /&gt;
     df.groupby(&amp;quot;sender&amp;quot;)&lt;br /&gt;
     .size()&lt;br /&gt;
     .to_frame(name=&amp;quot;count&amp;quot;)&lt;br /&gt;
     .sort_values(&amp;quot;count&amp;quot;, ascending=False)&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 df2 = (&lt;br /&gt;
     df[ [ &amp;quot;name&amp;quot;, &amp;quot;amount&amp;quot; ] ]&lt;br /&gt;
     .groupby(&amp;quot;name&amp;quot;)&lt;br /&gt;
     .agg(amountSum=(&amp;quot;amount&amp;quot;, &amp;quot;sum&amp;quot;), count=(&amp;quot;name&amp;quot;, &amp;quot;count&amp;quot;))&lt;br /&gt;
     .sort_values(by=[&amp;quot;amountSum&amp;quot;, &amp;quot;count&amp;quot;], ascending=False)&lt;br /&gt;
 )&lt;br /&gt;
 print(df2.head(10)) # top10&lt;br /&gt;
 &lt;br /&gt;
 # resort by count&lt;br /&gt;
 print(df2.sort_values(by=&amp;quot;count&amp;quot;, ascending=False).head(10))&lt;br /&gt;
&lt;br /&gt;
===Group by year, quarter, month, week using count and add missing===&lt;br /&gt;
Simple: add missing years for multi index&lt;br /&gt;
 df1 = pd.DataFrame(&lt;br /&gt;
     columns=[&amp;quot;year&amp;quot;, &amp;quot;type&amp;quot;, &amp;quot;count&amp;quot;],&lt;br /&gt;
     data=[&lt;br /&gt;
         (2000, &amp;quot;Run&amp;quot;, 1),&lt;br /&gt;
         (2003, &amp;quot;Ride&amp;quot;, 2),&lt;br /&gt;
         (2003, &amp;quot;Swim&amp;quot;, 1),&lt;br /&gt;
     ],&lt;br /&gt;
 )&lt;br /&gt;
 df2 = pd.DataFrame(&lt;br /&gt;
     data={&lt;br /&gt;
         &amp;quot;year&amp;quot;: range(df1[&amp;quot;year&amp;quot;].min(), df1[&amp;quot;year&amp;quot;].max() + 1),&lt;br /&gt;
         &amp;quot;type&amp;quot;: &amp;quot;Run&amp;quot;,&lt;br /&gt;
         &amp;quot;count&amp;quot;: 0,&lt;br /&gt;
     }&lt;br /&gt;
 ).set_index([&amp;quot;year&amp;quot;, &amp;quot;type&amp;quot;])&lt;br /&gt;
 df1 = df1.set_index([&amp;quot;year&amp;quot;, &amp;quot;type&amp;quot;])&lt;br /&gt;
 df = df1.add(df2, fill_value=0).reset_index()&lt;br /&gt;
&lt;br /&gt;
Advanced&lt;br /&gt;
 # df has columns: date and derived year, quarter, month, week, see above&lt;br /&gt;
 # group by year, quarter, month, week, with count as aggregation and gap-filling&lt;br /&gt;
 year_min, year_max = df[&amp;quot;year&amp;quot;].min, df[&amp;quot;year&amp;quot;].max&lt;br /&gt;
 # year&lt;br /&gt;
 df_year = (&lt;br /&gt;
     df[ [ &amp;quot;year&amp;quot;, &amp;quot;date&amp;quot; ] ].groupby([&amp;quot;year&amp;quot;]).count().rename(columns={&amp;quot;date&amp;quot;: &amp;quot;count&amp;quot;})&lt;br /&gt;
 )&lt;br /&gt;
 df_year = df_year.reindex(range(year_min(), year_max() + 1), fill_value=None)&lt;br /&gt;
 df_year = df_year.reset_index().rename(columns={&amp;quot;year&amp;quot;: &amp;quot;date-grouping&amp;quot;})&lt;br /&gt;
 &lt;br /&gt;
 # quarter&lt;br /&gt;
 df_quarter = (&lt;br /&gt;
     df[ [&amp;quot;year&amp;quot;, &amp;quot;quarter&amp;quot;, &amp;quot;date&amp;quot;] ]&lt;br /&gt;
     .groupby([&amp;quot;year&amp;quot;, &amp;quot;quarter&amp;quot;])&lt;br /&gt;
     .count()&lt;br /&gt;
     .rename(columns={&amp;quot;date&amp;quot;: &amp;quot;count&amp;quot;})&lt;br /&gt;
 )&lt;br /&gt;
 df_quarter = df_quarter.reindex(&lt;br /&gt;
     pd.MultiIndex.from_product(&lt;br /&gt;
         [range(year_min(), year_max() + 1), range(1, 5)], names=[&amp;quot;year&amp;quot;, &amp;quot;quarter&amp;quot;]&lt;br /&gt;
     ),&lt;br /&gt;
     fill_value=None,&lt;br /&gt;
 )&lt;br /&gt;
 df_quarter = df_quarter.reset_index()&lt;br /&gt;
 df_quarter[&amp;quot;date-grouping&amp;quot;] = (&lt;br /&gt;
     df_quarter[&amp;quot;year&amp;quot;].astype(str) + &amp;quot;-Q&amp;quot; + df_quarter[&amp;quot;quarter&amp;quot;].astype(str)&lt;br /&gt;
 )&lt;br /&gt;
 df_quarter = df_quarter.drop(columns=[&amp;quot;year&amp;quot;, &amp;quot;quarter&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 # month&lt;br /&gt;
 df_month = (&lt;br /&gt;
     df[ [&amp;quot;year&amp;quot;, &amp;quot;month&amp;quot;, &amp;quot;date&amp;quot;] ]&lt;br /&gt;
     .groupby([&amp;quot;year&amp;quot;, &amp;quot;month&amp;quot;])&lt;br /&gt;
     .count()&lt;br /&gt;
     .rename(columns={&amp;quot;date&amp;quot;: &amp;quot;count&amp;quot;})&lt;br /&gt;
 )&lt;br /&gt;
 df_month = df_month.reindex(&lt;br /&gt;
     pd.MultiIndex.from_product(&lt;br /&gt;
         [range(year_min(), year_max() + 1), range(1, 13)], names=[&amp;quot;year&amp;quot;, &amp;quot;month&amp;quot;]&lt;br /&gt;
     ),&lt;br /&gt;
     fill_value=None,&lt;br /&gt;
 )&lt;br /&gt;
 df_month = df_month.reset_index()&lt;br /&gt;
 df_month[&amp;quot;date-grouping&amp;quot;] = (&lt;br /&gt;
     df_month[&amp;quot;year&amp;quot;].astype(str) + &amp;quot;-&amp;quot; + df_month[&amp;quot;month&amp;quot;].astype(str).str.zfill(2)&lt;br /&gt;
 )&lt;br /&gt;
 df_month = df_month.drop(columns=[&amp;quot;year&amp;quot;, &amp;quot;month&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
 # week&lt;br /&gt;
 df_week = (&lt;br /&gt;
     df[ [&amp;quot;year&amp;quot;, &amp;quot;week&amp;quot;, &amp;quot;date&amp;quot;] ]&lt;br /&gt;
     .groupby([&amp;quot;year&amp;quot;, &amp;quot;week&amp;quot;])&lt;br /&gt;
     .count()&lt;br /&gt;
     .rename(columns={&amp;quot;date&amp;quot;: &amp;quot;count&amp;quot;})&lt;br /&gt;
 )&lt;br /&gt;
 df_week = df_week.reindex(&lt;br /&gt;
     pd.MultiIndex.from_product(&lt;br /&gt;
         [range(year_min(), year_max() + 1), range(1, 53)], names=[&amp;quot;year&amp;quot;, &amp;quot;week&amp;quot;]&lt;br /&gt;
     ),&lt;br /&gt;
     fill_value=None,&lt;br /&gt;
 )&lt;br /&gt;
 df_week = df_week.reset_index()&lt;br /&gt;
 df_week[&amp;quot;date-grouping&amp;quot;] = (&lt;br /&gt;
     df_week[&amp;quot;year&amp;quot;].astype(str) + &amp;quot;-&amp;quot; + df_week[&amp;quot;week&amp;quot;].astype(str).str.zfill(2)&lt;br /&gt;
 )&lt;br /&gt;
 df_week = df_week.drop(columns=[&amp;quot;year&amp;quot;, &amp;quot;week&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
===Group by Type and Date===&lt;br /&gt;
 # group by hour&lt;br /&gt;
 # truncate min and sec data&lt;br /&gt;
 df[&amp;quot;hour&amp;quot;] = pd.to_datetime(df[&amp;quot;time&amp;quot;]).dt.floor(&amp;quot;H&amp;quot;)&lt;br /&gt;
 # sum per hour&lt;br /&gt;
 df2 = df[ [&amp;quot;hour&amp;quot;, &amp;quot;amount&amp;quot;] ].groupby(&amp;quot;hour&amp;quot;).agg(amountSum=(&amp;quot;amount&amp;quot;, &amp;quot;sum&amp;quot;))&lt;br /&gt;
 df2.plot()&lt;br /&gt;
 plt.show()&lt;br /&gt;
&lt;br /&gt;
 # Datetime Index Grouping&lt;br /&gt;
 df = df.groupby(pd.Grouper(freq=&amp;quot;5min&amp;quot;, offset=&amp;quot;00h00min&amp;quot;)).max()&lt;br /&gt;
 &lt;br /&gt;
 # Group by type and date of month start&lt;br /&gt;
 df_month = df.groupby([&amp;quot;type&amp;quot;, pd.Grouper(key=&amp;quot;date&amp;quot;, freq=&amp;quot;MS&amp;quot;)]).agg(&lt;br /&gt;
     {&amp;quot;id&amp;quot;: &amp;quot;count&amp;quot;, &amp;quot;minutes&amp;quot;: &amp;quot;sum&amp;quot;}&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 # generate index of the desired month-freq:&lt;br /&gt;
 idx = pd.date_range(&lt;br /&gt;
     start=df[&amp;quot;date&amp;quot;].min().replace(day=1),&lt;br /&gt;
     end=df[&amp;quot;date&amp;quot;].max().replace(day=1),&lt;br /&gt;
     freq=&amp;quot;MS&amp;quot;,  # MS = Month Start&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 # add missing months&lt;br /&gt;
 df_month = df_month.reindex(&lt;br /&gt;
     pd.MultiIndex.from_product(&lt;br /&gt;
         [df_month.index.get_level_values(&amp;quot;type&amp;quot;), idx],&lt;br /&gt;
         names=[&amp;quot;type&amp;quot;, &amp;quot;date&amp;quot;],&lt;br /&gt;
     )&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 # fill missing by 0 and convert count back to type int&lt;br /&gt;
 df_month = df_month.fillna(0).astype({&amp;quot;count&amp;quot;: int})&lt;br /&gt;
&lt;br /&gt;
====Group Datetime by Time of Day only====&lt;br /&gt;
 df[&amp;quot;Time&amp;quot;] = df[&amp;quot;Date&amp;quot;].dt.round(&amp;quot;5min&amp;quot;).dt.time&lt;br /&gt;
 &lt;br /&gt;
 df_grouped = df [ [&amp;quot;RespTime&amp;quot;, &amp;quot;Time&amp;quot; ] ].groupby(&amp;quot;Time&amp;quot;).mean()&lt;br /&gt;
&lt;br /&gt;
====Group and calculate min,avg,max====&lt;br /&gt;
 df_grouped = (&lt;br /&gt;
     df[ [&amp;quot;Time&amp;quot;, &amp;quot;RespTime&amp;quot;] ]&lt;br /&gt;
     .groupby(&amp;quot;Time&amp;quot;)&lt;br /&gt;
     .agg(&lt;br /&gt;
         max=pd.NamedAgg(column=&amp;quot;RespTime&amp;quot;, aggfunc=&amp;quot;max&amp;quot;),&lt;br /&gt;
         avg=pd.NamedAgg(column=&amp;quot;RespTime&amp;quot;, aggfunc=&amp;quot;mean&amp;quot;),&lt;br /&gt;
         min=pd.NamedAgg(column=&amp;quot;RespTime&amp;quot;, aggfunc=&amp;quot;min&amp;quot;),&lt;br /&gt;
     )&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
==Column Handling==&lt;br /&gt;
===Renaming===&lt;br /&gt;
 df = df.rename(&lt;br /&gt;
      columns = {&amp;quot;invasiv_beatmet&amp;quot;: &amp;quot;beatmet&amp;quot;,}, &lt;br /&gt;
      errors=&amp;quot;raise&amp;quot;,&lt;br /&gt;
      )&lt;br /&gt;
&lt;br /&gt;
rename column headers by extracting some int values from a string&lt;br /&gt;
 l2 = []&lt;br /&gt;
 for col in df.columns:&lt;br /&gt;
     year = int(col[0:4])&lt;br /&gt;
     week = int(col[5:7])&lt;br /&gt;
     l2.append(year * 100 + week)&lt;br /&gt;
 df.columns = l2&lt;br /&gt;
&lt;br /&gt;
===Dropping===&lt;br /&gt;
drop columns&lt;br /&gt;
 df = df.drop(columns=[&amp;quot;Sterbejahr&amp;quot;, &amp;quot;Sterbewoche&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
===Change Column Order===&lt;br /&gt;
 df = df.reindex(&lt;br /&gt;
     [&lt;br /&gt;
         &amp;quot;Wann&amp;quot;,&lt;br /&gt;
         &amp;quot;Art&amp;quot;,&lt;br /&gt;
         &amp;quot;Wer&amp;quot;,&lt;br /&gt;
     ],&lt;br /&gt;
     axis=&amp;quot;columns&amp;quot;,&lt;br /&gt;
 &lt;br /&gt;
 # column reorder&lt;br /&gt;
 cols_first = [&amp;quot;year&amp;quot;, &amp;quot;month&amp;quot;, &amp;quot;week&amp;quot;, &amp;quot;weekyear&amp;quot;]&lt;br /&gt;
 cols_last = [col for col in df.columns if col not in cols_first]&lt;br /&gt;
 cols_first.extend(cols_last)&lt;br /&gt;
 df = df[cols_first]&lt;br /&gt;
 del cols_first, cols_last&lt;br /&gt;
 &lt;br /&gt;
 # same but different&lt;br /&gt;
 first_columns = (&amp;quot;description&amp;quot;, &amp;quot;isin&amp;quot;, &amp;quot;side&amp;quot;, &amp;quot;quantity&amp;quot;, &amp;quot;Price&amp;quot;, &amp;quot;amount&amp;quot;)&lt;br /&gt;
 i = 0&lt;br /&gt;
 for col in first_columns:&lt;br /&gt;
     df.insert(i, col, df.pop(col))&lt;br /&gt;
     i += 1&lt;br /&gt;
 del i&lt;br /&gt;
&lt;br /&gt;
==Index Handling==&lt;br /&gt;
select column as index&lt;br /&gt;
 df = df.set_index(&amp;quot;Date&amp;quot;)&lt;br /&gt;
move date from index back to column&lt;br /&gt;
 df = df.reset_index()&lt;br /&gt;
&lt;br /&gt;
rename index&lt;br /&gt;
 df.index.name = &amp;quot;Date&amp;quot;&lt;br /&gt;
&lt;br /&gt;
reset index to start at 0&lt;br /&gt;
 df2 = df1[1 * 365 : 2 * 365].reset_index(drop=True)&lt;br /&gt;
&lt;br /&gt;
int index to str index&lt;br /&gt;
 df.index = df.index.map(str)&lt;br /&gt;
&lt;br /&gt;
===text indexes===&lt;br /&gt;
 df = df.set_index(&amp;quot;Altersgruppe&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===datetime indexes===&lt;br /&gt;
 assert type(df.index) is pd.DatetimeIndex&lt;br /&gt;
 df.index = df.index.tz_convert(&amp;quot;Europe/Berlin&amp;quot;)&lt;br /&gt;
 df.index = df.index.tz_convert(None) # to remove tz info&lt;br /&gt;
 &lt;br /&gt;
 # select &amp;quot;Date&amp;quot; column as index&lt;br /&gt;
 df = df.set_index([&amp;quot;Date&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
 # convert index to datetime &lt;br /&gt;
 df.index = pd.to_datetime(df.index)&lt;br /&gt;
 &lt;br /&gt;
 # add missing dates&lt;br /&gt;
 df = df.reindex(&lt;br /&gt;
     pd.date_range(df.index.min(), df.index.max(), freq=&amp;quot;D&amp;quot;), fill_value=0&lt;br /&gt;
 )&lt;br /&gt;
 df.index.name = &amp;quot;date&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # remove timezone offset (for Excel can not handle this)&lt;br /&gt;
 df.index = df.index.tz_localize(None)&lt;br /&gt;
  &lt;br /&gt;
 date_last = df.index[-1])&lt;br /&gt;
&lt;br /&gt;
 # reindex and fill missing with 0&lt;br /&gt;
 date_last = pd.to_datetime(df.index[-1]).date()&lt;br /&gt;
 idx = pd.date_range(&#039;2020-01-01&#039;, date_last))&lt;br /&gt;
 df = df.reindex(idx, fill_value=0)&lt;br /&gt;
 &lt;br /&gt;
 # add missing dates&lt;br /&gt;
 df = df.asfreq(&#039;D&#039;, fill_value=0)&lt;br /&gt;
 df = df.asfreq(freq=&amp;quot;5M&amp;quot;)&lt;br /&gt;
 df = df.sort_index() # needed?&lt;br /&gt;
 &lt;br /&gt;
 # drop values of column for last 3 weeks&lt;br /&gt;
 date_3w = dt.date.today() - dt.timedelta(weeks=3)&lt;br /&gt;
 df.loc[df.index.date &amp;gt;= date_3w, &amp;quot;Cases&amp;quot;] = None&lt;br /&gt;
 # or&lt;br /&gt;
 df.loc[df.index.date &amp;lt; pd.to_datetime(&amp;quot;2020-03-01&amp;quot;), &amp;quot;Value&amp;quot;] = None&lt;br /&gt;
&lt;br /&gt;
====filter on date index====&lt;br /&gt;
 # drop data prior to 2020&lt;br /&gt;
 df = df.loc[&#039;2020-01-01&#039;:]&lt;br /&gt;
 # alternative:&lt;br /&gt;
 df = df[df.index &amp;gt;= &amp;quot;2021-01-10&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
==Pivot, UnPivot/Melt==&lt;br /&gt;
===Pivot===&lt;br /&gt;
 # df has columns &amp;quot;key&amp;quot; (column that shall be the index) , &amp;quot;year&amp;quot; (column you want to convert to multiple columns), &amp;quot;value&amp;quot; (the numbers to aggregate)&lt;br /&gt;
 # convert to wide format with id as columns&lt;br /&gt;
 df2 = df.pivot_table(index=&amp;quot;key&amp;quot;, columns=&amp;quot;year&amp;quot;, values=&amp;quot;value&amp;quot;, aggfunc=&amp;quot;first&amp;quot;) # aggfunc=&amp;quot;sum&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===melt/unpivot wide table to long table format===&lt;br /&gt;
 df2 = pd.melt(df, id_vars=&amp;quot;Date&amp;quot;, value_vars=df.columns[1:])  #&lt;br /&gt;
 df2 = df2.rename(columns={&amp;quot;variable&amp;quot;: &amp;quot;Machine&amp;quot;, &amp;quot;value&amp;quot;: &amp;quot;Status&amp;quot;})&lt;br /&gt;
 &lt;br /&gt;
 # not working, using melt instead&lt;br /&gt;
 # df3 = pd.wide_to_long(df, stubnames=&amp;quot;Date&amp;quot;, i=df.columns[1:], j=&amp;quot;Status&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Merge/Concat and Append DFs==&lt;br /&gt;
===Merge/Concat===&lt;br /&gt;
 df_sum = pd.DataFrame()&lt;br /&gt;
 df_sum = pd.concat(&lt;br /&gt;
     [df_sum, df],&lt;br /&gt;
     ignore_index=True,&lt;br /&gt;
 )&lt;br /&gt;
===Append===&lt;br /&gt;
 # merge columns from different dfs, when have matching index, e.g. user_id&lt;br /&gt;
 df = pd.concat([df1, df2], axis=1)&lt;br /&gt;
 &lt;br /&gt;
 # append df2 to end of df1&lt;br /&gt;
 df1 = pd.DataFrame({&amp;quot;date&amp;quot;: pd.date_range(start=&amp;quot;2020-01&amp;quot;, end=&amp;quot;2020-02&amp;quot;, freq=&amp;quot;W&amp;quot;)})&lt;br /&gt;
 df2 = pd.DataFrame({&amp;quot;date&amp;quot;: pd.date_range(start=&amp;quot;2024-01&amp;quot;, end=&amp;quot;2024-12&amp;quot;, freq=&amp;quot;D&amp;quot;)})&lt;br /&gt;
 df = pd.concat([df1, df2]).reset_index(drop=True)&lt;br /&gt;
&lt;br /&gt;
 # join series of 2 df&lt;br /&gt;
 df_covid_2020 = pd.DataFrame()&lt;br /&gt;
 df_covid_2020[&#039;Deaths_Covid_2020&#039;] = df1[&#039;Deaths_Covid_2020&#039;].append(&lt;br /&gt;
     df2[&#039;Deaths_Covid_2020&#039;], ignore_index=True)&lt;br /&gt;
 &lt;br /&gt;
 # note: this would require all index of df in df2 present:&lt;br /&gt;
 # df[machine] = df2[machine]&lt;br /&gt;
&lt;br /&gt;
==Rolling Average, mean of columns, min, max==&lt;br /&gt;
 df[&#039;2016_roll&#039;] = df[&#039;2016&#039;].rolling(window=7, min_periods=1).mean().round(1)&lt;br /&gt;
 df[&#039;2017_roll&#039;] = df[&#039;2017&#039;].rolling(window=7, min_periods=1).mean().round(1)&lt;br /&gt;
 df[&#039;2018_roll&#039;] = df[&#039;2018&#039;].rolling(window=7, min_periods=1).mean().round(1)&lt;br /&gt;
 df[&#039;2019_roll&#039;] = df[&#039;2019&#039;].rolling(window=7, min_periods=1).mean().round(1)&lt;br /&gt;
 df[&#039;2020_roll&#039;] = df[&#039;2020&#039;].rolling(window=7, min_periods=1).mean().round(1)&lt;br /&gt;
 # mean value of 4 columns&lt;br /&gt;
 df[&#039;2016_2019_mean&#039;] = df.iloc[:, [1, 2, 3, 4]&lt;br /&gt;
                                ].mean(axis=1)  # not column 0 = day&lt;br /&gt;
 df[&#039;2016_2019_mean_roll&#039;] = df[&#039;2016_2019_mean&#039;].rolling(&lt;br /&gt;
     window=7, min_periods=1).mean().round(1)&lt;br /&gt;
 &lt;br /&gt;
 df[&#039;2016_2019_roll_max&#039;] = df.iloc[:, [6, 7, 8, 9]].max(axis=1)&lt;br /&gt;
 df[&#039;2016_2019_roll_min&#039;] = df.iloc[:, [6, 7, 8, 9]].min(axis=1)&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
==Helpers==&lt;br /&gt;
 def pandas_set_date_index(df, date_column: str):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot; use date as index &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     df[date_column] = pd.to_datetime(df[date_column], format=&#039;%Y-%m-%d&#039;)&lt;br /&gt;
     df = df.set_index([date_column])&lt;br /&gt;
     return df&lt;br /&gt;
 &lt;br /&gt;
 def pandas_calc_roll_av(df, column: str, days: int = 7):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot; calc rolling average over column &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     df[column + &#039;_roll_av&#039;] = df[column].rolling(&lt;br /&gt;
         window=days, min_periods=1).mean().round(1)&lt;br /&gt;
     return df&lt;br /&gt;
 &lt;br /&gt;
 # custom rounding&lt;br /&gt;
 def custom_round(x: float, base: int = 5) -&amp;gt; int:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Custom rounding.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     # from https://stackoverflow.com/questions/40372030/pandas-round-to-the-nearest-n&lt;br /&gt;
     return int(base * round(float(x) / base))&lt;br /&gt;
 &lt;br /&gt;
 df[&amp;quot;power_rounded&amp;quot;] = df[&amp;quot;power&amp;quot;].apply( lambda x: custom_round(x, base=20) )&lt;br /&gt;
&lt;br /&gt;
==Transpose==&lt;br /&gt;
 df = df.transpose()&lt;br /&gt;
&lt;br /&gt;
==Leftovers==&lt;br /&gt;
 # when in a function one might get the SettingWithCopyWarning, fix via&lt;br /&gt;
 df = df.copy()&lt;br /&gt;
&lt;br /&gt;
 # copy&lt;br /&gt;
 df2[&#039;Date&#039;] = df0[&#039;Date&#039;]&lt;br /&gt;
 &lt;br /&gt;
 # drop 2 rows from the beginning&lt;br /&gt;
 df = df2.drop([0, 1])&lt;br /&gt;
&lt;br /&gt;
====append today using yesterdays value====&lt;br /&gt;
 str_today = dt.datetime.today().strftime(&amp;quot;%Y-%m-%d&amp;quot;)&lt;br /&gt;
 ts_today = pd.Timestamp(str_today)&lt;br /&gt;
 if df.index[-1] != ts_today:&lt;br /&gt;
     df.loc[ts_today] = df.loc[df.index[-1], &amp;quot;Count&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
==Plotting via Matplotlib==&lt;br /&gt;
see [[Matplotlib]]&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Windows_Batch_Scripting&amp;diff=5399</id>
		<title>Windows Batch Scripting</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Windows_Batch_Scripting&amp;diff=5399"/>
		<updated>2026-03-06T14:27:13Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Net use for network drive mounting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Software]][[Category:Windows]]&lt;br /&gt;
[http://weblogs.asp.net/jongalloway/top-10-dos-batch-tips-yes-dos-batch see &amp;quot;Top 10 DOS Batch tips&amp;quot;] and [http://www.axel-hahn.de/batch/batchecke/tipps/ BATch-Dateien - kleine Tipps] for more stuff&lt;br /&gt;
&lt;br /&gt;
===current dir and filename===&lt;br /&gt;
 # current working dir&lt;br /&gt;
 echo %CD%&lt;br /&gt;
 # path of current file&lt;br /&gt;
 echo %0&lt;br /&gt;
# filename without the extension.&lt;br /&gt;
 echo %~n0&lt;br /&gt;
# filename and extension.&lt;br /&gt;
 echo %~nx0&lt;br /&gt;
# or &lt;br /&gt;
 echo %~n0%~x0&lt;br /&gt;
&lt;br /&gt;
===Title to Dirname===&lt;br /&gt;
 @echo off&lt;br /&gt;
 for %%f in (%cd%) do set dirname=%%~nxf&lt;br /&gt;
 title %dirname%&lt;br /&gt;
&lt;br /&gt;
===Empty c:\tmp===&lt;br /&gt;
 @echo off&lt;br /&gt;
 rmdir /q /s c:\tmp&lt;br /&gt;
 # or rd /q /s c:\tmp&lt;br /&gt;
 mkdir c:\tmp&lt;br /&gt;
&lt;br /&gt;
===find files / search (and delete) old files===&lt;br /&gt;
via forfiles&lt;br /&gt;
 forfiles /P &amp;quot;C:\tmp\test-del&amp;quot; /S /D -30 /C &amp;quot;cmd /c echo @path&amp;quot;&lt;br /&gt;
 forfiles /P &amp;quot;C:\tmp\test-del&amp;quot; /S /D -30 /C &amp;quot;cmd /c del @path&amp;quot;&lt;br /&gt;
 forfiles /P &amp;quot;C:\tmp\test-del&amp;quot; -S -D 90 -m *.log -c &amp;quot;cmd /c del @path&amp;quot;&lt;br /&gt;
&lt;br /&gt;
via robocopy&lt;br /&gt;
 robocopy &amp;quot;C:\tmp\test-del&amp;quot; &amp;quot;*.log&amp;quot; &amp;quot;C:\cleanup-logs&amp;quot; /mov /minage:90 /NP&lt;br /&gt;
 del C:\cleanup-logs /q&lt;br /&gt;
&lt;br /&gt;
===ReadOnly Flag / WriteProtection===&lt;br /&gt;
Remove&lt;br /&gt;
 attrib -h -r -s /s /d *.*&lt;br /&gt;
Set &lt;br /&gt;
 attrib +r c:\Users\torben\Desktop\SyncedFolder\*.* /s&lt;br /&gt;
&lt;br /&gt;
  R   Read-only file attribute.&lt;br /&gt;
  S   System file attribute.&lt;br /&gt;
  H   Hidden file attribute.&lt;br /&gt;
  /S  Processes matching files in the current folder and all subfolders.&lt;br /&gt;
  /D  Processes folders as well.&lt;br /&gt;
&lt;br /&gt;
===Set Current Working Dir to Script Location ===&lt;br /&gt;
This is important if the script is started using a taskmanager etc.&lt;br /&gt;
 e:&lt;br /&gt;
 cd %~dp0&lt;br /&gt;
&lt;br /&gt;
===Set Window Title===&lt;br /&gt;
 TITLE My Window Title&lt;br /&gt;
or when opening via START from another Batch file&lt;br /&gt;
 START &amp;quot;My Window Title&amp;quot; 2.cmd&lt;br /&gt;
&lt;br /&gt;
===Get FolderName===&lt;br /&gt;
This when excecuted in c:\sub\folder it returns &amp;quot;folder&amp;quot;&lt;br /&gt;
 for %%* in (%CD%) do set CurrDirName=%%~nx*&lt;br /&gt;
 echo %CurrDirName%&lt;br /&gt;
&lt;br /&gt;
===List of files to Textfile===&lt;br /&gt;
 dir *.* /b &amp;gt; ..\liste.txt&lt;br /&gt;
&lt;br /&gt;
===Pipe output to textfile===&lt;br /&gt;
From [https://support.microsoft.com/en-us/help/110930/redirecting-error-messages-from-command-prompt-stderr-stdout] You can print the errors and standard output to a single file by using the &amp;quot;&amp;amp;1&amp;quot; command to redirect the output for STDERR to STDOUT and then sending the output from STDOUT to a file:&lt;br /&gt;
 some_command &amp;gt; output.log 2&amp;gt;&amp;amp;1&lt;br /&gt;
 some_command 1&amp;gt; output.log 2&amp;gt; error.log&lt;br /&gt;
&lt;br /&gt;
===exit if command fails with error===&lt;br /&gt;
 some_command1&lt;br /&gt;
 IF %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%&lt;br /&gt;
 some_command2&lt;br /&gt;
 IF %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%&lt;br /&gt;
&lt;br /&gt;
===Net use for network drive mounting===&lt;br /&gt;
 net use Y: \\server\path /user:myuser&lt;br /&gt;
 &lt;br /&gt;
 net use Y: /delete&lt;br /&gt;
&lt;br /&gt;
===Zip */*.ini===&lt;br /&gt;
 @echo off&lt;br /&gt;
 setlocal EnableDelayedExpansion&lt;br /&gt;
 set &amp;quot;OUT=inis.zip&amp;quot;&lt;br /&gt;
 REM Use PowerShell to expand glob pattern and pipe relative paths to tar with --files-from=-&lt;br /&gt;
 powershell -NoLogo -NoProfile -Command &amp;quot;Get-ChildItem -Path &#039;*\*.ini&#039; -File | ForEach-Object { $_.FullName.Substring((Get-Location).Path.Length + 1) } | tar -c -a -f &#039;%OUT%&#039; --files-from=-&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===Loops===&lt;br /&gt;
====if====&lt;br /&gt;
check if mounting (net use) was successful&lt;br /&gt;
 net use q: \\server\share&lt;br /&gt;
 q:&lt;br /&gt;
 cd \&lt;br /&gt;
 IF NOT EXIST dir1\dir2 (&lt;br /&gt;
   echo mouting of share failed&lt;br /&gt;
   pause&lt;br /&gt;
   c:&lt;br /&gt;
   net use q: /delete&lt;br /&gt;
   EXIT /B 1&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
====for====&lt;br /&gt;
loop over values&lt;br /&gt;
 for %%L in (cn, de, en, es, fr, ko, sk, vn) do (&lt;br /&gt;
 echo %%L&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
loop over all dirs&lt;br /&gt;
 FOR /D %%D in (&amp;quot;*&amp;quot;) DO (&lt;br /&gt;
 echo %%D&lt;br /&gt;
 )&lt;br /&gt;
double loop over all dirs&lt;br /&gt;
 FOR /D %%P in (&amp;quot;*&amp;quot;) DO (&lt;br /&gt;
 echo parent = %%P&lt;br /&gt;
   FOR /D %%C in (&amp;quot;%%P\*&amp;quot;) DO (&lt;br /&gt;
   echo child = %%C&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
loop over files&lt;br /&gt;
 for %%F in (*.pdf) do (&lt;br /&gt;
 echo %%F&lt;br /&gt;
 copy %%F %%~nF.old&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
====for and grep.exe====&lt;br /&gt;
using UnixUtils grep.exe one can easily extract exceptions from logs:&lt;br /&gt;
 for %%F in (*.log) do (&lt;br /&gt;
   grep -B3 -A3 &amp;quot;exception&amp;quot; %%F &amp;gt; %%~nF-error.txt &lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
===Variables===&lt;br /&gt;
 :: no spaces around &#039;=&#039;!!!&lt;br /&gt;
 set xyz=myfile.bat&lt;br /&gt;
 set /p xyz=Variable Eingeben:&lt;br /&gt;
 set /p xyz= &amp;lt; TMP.dat &lt;br /&gt;
 echo %xyz%&lt;br /&gt;
&lt;br /&gt;
====Substrings====&lt;br /&gt;
Substring via &amp;quot;:~&amp;quot;&lt;br /&gt;
 set year=%date:~-4,4&lt;br /&gt;
Path, filename and extension of file stored in variable %%F&lt;br /&gt;
 set path=%%~pF&lt;br /&gt;
 set name=%%~nF&lt;br /&gt;
 set ext=%%~cF&lt;br /&gt;
&lt;br /&gt;
====Set Variable to Program Output====&lt;br /&gt;
 for /f &amp;quot;delims=&amp;quot; %%a in (&#039; powershell -c &amp;quot;$lastmonth = (Get-Date).addMonths(-3); $lastmonth.tostring(\&amp;quot;yyyy-MM\&amp;quot;)&amp;quot; &#039;) do set &amp;quot;MONTH=%%a&amp;quot;&lt;br /&gt;
 echo %MONTH%&lt;br /&gt;
&lt;br /&gt;
===Date &amp;amp; Time ===&lt;br /&gt;
====DateString====&lt;br /&gt;
 set DATESTR=%date:~-4,4%-%date:~-7,2%-%date:~-10,2%_%time:~0,2%:%time:~3,2%:%time:~6,2%&lt;br /&gt;
 or&lt;br /&gt;
 set DATESTR=%date:~-2,4%%date:~-7,2%%date:~-10,2%_%time:~0,2%%time:~3,2%%time:~6,2%&lt;br /&gt;
 :: replace &#039; &#039; in small hours with 0&lt;br /&gt;
 set DATESTR=%DATESTR: =0%&lt;br /&gt;
 = 240419_085724 &lt;br /&gt;
 zip.exe -9 %DATESTR%.zip *.bat&lt;br /&gt;
for better example of zipping see [[Backup#Zip_Folder|zip folder in Backup section]]&lt;br /&gt;
&lt;br /&gt;
====DayOfWeek====&lt;br /&gt;
from [https://stackoverflow.com/questions/25537313/issue-with-getting-batch-script-to-print-keeps-given-a-error-about-set-was-unexp here]&lt;br /&gt;
 SETLOCAL enabledelayedexpansion&lt;br /&gt;
 SET /a count=0&lt;br /&gt;
 FOR /F &amp;quot;skip=1&amp;quot; %%D IN (&#039;wmic path win32_localtime get dayofweek&#039;) DO (&lt;br /&gt;
     if &amp;quot;!count!&amp;quot; GTR &amp;quot;0&amp;quot; GOTO next&lt;br /&gt;
     set dow=%%D&lt;br /&gt;
     SET /a count+=1&lt;br /&gt;
 )&lt;br /&gt;
 :next&lt;br /&gt;
 echo %dow%&lt;br /&gt;
&lt;br /&gt;
====Yesterday====&lt;br /&gt;
from [https://stackoverflow.com/questions/2954359/dos-batch-programming-howto-get-and-display-yesterday-date here]&lt;br /&gt;
 set befehl=&amp;quot;PowerShell $date = Get-Date; $date=$date.AddDays(-1); $date.ToString(&#039;yyyy-MM-dd&#039;)&amp;quot;&lt;br /&gt;
 for /f %%i in (&#039;%befehl%&#039;) do set yesterday=%%i&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Copy all files from subfolders to one folder ===&lt;br /&gt;
from [https://stackoverflow.com/questions/11720681/windows-batch-copy-files-from-subfolders-to-one-folder here]&lt;br /&gt;
 set source=c:\source&lt;br /&gt;
 set target=c:\target&lt;br /&gt;
 cd %source%&lt;br /&gt;
 for /r %%a in (*.*) do (&lt;br /&gt;
  COPY &amp;quot;%%a&amp;quot; &amp;quot;%target%&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
===FTP===&lt;br /&gt;
====FTP-Upload====&lt;br /&gt;
file &amp;quot;ftp.bat&amp;quot;&lt;br /&gt;
 @echo off&lt;br /&gt;
 ftp &amp;quot;-s:FtpScript&amp;quot;&lt;br /&gt;
 pause&lt;br /&gt;
 cls&lt;br /&gt;
&lt;br /&gt;
file &amp;quot;FtpScript&amp;quot;&lt;br /&gt;
 open www.xyz.de&lt;br /&gt;
 [User]&lt;br /&gt;
 [Password]&lt;br /&gt;
 BINARY&lt;br /&gt;
 put [File]&lt;br /&gt;
 quit&lt;br /&gt;
&lt;br /&gt;
===UnixUtils===&lt;br /&gt;
Using [https://sourceforge.net/projects/unxutils/ UnixUtils] for Windows wget, grep, etc are usable to scripts in Windows. In the UnixUtils.zip the .exe files are located in foltder usr/local/wbin/ . Examples:&lt;br /&gt;
 head.exe -c 1000 mylog.log &amp;gt; outHead1000Bytes.log&lt;br /&gt;
 head.exe -n 1000 mylog.log &amp;gt; outHead1000Lines.log&lt;br /&gt;
 tail.exe -c 1000 mylog.log &amp;gt; outTail1000Bytes.log&lt;br /&gt;
 tail.exe -n 1000 mylog.log &amp;gt; outTail1000Lines.log&lt;br /&gt;
 grep.exe -i -B3 -A1 &amp;quot;ERROR&amp;quot; mylog.log &amp;gt; outGrepErrors.log&lt;br /&gt;
&lt;br /&gt;
====Curl to check if page online====&lt;br /&gt;
 @echo off&lt;br /&gt;
 set URL1=https://my.server.com&lt;br /&gt;
 :loop&lt;br /&gt;
 for /f &amp;quot;delims=&amp;quot; %%i in (&#039;curl -o nul -I -s -w &amp;quot;%%{http_code}&amp;quot; &amp;quot;%URL%&amp;quot;&#039;) do set HTTP_STATUS=%%i&lt;br /&gt;
 echo Status: %HTTP_STATUS% at %date% %time%&lt;br /&gt;
 timeout /t 5&lt;br /&gt;
 goto loop&lt;br /&gt;
&lt;br /&gt;
===TOP CPU RAM Disk I/O===&lt;br /&gt;
# CPU + RAM&lt;br /&gt;
 top&lt;br /&gt;
# I/O&lt;br /&gt;
 sudo iotop -o -d 2 -k&lt;br /&gt;
&lt;br /&gt;
===Run as other user===&lt;br /&gt;
 runas /user:MYDOMAIN\myuser /savecred  &amp;quot;cmd /K cd c:\mydir &amp;amp;&amp;amp; my_programm.exe&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 /K to keep window open when finished&lt;br /&gt;
 /C to close window when finished&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5398</id>
		<title>AI LLM for Coding</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5398"/>
		<updated>2026-02-22T06:58:50Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Google Gemini==&lt;br /&gt;
pros: free to use with google account&lt;br /&gt;
create your API key at https://aistudio.google.com/app/api-keys&lt;br /&gt;
&lt;br /&gt;
Install&lt;br /&gt;
 npm install -g @google/gemini-cli@latest&lt;br /&gt;
 gemini&lt;br /&gt;
 # or just run&lt;br /&gt;
 npx https://github.com/google-gemini/gemini-cli&lt;br /&gt;
usage&lt;br /&gt;
 cd /tmp&lt;br /&gt;
 export GEMINI_API_KEY=&amp;quot;xxx&amp;quot;&lt;br /&gt;
 git clone https://github.com/entorb/meeting-meter&lt;br /&gt;
 cd myProject&lt;br /&gt;
 gemini&lt;br /&gt;
&lt;br /&gt;
to switch model from pro to flash:&lt;br /&gt;
 export GEMINI_MODEL=&amp;quot;gemini-2.5-flash&amp;quot;&lt;br /&gt;
&lt;br /&gt;
secret alternative: .env file&lt;br /&gt;
 GEMINI_API_KEY=xxx&lt;br /&gt;
&lt;br /&gt;
==Google Antigravity==&lt;br /&gt;
https://antigravity.google/download&lt;br /&gt;
&lt;br /&gt;
==Claude==&lt;br /&gt;
===Web===&lt;br /&gt;
https://claude.ai &amp;lt;br&amp;gt;&lt;br /&gt;
pro: Daily reset free quota&amp;lt;br&amp;gt;&lt;br /&gt;
cons: need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
===Console===&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: https://console.anthropic.com &amp;lt;br&amp;gt;&lt;br /&gt;
pro: fully integrated in code base&amp;lt;br&amp;gt;&lt;br /&gt;
cons: costs money, 5€ are easily spend&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
&lt;br /&gt;
==Tips==&lt;br /&gt;
* E2E tests (e.g. Cypress) are very important&lt;br /&gt;
* Use tools for code formatting, linting, type-hints&lt;br /&gt;
* Configure these tools to produce minimum output to reduce token consumption. e.g. for&lt;br /&gt;
** Prettier:  --log-level silent&lt;br /&gt;
** Cypress: cypress run --e2e --quiet&lt;br /&gt;
* For Refactoring/renaming: better do it manually, via vscode, that is a lot faster&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Python&amp;diff=5397</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5397"/>
		<updated>2026-02-17T08:26:01Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Process Bar */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Coding]][[Category:Python]]&lt;br /&gt;
&lt;br /&gt;
==Getting Started==&lt;br /&gt;
===Install===&lt;br /&gt;
====Python====&lt;br /&gt;
Best use [https://docs.astral.sh/uv/ UV] for managing python and package versions.&lt;br /&gt;
&lt;br /&gt;
* for Windows: get and install Python from https://www.python.org&lt;br /&gt;
* for MacOS: use [https://github.com/pyenv/pyenv?tab=readme-ov-file#a-getting-pyenv pyenv]&lt;br /&gt;
 brew install xz &lt;br /&gt;
 brew install pyenv&lt;br /&gt;
 &lt;br /&gt;
 echo &#039;export PYENV_ROOT=&amp;quot;$HOME/.pyenv&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 echo &#039;[ [ -d $PYENV_ROOT/bin ] ] &amp;amp;&amp;amp; export PATH=&amp;quot;$PYENV_ROOT/bin:$PATH&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 echo &#039;eval &amp;quot;$(pyenv init - zsh)&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
 exec &amp;quot;$SHELL&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 pyenv install --list&lt;br /&gt;
 pyenv install 3.13.5&lt;br /&gt;
 pyenv global 3.13.5&lt;br /&gt;
 &lt;br /&gt;
 vim ~/.zshrc &lt;br /&gt;
 # add &lt;br /&gt;
 if command -v pyenv 1&amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then&lt;br /&gt;
   eval &amp;quot;$(pyenv init -)&amp;quot;&lt;br /&gt;
 fi&lt;br /&gt;
&lt;br /&gt;
====Editor: Visual Studio Code====&lt;br /&gt;
excellent and free source-code editor that supports many languages.&lt;br /&gt;
* get and install from https://code.visualstudio.com&lt;br /&gt;
&lt;br /&gt;
See Wickie page [[Visual_Studio_Code]] for general setup, extension, config...&lt;br /&gt;
&lt;br /&gt;
Python Extensions&lt;br /&gt;
* Python&lt;br /&gt;
* Pylance&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff Ruff Formater and Linter]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items/?itemName=tamasfe.even-better-toml Even Better TOML]&lt;br /&gt;
Settings (CTRL + ,)&lt;br /&gt;
* Extensions -&amp;gt; Python -&amp;gt; Formatting: Provider = black&lt;br /&gt;
* Text Editor -&amp;gt; Editor: Format On Save&lt;br /&gt;
* Text Editor -&amp;gt; Files:Eol -&amp;gt; \n&lt;br /&gt;
* Linter: Ruff&lt;br /&gt;
Settings in settings.json&lt;br /&gt;
 &amp;quot;python.analysis.completeFunctionParens&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.autoImportCompletions&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.inlayHints.functionReturnTypes&amp;quot;: true,&lt;br /&gt;
 &amp;quot;python.analysis.typeCheckingMode&amp;quot;: &amp;quot;strict&amp;quot;,&lt;br /&gt;
 // Ruff&lt;br /&gt;
 &amp;quot;[python]&amp;quot;: {&lt;br /&gt;
   &amp;quot;editor.defaultFormatter&amp;quot;: &amp;quot;charliermarsh.ruff&amp;quot;,&lt;br /&gt;
   &amp;quot;editor.formatOnSave&amp;quot;: true,&lt;br /&gt;
   &amp;quot;editor.codeActionsOnSave&amp;quot;: {&lt;br /&gt;
     &amp;quot;source.fixAll&amp;quot;: &amp;quot;explicit&amp;quot;,&lt;br /&gt;
     &amp;quot;source.organizeImports.ruff&amp;quot;: &amp;quot;explicit&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 },&lt;br /&gt;
&lt;br /&gt;
Run your code&lt;br /&gt;
 CTRL + F5 : run&lt;br /&gt;
 F5        : run in debugger&lt;br /&gt;
&lt;br /&gt;
====Code Formatter Ruff====&lt;br /&gt;
use software for handling the code formatting like &amp;quot;ruff&amp;quot; or &amp;quot;black&amp;quot;, where Ruff is much faster and additionally provides linting.&lt;br /&gt;
 pip install ruff&lt;br /&gt;
than activate in editor like vs code&lt;br /&gt;
&lt;br /&gt;
===My Template===&lt;br /&gt;
see latest version at https://github.com/entorb/template-python&lt;br /&gt;
&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Main file.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 # by Torben Menke https://entorb.net &lt;br /&gt;
 import logging&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 def init_logging() -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Initialize logging.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     logging.addLevelName(logging.DEBUG, &amp;quot;D&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.INFO, &amp;quot;I&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.WARNING, &amp;quot;W&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.ERROR, &amp;quot;E&amp;quot;)&lt;br /&gt;
     logging.addLevelName(logging.CRITICAL, &amp;quot;C&amp;quot;)&lt;br /&gt;
     logging.basicConfig(&lt;br /&gt;
         level=logging.INFO, format=&amp;quot;%(levelname)s: %(name)s: %(message)s&amp;quot;&lt;br /&gt;
     )&lt;br /&gt;
     logging.getLogger(&amp;quot;httpx&amp;quot;).setLevel(logging.WARNING)&lt;br /&gt;
 &lt;br /&gt;
 init_logging()&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
 &lt;br /&gt;
 def main():&lt;br /&gt;
     LOGGER.info(&amp;quot;Moin Moin&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     main()&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Other file&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 import logging&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
&lt;br /&gt;
===Compile to .exe===&lt;br /&gt;
 pip install pyinstaller&lt;br /&gt;
 pyinstaller --onefile --console your.py&lt;br /&gt;
 # for Excel and Matplotlib these options are required&lt;br /&gt;
 --hidden-import=openpyxl --hidden-import=matplotlib --hidden-import pandas.plotting._matplotlib &lt;br /&gt;
([[Python - py2exe]] is deprecated)&lt;br /&gt;
&lt;br /&gt;
==Basics==&lt;br /&gt;
=== Naming Conventions===&lt;br /&gt;
[https://google.github.io/styleguide/pyguide.html Google Python Style Guide]:&lt;br /&gt;
 module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_CONSTANT_NAME, global_var_name, instance_var_name, function_parameter_name, local_var_name&lt;br /&gt;
&lt;br /&gt;
====Doc Strings====&lt;br /&gt;
It is best practice to start a file with a docstring:&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;My Script&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
This can be accessed linke this:&lt;br /&gt;
 title = __doc__[:-1]  # type: ignore&lt;br /&gt;
&lt;br /&gt;
===Installing packages===&lt;br /&gt;
 python -m pip install --upgrade pip&lt;br /&gt;
 &lt;br /&gt;
 pip install somemodule&lt;br /&gt;
 # or &lt;br /&gt;
 pip3 install somemodule&lt;br /&gt;
 # or read from file&lt;br /&gt;
 pip install -r requirements.txt&lt;br /&gt;
 &lt;br /&gt;
 # uninstall&lt;br /&gt;
 pip uninstall somemodule&lt;br /&gt;
 &lt;br /&gt;
 # using a web proxy&lt;br /&gt;
 # set proxy for windows cmd session&lt;br /&gt;
 SET HTTPS_PROXY=http://myProxy:8080&lt;br /&gt;
 (afterwards --proxy setting below no longer required&lt;br /&gt;
 or&lt;br /&gt;
 pip install --proxy http://myProxy:8080 somemodule&lt;br /&gt;
 &lt;br /&gt;
 # list outdated packages&lt;br /&gt;
 pip list --outdated&lt;br /&gt;
 &lt;br /&gt;
 # update package&lt;br /&gt;
 pip install --upgrade pyinstaller&lt;br /&gt;
 &lt;br /&gt;
 # updating all via Windows Powershell (from [https://thecesrom.dev/2021/05/26/the-one-liner-for-updating-pip-and-all-outdated-packages/])&lt;br /&gt;
 pip freeze | %{$_.split(&#039;==&#039;)[0]} | %{pip install --upgrade $_}&lt;br /&gt;
 &lt;br /&gt;
 # updating all via Bash (from [https://thecesrom.dev/2021/05/26/the-one-liner-for-updating-pip-and-all-outdated-packages/])&lt;br /&gt;
 pip freeze | grep -v &#039;^\-e&#039; | cut -d = -f 1  | xargs -n1 pip install --upgrade&lt;br /&gt;
 &lt;br /&gt;
 # downgrade&lt;br /&gt;
 pip install --upgrade pandas==1.2.4&lt;br /&gt;
&lt;br /&gt;
====Oneline Updater for requirements.txt====&lt;br /&gt;
https://pkgui.com/pip&lt;br /&gt;
&lt;br /&gt;
====update packages====&lt;br /&gt;
 pip install pip-review&lt;br /&gt;
 pip-review --auto&lt;br /&gt;
&lt;br /&gt;
====generate requirements.txt====&lt;br /&gt;
 pip install pipreqs&lt;br /&gt;
 pipreqs ./&lt;br /&gt;
&lt;br /&gt;
===Loops===&lt;br /&gt;
ATTENTION: The loops do not create a new variable scope. Only functions and modules introduce a new scope!&lt;br /&gt;
 for p in all_files:&lt;br /&gt;
     print(p)&lt;br /&gt;
 &lt;br /&gt;
 for i, p in enumerate(all_files):&lt;br /&gt;
     print(i, p)&lt;br /&gt;
 &lt;br /&gt;
 for i in range(10):&lt;br /&gt;
     print(i)&lt;br /&gt;
 # del i &lt;br /&gt;
 &lt;br /&gt;
 while i &amp;lt;= 100:&lt;br /&gt;
     i += 1&lt;br /&gt;
     ...&lt;br /&gt;
     if sth1:&lt;br /&gt;
         continue # start next loop&lt;br /&gt;
     if sth2:&lt;br /&gt;
         break # exit loop&lt;br /&gt;
 &lt;br /&gt;
 # inline if (requires a dummy else):&lt;br /&gt;
 print(&amp;quot;something&amp;quot;) if sth else 0&lt;br /&gt;
&lt;br /&gt;
===Math===&lt;br /&gt;
see [[Python - Math]] for linear regression&lt;br /&gt;
&lt;br /&gt;
Modulo&lt;br /&gt;
 15 % 4&lt;br /&gt;
 # &amp;gt; 3&lt;br /&gt;
&lt;br /&gt;
Integer Division&lt;br /&gt;
 17 // 4&lt;br /&gt;
 # &amp;gt; 4&lt;br /&gt;
&lt;br /&gt;
====Random====&lt;br /&gt;
 import random&lt;br /&gt;
 random.randint(1000000, 9999999)&lt;br /&gt;
&lt;br /&gt;
==Basic Objects==&lt;br /&gt;
===Variables===&lt;br /&gt;
 del var  # delete / undef a variable&lt;br /&gt;
 var = None  # sets to null&lt;br /&gt;
 &lt;br /&gt;
 # check if variable is defined&lt;br /&gt;
 if &amp;quot;var&amp;quot; in locals():&lt;br /&gt;
     pass&lt;br /&gt;
 # for object oriented projects:&lt;br /&gt;
 if &amp;quot;var&amp;quot; in self.__dict__:&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # Access global variables in functions&lt;br /&gt;
 var = 123&lt;br /&gt;
 &lt;br /&gt;
 def test() -&amp;gt; None:&lt;br /&gt;
     global var  # point to global instead of creation of local var&lt;br /&gt;
     var = 321&lt;br /&gt;
&lt;br /&gt;
===Strings===&lt;br /&gt;
 # num &amp;lt;-&amp;gt; str&lt;br /&gt;
 s = str(i)  # int to string&lt;br /&gt;
 f = float(s)  # str -&amp;gt; float&lt;br /&gt;
 i = int(s)&lt;br /&gt;
 str(round(f, 1))  # round first&lt;br /&gt;
 # tests&lt;br /&gt;
 s.isdigit()  # 0-9&lt;br /&gt;
 # note isdecimal() does also not match &#039;1.1&#039;&lt;br /&gt;
 &lt;br /&gt;
 # printf: 1 digit&lt;br /&gt;
 s = f&amp;quot;{value:0.1f}&amp;quot;&lt;br /&gt;
 s = &amp;quot;%0.1f&amp;quot; % value&lt;br /&gt;
&lt;br /&gt;
====Modify Strings==== &lt;br /&gt;
 # get string from prompt&lt;br /&gt;
 s = input(&amp;quot;Enter Text: &amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 s = s.strip()  # trim spaces from both sides, rstrip for right only&lt;br /&gt;
 s = s.lower()  # lower case&lt;br /&gt;
 s = s.upper()  # upper case&lt;br /&gt;
 s = s.title()  # upper case for first char of word&lt;br /&gt;
 &lt;br /&gt;
 # upper case first letter of each word and also removes multiple and trailing spaces&lt;br /&gt;
 import string&lt;br /&gt;
 s = string.capwords(s)&lt;br /&gt;
 &lt;br /&gt;
 # replace&lt;br /&gt;
 s.replace(x, y)&lt;br /&gt;
 &lt;br /&gt;
 # trim whitespaces from left and right&lt;br /&gt;
 s.strip()&lt;br /&gt;
 &lt;br /&gt;
 # replace all (multiple) whitespaces by single space &#039; &#039;&lt;br /&gt;
 s = &amp;quot; &amp;quot;.join(s.split())&lt;br /&gt;
 &lt;br /&gt;
 # generate key value pairs from dict&lt;br /&gt;
 # key1=value1&amp;amp;key2=value2&lt;br /&gt;
 param_str = &amp;quot;&amp;amp;&amp;quot;.join(&amp;quot;=&amp;quot;.join(tup) for tup in dict.items())&lt;br /&gt;
 &lt;br /&gt;
 # repeat string multiple times&lt;br /&gt;
 s * 5  # = s+s+s+s+s&lt;br /&gt;
&lt;br /&gt;
====Substrings====&lt;br /&gt;
 # find a substring:&lt;br /&gt;
 if x in s:  # True / False&lt;br /&gt;
     pass&lt;br /&gt;
 if len(s) &amp;gt; 0:&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # handling substrings&lt;br /&gt;
 a = &amp;quot;abcd&amp;quot;&lt;br /&gt;
 b = a[:1] + &amp;quot;o&amp;quot; + a[2:]&lt;br /&gt;
 # &amp;gt; &#039;aocd&#039;&lt;br /&gt;
 &lt;br /&gt;
 s = &amp;quot;Hello there !bob@&amp;quot;&lt;br /&gt;
 i1 = s.find(&amp;quot;!&amp;quot;) + 1&lt;br /&gt;
 i2 = s.find(&amp;quot;@&amp;quot;)&lt;br /&gt;
 substr = s[i1:i2]&lt;br /&gt;
 &lt;br /&gt;
 def substr_between(s: str, s1: str, s2: str) -&amp;gt; str:&lt;br /&gt;
     assert s1 in s, f&amp;quot;E: can&#039;t find &#039;{s1}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     assert s2 in s, f&amp;quot;E: can&#039;t find &#039;{s1}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     i1 = s.find(s1) + len(s1)&lt;br /&gt;
     i2 = s.find(s2)&lt;br /&gt;
     assert i1 &amp;lt; i2, f&amp;quot;E: &#039;{s1}&#039; not before &#039;{s2}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     return s[i1:i2]&lt;br /&gt;
&lt;br /&gt;
====Binary, raw strings, html encoding====&lt;br /&gt;
 # Binary Strings&lt;br /&gt;
 sb = b&amp;quot;asdf&amp;quot;&lt;br /&gt;
 # or&lt;br /&gt;
 sb = str.encode(&amp;quot;asdf&amp;quot;)&lt;br /&gt;
 s = sb.decode(&amp;quot;ascii&amp;quot;)  # decode binary strings&lt;br /&gt;
 sb = s.encode(&amp;quot;ascii&amp;quot;)  # encode string to binary&lt;br /&gt;
 &lt;br /&gt;
 # raw string&lt;br /&gt;
 s = r&amp;quot;c:\Windows&amp;quot;  # no escape of \  needed&lt;br /&gt;
 &lt;br /&gt;
 # convert utf-8 to html umlaute&lt;br /&gt;
 s = &amp;quot;Nürnberg&amp;quot;.encode(&amp;quot;ascii&amp;quot;, &amp;quot;xmlcharrefreplace&amp;quot;).decode()&lt;br /&gt;
 # -&amp;gt; N&amp;amp;#252;rnberg&lt;br /&gt;
&lt;br /&gt;
====Guess encoding via chardet====&lt;br /&gt;
 import chardet  # pip install chardet&lt;br /&gt;
 result = chardet.detect(raw_data)&lt;br /&gt;
 if result[&amp;quot;encoding&amp;quot;] and result[&amp;quot;confidence&amp;quot;] &amp;gt; 0.5:  # noqa: PLR2004&lt;br /&gt;
     encoding = result[&amp;quot;encoding&amp;quot;]&lt;br /&gt;
 else:&lt;br /&gt;
     encoding = &amp;quot;utf-8&amp;quot;&lt;br /&gt;
&lt;br /&gt;
there is a much faster alternative [https://github.com/PyYoshi/cChardet cchardet], but that requires Microsoft Visual C++ 14.0&lt;br /&gt;
&lt;br /&gt;
====Merge variables in string / sprintf====&lt;br /&gt;
 print(&amp;quot;Renner =&amp;quot;, i)&lt;br /&gt;
 print(&amp;quot;Renner = %3d&amp;quot; % i)  # leading 0&#039;s&lt;br /&gt;
 print(f&amp;quot;Renner = {i}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # place formatted numbers in a string / sprintf&lt;br /&gt;
 s = &amp;quot;The {:.1f}% {} cost {:05.2f} euros&amp;quot;.format( 5.1, &amp;quot;beer&amp;quot;, 3.50)&lt;br /&gt;
 print(s)&lt;br /&gt;
 # &amp;gt; The 5.1% beer cost 03.50 euros&lt;br /&gt;
 &lt;br /&gt;
 s = f&amp;quot;The length is {72.8958:.2f} meters&amp;quot;&lt;br /&gt;
 # &amp;gt; The length is 72.90 meters&lt;br /&gt;
&lt;br /&gt;
===Lists===&lt;br /&gt;
like @array in Perl&lt;br /&gt;
 lst = [1, 2, 3, 4, 5, 6]&lt;br /&gt;
 lst = [x / 2 for x in range(10)]&lt;br /&gt;
 lst = [None] * 10 # Initiate list of None elements:&lt;br /&gt;
 len(lst) # length&lt;br /&gt;
 lst2 = lst[0:10]  # get elements 0-10&lt;br /&gt;
 for i in lst:&lt;br /&gt;
     print(i)&lt;br /&gt;
&lt;br /&gt;
====clone list====&lt;br /&gt;
 # dangerous: creates a link to the original list&lt;br /&gt;
 list_2 = my_list  # M&#039;s elements are LINKS to L&#039;s&lt;br /&gt;
 # clones can be achieved via:&lt;br /&gt;
 list_2 = my_list.copy&lt;br /&gt;
 list_2 = my_list[:]&lt;br /&gt;
 list_2 = list(my_list)&lt;br /&gt;
&lt;br /&gt;
====list modifications====&lt;br /&gt;
 lst.pop()  # returns and removes the last item&lt;br /&gt;
 lst.pop(i)  # returns and removes the item at  position int i&lt;br /&gt;
 lst.insert(i, x)  # insert item x at position int i&lt;br /&gt;
 lst.remove(x)  # removes the first occurrence of  item x&lt;br /&gt;
 lst.append(x)  # append a single element&lt;br /&gt;
 lst.extend(m)  # append elements of another list&lt;br /&gt;
 &lt;br /&gt;
 lst.reverse()  # reverse the order of the list&lt;br /&gt;
 lst = sorted([&amp;quot;B&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;c&amp;quot;], key=str.casefold)  #  case insensitive / ignore case&lt;br /&gt;
 &lt;br /&gt;
 # list to string&lt;br /&gt;
 s = &amp;quot;&amp;quot;.join(lst)&lt;br /&gt;
 s = &amp;quot;\n&amp;quot;.join(lst)&lt;br /&gt;
 # string to list&lt;br /&gt;
 lst = s.split()  # default: split on space&lt;br /&gt;
 lst = s.split(&amp;quot;,&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # remove empty values from end&lt;br /&gt;
 while L[-1] == &amp;quot;&amp;quot;:&lt;br /&gt;
     L.pop()&lt;br /&gt;
 &lt;br /&gt;
 # check and remove items from list&lt;br /&gt;
  # from https://stackoverflow.com/a/6024599&lt;br /&gt;
  # iterates in-situ via reversed index&lt;br /&gt;
 for i in range(len(lst) - 1, -1, -1):&lt;br /&gt;
     element = lst[i]&lt;br /&gt;
     if check(element):&lt;br /&gt;
         del lst[i]&lt;br /&gt;
&lt;br /&gt;
====check if item is in list====&lt;br /&gt;
 x in lst&lt;br /&gt;
 x not in lst&lt;br /&gt;
 lst.count(x)  # how many items x are in the list&lt;br /&gt;
 i = lst.index(&amp;quot;word&amp;quot;)  # find in list / returns the  position of the first match in list&lt;br /&gt;
&lt;br /&gt;
====pair 2 lists====&lt;br /&gt;
 # zip: merge 2 lists to list of tuples&lt;br /&gt;
 data = list(zip(data_x, data_y, strict=True))&lt;br /&gt;
 &lt;br /&gt;
 # unzip: split list of pairs into 2 lists&lt;br /&gt;
 data_x, data_y = zip(*data, strict=True)&lt;br /&gt;
 &lt;br /&gt;
 # Cartesian product of lists / tuples&lt;br /&gt;
 import itertools&lt;br /&gt;
 for i in itertools.product(*list_of_lists):&lt;br /&gt;
     print(i)&lt;br /&gt;
&lt;br /&gt;
====sort multidim list====&lt;br /&gt;
 lst = sorted(lst, key=lambda x: x[0], reverse=False)&lt;br /&gt;
 lst = sorted(lst, key=lambda row: (row[&amp;quot;Wann&amp;quot;], row [&amp;quot;Wer&amp;quot;]), reverse=False)&lt;br /&gt;
&lt;br /&gt;
====filter list====&lt;br /&gt;
 lines = [x for x in lines if not x.startswith (&amp;quot;word&amp;quot;)]&lt;br /&gt;
 lst = [&amp;quot;asdf&amp;quot;, &amp;quot;asdf2&amp;quot;, &amp;quot;qwertz&amp;quot;]&lt;br /&gt;
 lst = [elem for elem in lst if &amp;quot;asdf&amp;quot; in elem]&lt;br /&gt;
&lt;br /&gt;
====for each element====&lt;br /&gt;
 # trim/strip spaces for each element&lt;br /&gt;
 lines = [s.strip() for s in lines]&lt;br /&gt;
 # modify each item in list by adding constant string&lt;br /&gt;
 l = [s + &#039;;&#039; + v for v in l]&lt;br /&gt;
  &lt;br /&gt;
 # modify item in list&lt;br /&gt;
 for idx, line in enumerate(cont):&lt;br /&gt;
     if &amp;quot;K1001/1&amp;quot; in line:&lt;br /&gt;
         line = &amp;quot;K1001/1 Test Nr &amp;quot; + str(i) + &amp;quot;\n&amp;quot;&lt;br /&gt;
         cont[idx] = line&lt;br /&gt;
         break&lt;br /&gt;
&lt;br /&gt;
===Tuples===&lt;br /&gt;
Ordered sequence, with no ability to replace or delete items&lt;br /&gt;
 tpl = (1,2,3,4,5,6)&lt;br /&gt;
&lt;br /&gt;
list -&amp;gt; tuple&lt;br /&gt;
 tpl = tuple(lst)&lt;br /&gt;
&lt;br /&gt;
combine 2 tuples&lt;br /&gt;
 tpl = tpl1 + tpl2&lt;br /&gt;
&lt;br /&gt;
===Dictionaries===&lt;br /&gt;
like %hash in Perl&lt;br /&gt;
 d = {&amp;quot;key1&amp;quot;: &amp;quot;value1&amp;quot;, &amp;quot;key2&amp;quot;: 123}&lt;br /&gt;
 d[&amp;quot;key3&amp;quot;] = (1, 2, 3)&lt;br /&gt;
 del d[&amp;quot;key3&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
 len(d)&lt;br /&gt;
 d.clear()&lt;br /&gt;
 d.copy()&lt;br /&gt;
 d.keys()&lt;br /&gt;
 d.values()&lt;br /&gt;
 d.items()  # returns a list of tuples (key, value)&lt;br /&gt;
 d.get(k)  # returns value of key k&lt;br /&gt;
 d.get(k, x)  # returns value of key k; if k is not  in d it returns x&lt;br /&gt;
 count = d.get(&amp;quot;key&amp;quot;, 0) + 1 # nice for counters&lt;br /&gt;
 d.pop(k)  # returns and removes item k&lt;br /&gt;
 d.pop(k, x)  # returns and removes item k; if k is  not in d it returns x&lt;br /&gt;
 # checks&lt;br /&gt;
 x in d&lt;br /&gt;
 x not in d&lt;br /&gt;
 &lt;br /&gt;
 # dict can have tuple as key&lt;br /&gt;
 tpl = (&amp;quot;key1&amp;quot;, &amp;quot;key2&amp;quot;, 123)&lt;br /&gt;
 d[tpl] = 123456&lt;br /&gt;
 &lt;br /&gt;
 # loop over all key-value pairs&lt;br /&gt;
 for key, value in d.items():&lt;br /&gt;
     print(f&amp;quot;{key} = {value}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # sorted keys:&lt;br /&gt;
 for key in sorted(dict.keys()):&lt;br /&gt;
     pass&lt;br /&gt;
 # sorted values, reversed&lt;br /&gt;
 for key, value in sorted(d.items(), key=lambda item:  item[1], reverse=True):&lt;br /&gt;
     pass&lt;br /&gt;
 &lt;br /&gt;
 # join / merge 2 dicts&lt;br /&gt;
 d.update(d2)&lt;br /&gt;
 &lt;br /&gt;
 # flatten dict of dict&lt;br /&gt;
 flat = d.copy()&lt;br /&gt;
 meta = d.pop(&amp;quot;metadata&amp;quot;)&lt;br /&gt;
 flat.update(meta)&lt;br /&gt;
&lt;br /&gt;
====MultiDim Dictionaries====&lt;br /&gt;
 d = {}&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;] = {}&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;][&amp;quot;value&amp;quot;] = 1.909e18&lt;br /&gt;
 d[&amp;quot;item1&amp;quot;][&amp;quot;name&amp;quot;] = &amp;quot;Item 1&amp;quot;&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;] = {}&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;][&amp;quot;value&amp;quot;] = 1.725e18&lt;br /&gt;
 d[&amp;quot;item2&amp;quot;][&amp;quot;name&amp;quot;] = &amp;quot;Item 2&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 for k in d.keys():&lt;br /&gt;
     d[k][&amp;quot;value&amp;quot;] = d[k][&amp;quot;value&amp;quot;] * 1.1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 def find_common_strings(dict1: dict, dict2: dict) -&amp;gt; dict[Any, Any]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Find common strings in two dict, returning a new dict of those strings.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     def recursive_match(d1: dict, d2: dict) -&amp;gt; dict[Any, Any]:&lt;br /&gt;
         common_dict = {}&lt;br /&gt;
         for key in d1:  # noqa: PLC0206&lt;br /&gt;
             if key in d2:&lt;br /&gt;
                 if isinstance(d1[key], dict) and isinstance(d2[key], dict):&lt;br /&gt;
                     # Recurse if both values are dicts&lt;br /&gt;
                     nested_common = recursive_match(d1[key], d2[key])&lt;br /&gt;
                     if nested_common:  # Add to result if common values are found&lt;br /&gt;
                         common_dict[key] = nested_common&lt;br /&gt;
                 elif isinstance(d1[key], str) and isinstance(d2[key], str):&lt;br /&gt;
                     # Check if both are strings and identical&lt;br /&gt;
                     if d1[key] == d2[key]:&lt;br /&gt;
                         common_dict[key] = d1[key]&lt;br /&gt;
         return common_dict&lt;br /&gt;
 &lt;br /&gt;
     return recursive_match(dict1, dict2)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def update_dict_with_new_values(original_dict: dict, new_values: dict) -&amp;gt; dict:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Update strings in the original dict with new values from the new_values dict.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     def recursive_update(d1: dict, new_vals: dict) -&amp;gt; None:&lt;br /&gt;
         for key in new_vals:  # noqa: PLC0206&lt;br /&gt;
             if key in d1:&lt;br /&gt;
                 if isinstance(d1[key], dict) and isinstance(new_vals[key], dict):&lt;br /&gt;
                     # Recurse if both values are dicts&lt;br /&gt;
                     recursive_update(d1[key], new_vals[key])&lt;br /&gt;
                 elif isinstance(d1[key], str) and isinstance(new_vals[key], str):&lt;br /&gt;
                     # Update the string&lt;br /&gt;
                     d1[key] = new_vals[key]&lt;br /&gt;
 &lt;br /&gt;
     recursive_update(original_dict, new_values)&lt;br /&gt;
     return original_dict&lt;br /&gt;
&lt;br /&gt;
===Datetime, Date and Time===&lt;br /&gt;
 import datetime as dt&lt;br /&gt;
 from zoneinfo import ZoneInfo&lt;br /&gt;
 TZ_DE = ZoneInfo(&amp;quot;Europe/Berlin&amp;quot;)&lt;br /&gt;
 TZ_UTC = ZoneInfo(&amp;quot;UTC&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
====Create====&lt;br /&gt;
 # date&lt;br /&gt;
 date = dt.date(2023, 12, 31)&lt;br /&gt;
 date = dt.date.fromisocalendar(int(year), int(week),  int(daynum))  # daynum: 1..7&lt;br /&gt;
 date = dt.date.fromisoformat(&amp;quot;2020-03-10&amp;quot;)&lt;br /&gt;
 date = dt.datetime.strptime(datestr, &amp;quot;%y%m%d&amp;quot;).date()&lt;br /&gt;
 date_today = dt.datetime.now(tz=dt.UTC).date()&lt;br /&gt;
 date_yesterday = dt.datetime.now(tz=TZ_DE).date() -  dt.timedelta(days=1)&lt;br /&gt;
 days_overdue = (date_completed - date_due).days&lt;br /&gt;
 # to add one month (which is not supported by timedelta) &lt;br /&gt;
 from dateutil.relativedelta import relativedelta&lt;br /&gt;
 date_end = date_start + relativedelta(months=1)&lt;br /&gt;
 delta_days = 1 + (end - start).days&lt;br /&gt;
 &lt;br /&gt;
 # datetime&lt;br /&gt;
 dt_now = dt.datetime.now(tz=TZ_DE)&lt;br /&gt;
 my_dt = dt.datetime(2023, 12, 31, 14, 31, 56,  tzinfo=TZ_DE)  # 2023-12-31 14:31:56&lt;br /&gt;
 my_dt = dt.datetime.fromtimestamp(my_timestamp,  tz=TZ_DE)&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat (&amp;quot;2017-01-01T12:30:59.000000&amp;quot;)&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(&amp;quot;2020-03-10  06:01:01+00:00&amp;quot;)&lt;br /&gt;
 s = &amp;quot;2020-03-10T06:01:01Z&amp;quot;&lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(s.replace(&amp;quot;Z&amp;quot;, &amp;quot; +00:00&amp;quot;))&lt;br /&gt;
 my_date_local = my_dt.astimezone(tz=TZ_DE).date()&lt;br /&gt;
 &lt;br /&gt;
 # datetime -&amp;gt; date&lt;br /&gt;
 my_date = my_dt.date()&lt;br /&gt;
 # date -&amp;gt; datetime&lt;br /&gt;
 my_date = dt.date(2023, 12, 31)&lt;br /&gt;
 my_dt = dt.datetime(my_date.year, my_date.month,  my_date.day, tzinfo=TZ_DE)&lt;br /&gt;
 &lt;br /&gt;
 # to string&lt;br /&gt;
 date_str = my_dt.strftime(&amp;quot;%y%m%d&amp;quot;)&lt;br /&gt;
 date_str = my_dt.strftime(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;)&lt;br /&gt;
 # alternative using time&lt;br /&gt;
 date_str = time.strftime(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;)&lt;br /&gt;
 # now in UTC without milliseconds&lt;br /&gt;
 date_str = dt.datetime.now(tz=dt.timezone.utc).replace(microsecond=0).isoformat() + &amp;quot;Z&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # remove timezone&lt;br /&gt;
 my_dt = my_dt.replace(tzinfo=None)&lt;br /&gt;
 &lt;br /&gt;
 # German format&lt;br /&gt;
 import locale&lt;br /&gt;
 locale.setlocale(locale.LC_ALL, &amp;quot;de_DE&amp;quot;)&lt;br /&gt;
 print(my_date.strftime(&amp;quot;%a %x&amp;quot;))  # Mo 25.12.2023&lt;br /&gt;
 &lt;br /&gt;
 # Calendar week&lt;br /&gt;
 week = my_date.isocalendar()[1]&lt;br /&gt;
 print(&amp;quot;KW%02d&amp;quot; % week)&lt;br /&gt;
 &lt;br /&gt;
 # first day of quarter&lt;br /&gt;
 def get_first_day_of_the_quarter(my_date: dt.date) -&amp;gt; dt.date:&lt;br /&gt;
     return dt.date(my_date.year, 1 + 3 * ((my_date.month - 1) // 3), 1)&lt;br /&gt;
&lt;br /&gt;
====rounding datetimes to minutes====&lt;br /&gt;
 def floor_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Floor (=round down) minutes to X min  resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     min_new = res * (my_dt.minute // res)&lt;br /&gt;
     return my_dt.replace(minute=min_new, second=0,  microsecond=0)&lt;br /&gt;
 &lt;br /&gt;
 def ceil_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Ceil (=round up) minutes to X min resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    min_new = res * (1 + my_dt.minute // res)&lt;br /&gt;
    return my_dt.replace(minute=0, second=0, microsecond=0) + dt.timedelta(&lt;br /&gt;
        minutes=min_new&lt;br /&gt;
    )&lt;br /&gt;
 &lt;br /&gt;
 def round_dt_minutes(my_dt: dt.datetime, res: int =  5) -&amp;gt; dt.datetime:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Cound minutes to X min resolution.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    min_old_dec = float(my_dt.minute) + float(my_dt.second * 60)&lt;br /&gt;
    min_new = res * round(min_old_dec / res)&lt;br /&gt;
    return my_dt.replace(minute=0, second=0, microsecond=0) + dt.timedelta(&lt;br /&gt;
        minutes=min_new&lt;br /&gt;
    )&lt;br /&gt;
 &lt;br /&gt;
 my_dt = dt.datetime.fromisoformat(&amp;quot;2011-11-04  00:05:23+00:00&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;original: {my_dt}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;floored: {floor_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;ceileded: {ceil_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
 print(f&amp;quot;rounded: {round_dt_minutes(my_dt,5)}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
====Timing / Time elapsed====&lt;br /&gt;
 import time &lt;br /&gt;
 timestart = time.time()&lt;br /&gt;
 sec = time.time() - timestart&lt;br /&gt;
 print(f&amp;quot;Time elapsed: {sec:.2f} sec&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==OS, Argpars, etc.==&lt;br /&gt;
===Hostname===&lt;br /&gt;
 from socket import gethostname&lt;br /&gt;
 print(gethostname())&lt;br /&gt;
&lt;br /&gt;
===Checking Operating System===&lt;br /&gt;
 import os&lt;br /&gt;
 import sys&lt;br /&gt;
  &lt;br /&gt;
 if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
     print(&amp;quot;posix/Unix/Linux&amp;quot;)&lt;br /&gt;
 elif os.name == &amp;quot;nt&amp;quot;:&lt;br /&gt;
     print(&amp;quot;windows&amp;quot;)&lt;br /&gt;
 else:&lt;br /&gt;
     print(&amp;quot;unknown os&amp;quot;)&lt;br /&gt;
     sys.exit(1)  # throws exception, use quit() to   close / die silently&lt;br /&gt;
&lt;br /&gt;
===Get filename of python script===&lt;br /&gt;
 my_file_path = __file__&lt;br /&gt;
&lt;br /&gt;
alternative using sys package&lt;br /&gt;
 from sys import argv&lt;br /&gt;
 myFilename = argv[0]&lt;br /&gt;
&lt;br /&gt;
===accessing os envrionment variables===&lt;br /&gt;
 import os&lt;br /&gt;
 print(os.getenv(&amp;quot;tmp&amp;quot;))&lt;br /&gt;
 # better:&lt;br /&gt;
 try:&lt;br /&gt;
     s = os.environ[key] # throws error if unset&lt;br /&gt;
 except KeyError:&lt;br /&gt;
     error_message(f&amp;quot;Environment variable {key} not set.&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Command Line Arguments===&lt;br /&gt;
====ArgumentParser====&lt;br /&gt;
 import argparse&lt;br /&gt;
 &lt;br /&gt;
 parser = (&lt;br /&gt;
     argparse.ArgumentParser()&lt;br /&gt;
 )  # construct the argument parser and parse the  arguments&lt;br /&gt;
 # -h comes automatically&lt;br /&gt;
 &lt;br /&gt;
 # Boolean Parameter&lt;br /&gt;
 parser.add_argument(&lt;br /&gt;
     &amp;quot;-v&amp;quot;, &amp;quot;--verbose&amp;quot;, help=&amp;quot;increase output  verbosity&amp;quot;, action=&amp;quot;store_true&amp;quot;&lt;br /&gt;
 )  # store_true -&amp;gt; Boolean Value&lt;br /&gt;
 &lt;br /&gt;
 # Choice Parameter&lt;br /&gt;
 # restrict to a list of possible values / choices&lt;br /&gt;
 # parser.add_argument(&amp;quot;--choice&amp;quot;, type=int, choices= [0, 1, 2], help=&amp;quot;Test choices&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Positional Parameter (like text.py 123)&lt;br /&gt;
 # parser.add_argument(&amp;quot;num&amp;quot;, type=int, help=&amp;quot;Number  of things&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Required Parameter&lt;br /&gt;
 # parser.add_argument(&amp;quot;-i&amp;quot;, &amp;quot;--input&amp;quot;, type=str,  required=True, help=&amp;quot;Path of file&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # Optional Parameter&lt;br /&gt;
 parser.add_argument(&amp;quot;-n&amp;quot;, &amp;quot;--number&amp;quot;, type=int,  help=&amp;quot;Number of clicks&amp;quot;)&lt;br /&gt;
 # Optional Parameter with Default&lt;br /&gt;
 parser.add_argument(&lt;br /&gt;
     &amp;quot;-s&amp;quot;,&lt;br /&gt;
     &amp;quot;--seconds&amp;quot;,&lt;br /&gt;
     type=int,&lt;br /&gt;
     default=sec_default,&lt;br /&gt;
     help=&amp;quot;Duration of clicking, default = %i (sec)&amp;quot;  % sec_default,&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 args = vars(parser.parse_args())&lt;br /&gt;
 &lt;br /&gt;
 if args[&amp;quot;verbose&amp;quot;]:&lt;br /&gt;
     pass  # do nothing&lt;br /&gt;
 # print (&amp;quot;verbosity turned on&amp;quot;)&lt;br /&gt;
 if args[&amp;quot;number&amp;quot;]:&lt;br /&gt;
     print(&amp;quot;num=%i&amp;quot; % args[&amp;quot;number&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
==== match case statement ====&lt;br /&gt;
(new in python 3.10)&lt;br /&gt;
 match args:&lt;br /&gt;
   case {&amp;quot;sap&amp;quot;: &amp;quot;prod&amp;quot;, &amp;quot;version&amp;quot;: 1}:&lt;br /&gt;
     ...&lt;br /&gt;
   case {&amp;quot;sap&amp;quot;: &amp;quot;prod&amp;quot;, &amp;quot;version&amp;quot;: 2}:&lt;br /&gt;
     ...&lt;br /&gt;
   case _: # default case&lt;br /&gt;
&lt;br /&gt;
==File Access==&lt;br /&gt;
===Pathlib===&lt;br /&gt;
for migrating see table [https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module]&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 p = Path(&amp;quot;dir/test.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 log_file = Path(__file__).with_suffix(&amp;quot;.log&amp;quot;)  # __file__ is name of python script&lt;br /&gt;
 &lt;br /&gt;
 my_fname = p.name  #  alternative to basename&lt;br /&gt;
 my_ext = p.suffix&lt;br /&gt;
 (filepath_without_ext, file_ext) = (p.stem, p.suffix)&lt;br /&gt;
 my_dir = p.parent&lt;br /&gt;
 my_parent_dir = p.parents[1]&lt;br /&gt;
 p2 = p.with_suffix(&amp;quot;.json&amp;quot;)&lt;br /&gt;
 p3 = p.parent / (p.name + &amp;quot;-autofix.tex&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # checks&lt;br /&gt;
 Path(my_file_str).exists()&lt;br /&gt;
 Path(my_file_str).is_file()&lt;br /&gt;
 Path(my_file_str).is_dir()&lt;br /&gt;
 &lt;br /&gt;
 # loop over glob of files matching wildcard&lt;br /&gt;
 for file_out in Path(&amp;quot;mydir&amp;quot;).glob(&amp;quot;*-autofix.tex&amp;quot;):&lt;br /&gt;
 &lt;br /&gt;
 # loop over dirs&lt;br /&gt;
 p = Path() # cwd&lt;br /&gt;
 list_of_repos = [x for x in p.iterdir() if x.is_dir() and (x / &amp;quot;.git&amp;quot;).is_dir()]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
alternative using old os functions&lt;br /&gt;
Split path into folder, filename, ext&lt;br /&gt;
 import os&lt;br /&gt;
 (dirName, fileName) = os.path.split(f)&lt;br /&gt;
 (fileBaseName, fileExtension) = os.path.splitext(fileName)&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-out.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===File Manipulations: copy, move, delete, touch===&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 # copy&lt;br /&gt;
 from shutil import copyfile&lt;br /&gt;
 copyfile(Path(&amp;quot;file-source.txt&amp;quot;), Path(&amp;quot;dir&amp;quot;) / Path(&amp;quot;file-target.txt&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # move / rename&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).replace(Path(&amp;quot;file2.txt&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # delete&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).unlink()&lt;br /&gt;
 &lt;br /&gt;
 # touch&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).touch()&lt;br /&gt;
&lt;br /&gt;
===File size and timestamp===&lt;br /&gt;
 # size&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).stat().st_size&lt;br /&gt;
 &lt;br /&gt;
 # timestamp last modified&lt;br /&gt;
 Path(&amp;quot;file.txt&amp;quot;).stat().st_mtime&lt;br /&gt;
&lt;br /&gt;
===Dir create/mkdir and delete===&lt;br /&gt;
 # create dir&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 Path(&amp;quot;myDir1/myDir2&amp;quot;).mkdir(parents=True, exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
 # delete dir&lt;br /&gt;
 import shutil&lt;br /&gt;
 shutil.rmtree(d)&lt;br /&gt;
&lt;br /&gt;
===Loop over Directories===&lt;br /&gt;
====Fetch Dir Contents / Loop over Files====&lt;br /&gt;
glob via pathlib&lt;br /&gt;
 for fileOut in Path(&amp;quot;mydir&amp;quot;).glob(&amp;quot;*-autofix.tex&amp;quot;):&lt;br /&gt;
&lt;br /&gt;
Get list of files in directory, filter dirs from list, filter by ext&lt;br /&gt;
 dir= &amp;quot;/path/to/some/dir&amp;quot;&lt;br /&gt;
 listoffiles = [ f for f in os.listdir(dir) if os.path.isfile(os.path.join(dir ,f)) and f.lower()[-4:] == &amp;quot;.gpx&amp;quot;]&lt;br /&gt;
 listoffiles.sort()&lt;br /&gt;
&lt;br /&gt;
====Traverse in Subdirs====&lt;br /&gt;
 # walk into path an fetch all files matching extension jpe?g&lt;br /&gt;
 files = []&lt;br /&gt;
 for (dirpath, dirnames, filenames) in os.walk(&amp;quot;.&amp;quot;):&lt;br /&gt;
     dirpath = dirpath.replace(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;)&lt;br /&gt;
     for file in filenames:&lt;br /&gt;
         if file.endswith(&amp;quot;.txt&amp;quot;):&lt;br /&gt;
             files.append(dirpath + &amp;quot;/&amp;quot; + file)&lt;br /&gt;
         elif re.search(r&amp;quot;\.jpe?g$&amp;quot;, file, re.IGNORECASE):&lt;br /&gt;
             files.append(dirpath + &amp;quot;/&amp;quot; + file)&lt;br /&gt;
&lt;br /&gt;
==File Parsing==&lt;br /&gt;
===File General===&lt;br /&gt;
====File Read====&lt;br /&gt;
 path_file_in = Path(&amp;quot;file.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # via pathlib&lt;br /&gt;
 cont = path_file_in.read_text(encoding=&amp;quot;utf-8&amp;quot;) # note: utf-8-sig for UTF-8-BOM&lt;br /&gt;
 &lt;br /&gt;
 # general&lt;br /&gt;
 with path_file_in.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
     cont = fh.read()&lt;br /&gt;
     # or&lt;br /&gt;
     lst = fh.readlines()&lt;br /&gt;
     # or&lt;br /&gt;
     line = fh.readline()&lt;br /&gt;
     # or&lt;br /&gt;
     for line in fh:&lt;br /&gt;
         print(line)&lt;br /&gt;
 &lt;br /&gt;
 fh = path_file_in.open(encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 ...&lt;br /&gt;
 fh.close()&lt;br /&gt;
&lt;br /&gt;
====File Write====&lt;br /&gt;
 path_file_out = Path(&amp;quot;out/1/out.txt&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # write via pathlib&lt;br /&gt;
 path_file_out.write_text(&amp;quot;some text&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # write general&lt;br /&gt;
 with path_file_out.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     # w = overWrite file ; a = append to file&lt;br /&gt;
     # If running Python in Windows, &amp;quot;\n&amp;quot; is automatically replaced by &amp;quot;\r\n&amp;quot;.&lt;br /&gt;
     # To prevent this use newline=&#039;\n&#039;&lt;br /&gt;
     fh.writelines(lst)  # no linebreaks&lt;br /&gt;
     # or&lt;br /&gt;
     fh.write(&amp;quot;\n&amp;quot;.join(lst))&lt;br /&gt;
     # or&lt;br /&gt;
     for line in lst:&lt;br /&gt;
         fh.write(line)&lt;br /&gt;
 &lt;br /&gt;
     # Force update of file contents without closing it&lt;br /&gt;
     fh.flush()&lt;br /&gt;
 &lt;br /&gt;
 # alternative&lt;br /&gt;
 fh = path_file_out.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;)&lt;br /&gt;
 ...&lt;br /&gt;
 fh.close()&lt;br /&gt;
&lt;br /&gt;
===CSV===&lt;br /&gt;
====CSV Read====&lt;br /&gt;
 import csv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 with Path(&amp;quot;data.csv&amp;quot;).open(mode=&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as fh:  # for Excel use ANSI&lt;br /&gt;
     csv_reader = csv.DictReader(fh, dialect=&amp;quot;excel&amp;quot;, delimiter=&amp;quot;\t&amp;quot;)&lt;br /&gt;
     for row in csv_reader:&lt;br /&gt;
         print(f&#039;\t{row[&amp;quot;name&amp;quot;]} works in the {row[&amp;quot;department&amp;quot;]} department&#039;)&lt;br /&gt;
&lt;br /&gt;
====CSV Write====&lt;br /&gt;
 import csv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 &lt;br /&gt;
 # simple&lt;br /&gt;
 with Path(&amp;quot;data.tsv&amp;quot;).open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     csvwriter = csv.writer(fh, delimiter=&amp;quot;\t&amp;quot;)&lt;br /&gt;
     csvwriter.writerow((&amp;quot;Date&amp;quot;, &amp;quot;Confirmed&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 # dictwriter&lt;br /&gt;
 with Path(&amp;quot;data.tsv&amp;quot;).open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     csvwriter = csv.DictWriter(&lt;br /&gt;
         fh,&lt;br /&gt;
         delimiter=&amp;quot;\t&amp;quot;,&lt;br /&gt;
         extrasaction=&amp;quot;ignore&amp;quot;,&lt;br /&gt;
         fieldnames=[&amp;quot;date&amp;quot;, &amp;quot;occupied_percent&amp;quot;, &amp;quot;occupied&amp;quot;, &amp;quot;total&amp;quot;],&lt;br /&gt;
     )&lt;br /&gt;
     csvwriter.writeheader()&lt;br /&gt;
     for d in my_list_of_dicts:&lt;br /&gt;
         d[&amp;quot;occupied_percent&amp;quot;] = round(100 * d[&amp;quot;occupied&amp;quot;] / d[&amp;quot;total&amp;quot;], 1)&lt;br /&gt;
         csvwriter.writerow(d)&lt;br /&gt;
&lt;br /&gt;
===JSON===&lt;br /&gt;
====JSON Read====&lt;br /&gt;
 import json&lt;br /&gt;
 # from file&lt;br /&gt;
 with path_to_download_file.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
     d_json = json.load(fh)&lt;br /&gt;
 # from string&lt;br /&gt;
 d_json = json.loads(response_text)&lt;br /&gt;
&lt;br /&gt;
 def json_read(file_path: Path) -&amp;gt; list[dict[str, str]]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Read JSON data from file.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     with file_path.open(encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
         json_data = json.load(fh)&lt;br /&gt;
     return json_data&lt;br /&gt;
&lt;br /&gt;
====JSON Write====&lt;br /&gt;
Write dict to file in JSON format, keeping utf-8 encoding&lt;br /&gt;
 import json&lt;br /&gt;
 &lt;br /&gt;
 with path_to_download_file.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
     json.dump(my_dict, fh, ensure_ascii=False, sort_keys=False, indent=2)&lt;br /&gt;
&lt;br /&gt;
 def json_write(file_path: Path, json_data: list[dict[str, str]]) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Write JSON data to file.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     with file_path.open(&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
         json.dump(json_data, fh, ensure_ascii=False, sort_keys=False, indent=2)&lt;br /&gt;
&lt;br /&gt;
===Excel===&lt;br /&gt;
====Excel Read====&lt;br /&gt;
 import openpyxl&lt;br /&gt;
 # or xlsxwriter for write-only&lt;br /&gt;
 &lt;br /&gt;
 workbook = openpyxl.load_workbook(&lt;br /&gt;
     pathToMyExcelFile,&lt;br /&gt;
     data_only=True,  # read values instead of formulas&lt;br /&gt;
     read_only=True,  # suppresses: &amp;quot;UserWarning: wmf image format is not supported so the image is being dropped&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 sheet = workbook[&amp;quot;mySheetName&amp;quot;]&lt;br /&gt;
 # or fetch active sheet&lt;br /&gt;
 sheet = workbook.active&lt;br /&gt;
 cell = sheet[&amp;quot;A34&amp;quot;]&lt;br /&gt;
 # or&lt;br /&gt;
 cell = sheet.cell(row=34, column=1)  # index start here with 1&lt;br /&gt;
 print(cell.value)&lt;br /&gt;
 # or&lt;br /&gt;
 print(sheet.cell(column=col, row=row).value)&lt;br /&gt;
&lt;br /&gt;
====Excel Write====&lt;br /&gt;
 import openpyxl&lt;br /&gt;
 &lt;br /&gt;
 workbook = openpyxl.Workbook()&lt;br /&gt;
 sheet = workbook.active&lt;br /&gt;
 cell = sheet[&amp;quot;A34&amp;quot;]&lt;br /&gt;
 # or&lt;br /&gt;
 cell = sheet.cell(row=i, column=j)  # index starts at 1&lt;br /&gt;
 cell.value = &amp;quot;asdf&amp;quot;&lt;br /&gt;
 workbook.save(&amp;quot;out.xlsx&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Word===&lt;br /&gt;
====Word Read====&lt;br /&gt;
 from io import BytesIO&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import pandas as pd&lt;br /&gt;
 from docx import Document&lt;br /&gt;
 from docx.document import Document as DocxDocument&lt;br /&gt;
 &lt;br /&gt;
 def read_doc_to_df(p_doc: Path | BytesIO) -&amp;gt; pd.DataFrame:  # noqa: C901, PLR0912&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Read Word document to DataFrame.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if isinstance(p_doc, Path):&lt;br /&gt;
         doc = Document(str(p_doc))&lt;br /&gt;
     elif isinstance(p_doc, BytesIO):&lt;br /&gt;
         doc = Document(p_doc)&lt;br /&gt;
 &lt;br /&gt;
     # list of multiple paragraphs per key&lt;br /&gt;
     data: dict[str, dict[str, list[str]]] = {}&lt;br /&gt;
 &lt;br /&gt;
     sections1: list[str] = []&lt;br /&gt;
     section1 = &amp;quot;&amp;quot;&lt;br /&gt;
     key = &amp;quot;&amp;quot;&lt;br /&gt;
     section2 = &amp;quot;&amp;quot;&lt;br /&gt;
     score = &amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     for paragraph in doc.paragraphs:&lt;br /&gt;
         assert paragraph.style is not None&lt;br /&gt;
         text = paragraph.text.strip()&lt;br /&gt;
         if paragraph.style.name == &amp;quot;Title&amp;quot;:  # doc title&lt;br /&gt;
             continue&lt;br /&gt;
         if paragraph.style.name == &amp;quot;Heading 1&amp;quot;:  # sheet name&lt;br /&gt;
             section1 = text&lt;br /&gt;
             if section1 not in sections1:&lt;br /&gt;
                 sections1.append(section1)&lt;br /&gt;
             key = &amp;quot;&amp;quot;&lt;br /&gt;
             section2 = &amp;quot;&amp;quot;&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Heading 2&amp;quot;:  # Key of the question&lt;br /&gt;
             key = text.split(&amp;quot; | &amp;quot;)[0]&lt;br /&gt;
             key = section1 + &amp;quot;|&amp;quot; + key&lt;br /&gt;
             section2 = &amp;quot;&amp;quot;&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Normal&amp;quot; and text.startswith(&amp;quot;[&amp;quot;) and &amp;quot;]&amp;quot; in text:&lt;br /&gt;
             section2, t2 = text[1:].split(&amp;quot;]&amp;quot;, maxsplit=1)&lt;br /&gt;
             if section2 == &amp;quot;Score&amp;quot;:&lt;br /&gt;
                 score = t2.strip()&lt;br /&gt;
                 data[key][section2] = [score]&lt;br /&gt;
         elif paragraph.style.name == &amp;quot;Normal&amp;quot;:&lt;br /&gt;
             text = paragraph.text.strip()&lt;br /&gt;
             # skip requirements leftovers and empty paragraphs&lt;br /&gt;
             if (section2 == &amp;quot;&amp;quot; and text.startswith(&amp;quot;...&amp;quot;)) or text == &amp;quot;&amp;quot;:&lt;br /&gt;
                 continue&lt;br /&gt;
             lst = data.setdefault(key, {}).setdefault(section2, [])&lt;br /&gt;
             lst.append(text)&lt;br /&gt;
             data[key][section2] = lst&lt;br /&gt;
         else:&lt;br /&gt;
             msg = f&amp;quot;Unexpected style: {paragraph.style.name} in paragraph: {text}&amp;quot;&lt;br /&gt;
             raise ValueError(msg)&lt;br /&gt;
 &lt;br /&gt;
     # flatten the text from list to str&lt;br /&gt;
     data2: dict[str, dict[str, str]] = {}&lt;br /&gt;
     for key, sections in data.items():&lt;br /&gt;
         for section2, lst in sections.items():&lt;br /&gt;
             s = &amp;quot;\n&amp;quot;.join(lst)&lt;br /&gt;
             if key not in data2:&lt;br /&gt;
                 data2[key] = {}&lt;br /&gt;
             data2[key][section2] = s.strip()&lt;br /&gt;
 &lt;br /&gt;
     df1 = pd.DataFrame.from_dict(data2, orient=&amp;quot;index&amp;quot;)&lt;br /&gt;
     df1.index.name = &amp;quot;Key&amp;quot;&lt;br /&gt;
     df1 = df1.reset_index()&lt;br /&gt;
     df1[ [ &amp;quot;Sheet&amp;quot;, &amp;quot;Key&amp;quot; ] ] = df1[&amp;quot;Key&amp;quot;].str.split(&amp;quot;|&amp;quot;, n=1, expand=True)&lt;br /&gt;
 &lt;br /&gt;
     return df1&lt;br /&gt;
&lt;br /&gt;
====Word Write====&lt;br /&gt;
 from io import BytesIO&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import pandas as pd from docx import Document&lt;br /&gt;
 from docx.document import Document as DocxDocument&lt;br /&gt;
 from docx.shared import Cm&lt;br /&gt;
 &lt;br /&gt;
 LAYOUT_SMALL_MARGINS = True&lt;br /&gt;
 if LAYOUT_SMALL_MARGINS:&lt;br /&gt;
     sections = doc.sections&lt;br /&gt;
     for section in sections:&lt;br /&gt;
         section.top_margin = Cm(1)&lt;br /&gt;
         section.bottom_margin = Cm(1)&lt;br /&gt;
         section.left_margin = Cm(1)&lt;br /&gt;
         section.right_margin = Cm(1)&lt;br /&gt;
 &lt;br /&gt;
 doc.add_heading(&amp;quot;My Title&amp;quot;, level=0)&lt;br /&gt;
 doc.add_heading(&amp;quot;My Section&amp;quot;, level=1)&lt;br /&gt;
 doc.add_paragraph(&amp;quot;Some Text)&lt;br /&gt;
 p = doc.add_paragraph()&lt;br /&gt;
 runner = p.add_run(bold text&amp;quot;)&lt;br /&gt;
 runner.bold = True  # runner.italic = True&lt;br /&gt;
&lt;br /&gt;
==Regular Expressions==&lt;br /&gt;
See [http://docs.python.org/library/re.html]&lt;br /&gt;
&lt;br /&gt;
See [https://pythex.org] for an online tester&lt;br /&gt;
&lt;br /&gt;
multiple flags are joined via pipe |&lt;br /&gt;
 s = re.sub(&amp;quot;asdf.*&amp;quot;, r&amp;quot;qwertz&amp;quot;, s, flags=re.DOTALL | re.IGNORECASE)&lt;br /&gt;
&lt;br /&gt;
====Lookahead and Lookbehind====&lt;br /&gt;
 pos lookahead: (?=...)&lt;br /&gt;
 neg lookahead: (?!...)&lt;br /&gt;
 pos lookbehind (?&amp;lt;=...)&lt;br /&gt;
 neg lookbehind (?&amp;lt;!...)&lt;br /&gt;
&lt;br /&gt;
====matching====&lt;br /&gt;
 import re&lt;br /&gt;
 &lt;br /&gt;
 # V0: simple 1&lt;br /&gt;
 myPattern = &amp;quot;(/\*\*\* 0097_210000_0192539580000_2898977_0050 \*\*\*/.*?)($|/\*\*\*)&amp;quot;&lt;br /&gt;
 myRegExp = re.compile(myPattern, re.DOTALL)&lt;br /&gt;
 myMatch = myRegExp.search(cont)&lt;br /&gt;
 assert myMatch != None, f&amp;quot;golden file not found in file {filename}&amp;quot;&lt;br /&gt;
 cont_golden = myMatch.group(1)&lt;br /&gt;
 &lt;br /&gt;
 # V1: simple 2&lt;br /&gt;
 assert (&lt;br /&gt;
     re.match(&amp;quot;^[a-z]{2}$&amp;quot;, d_settings[&amp;quot;country&amp;quot;]) != None&lt;br /&gt;
 ), f&#039;Error: county must be 2 digit lower case. We got: {d_settings[&amp;quot;country&amp;quot;]}&#039;&lt;br /&gt;
 &lt;br /&gt;
 # V2: find and count&lt;br /&gt;
 was = r&amp;quot;&amp;quot;&amp;quot;Part: ([^&amp;lt;+])&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 cnt_parts = len(re.findall(was, cont))&lt;br /&gt;
 cont = re.sub(was, r&amp;quot;\n\nTeil: \1\n\n&amp;quot;, cont)&lt;br /&gt;
 assert cnt_parts == 4, f&amp;quot;{cnt_parts} == 4&amp;quot;&lt;br /&gt;
 assert &amp;quot;Part:&amp;quot; not in cont&lt;br /&gt;
&lt;br /&gt;
=====Match email=====&lt;br /&gt;
 def checkValidEMail(email: str) -&amp;gt; bool:&lt;br /&gt;
     # from https://stackoverflow.com/posts/719543/timeline bottom edit&lt;br /&gt;
     if not re.fullmatch(r&amp;quot;^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$&amp;quot;, email):&lt;br /&gt;
         print(&amp;quot;Error: invalid email&amp;quot;)&lt;br /&gt;
         quit()&lt;br /&gt;
     return True&lt;br /&gt;
&lt;br /&gt;
====Find all====&lt;br /&gt;
 myMatches = re.findall(&#039;href=&amp;quot;([^&amp;quot;]+)&amp;quot;&#039;, cont)&lt;br /&gt;
 for myMatch in myMatches:&lt;br /&gt;
     print(myMatch)&lt;br /&gt;
&lt;br /&gt;
====substring====&lt;br /&gt;
 import re&lt;br /&gt;
 &lt;br /&gt;
 # simple via search&lt;br /&gt;
 lk_id = re.search(&#039;^.*timeseries\-(\d+)\.json$&#039;, f).group(1)&lt;br /&gt;
 &lt;br /&gt;
 # simple via sub&lt;br /&gt;
 myPattern = &amp;quot;^.*&amp;quot; + s1 + &amp;quot;(.*)&amp;quot; + s2 + &amp;quot;.*$&amp;quot;&lt;br /&gt;
 out = re.sub(myPattern, r&amp;quot;\1&amp;quot;, s)&lt;br /&gt;
 &lt;br /&gt;
 # more robust including an assert&lt;br /&gt;
 def substr_between(s: str, s1: str, s2: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     returns substring of s, found between strings s1 and s2&lt;br /&gt;
     s1 and s2 can be regular expressions&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     myPattern = s1 + &#039;(.*)&#039; + s2&lt;br /&gt;
     myRegExp = re.compile(myPattern)&lt;br /&gt;
     myMatches = myRegExp.search(s)&lt;br /&gt;
     assert myMatches != None, f&amp;quot;E: can&#039;t find &#039;{s1}&#039;...&#039;{s2}&#039; in &#039;{s}&#039;&amp;quot;&lt;br /&gt;
     out = myMatches.group(1)&lt;br /&gt;
     return out&lt;br /&gt;
&lt;br /&gt;
 matchObj = re.search(r&amp;quot;(\d+\.\d+)&amp;quot;, text&lt;br /&gt;
 if matchObj:&lt;br /&gt;
   price = float( &#039;%s&#039; % (matchObj).group(0) )&lt;br /&gt;
&lt;br /&gt;
====Naming of match groups====&lt;br /&gt;
(?P&amp;lt;name&amp;gt;...), see [https://docs.python.org/3/library/re.html]&lt;br /&gt;
&lt;br /&gt;
====Search and Replace====&lt;br /&gt;
From [http://www.regular-expressions.info/python.html]&amp;lt;br&amp;gt;&lt;br /&gt;
re.sub(regex, replacement, str) performs a search-and-replace across subject, replacing all matches of regex in str with replacement. The result is returned by the sub() function. The str string you pass is not modified.&lt;br /&gt;
&lt;br /&gt;
 s = re.sub(&amp;quot;  +&amp;quot;, &amp;quot; &amp;quot;, s)&lt;br /&gt;
&lt;br /&gt;
====Splitting====&lt;br /&gt;
From [http://docs.python.org/library/re.html]&amp;lt;br&amp;gt;&lt;br /&gt;
split() splits a string into a list delimited by the passed pattern. The method is invaluable for converting textual data into data structures that can be easily read and modified by Python as demonstrated in the following example that creates a phonebook.&lt;br /&gt;
&lt;br /&gt;
First, here is the input. Normally it may come from a file, here we are using triple-quoted string syntax:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; input = &amp;quot;&amp;quot;&amp;quot;Ross McFluff: 834.345.1254 155 Elm Street&lt;br /&gt;
 ...&lt;br /&gt;
 ... Ronald Heathmore: 892.345.3428 436 Finley Avenue&lt;br /&gt;
 ... Frank Burger: 925.541.7625 662 South Dogwood Way&lt;br /&gt;
 ...&lt;br /&gt;
 ...&lt;br /&gt;
 ... Heather Albrecht: 548.326.4584 919 Park Place&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The entries are separated by one or more newlines. Now we convert the string into a list with each nonempty line having its own entry:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; entries = re.split(&amp;quot;\n+&amp;quot;, input)&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; entries&lt;br /&gt;
 [&#039;Ross McFluff: 834.345.1254 155 Elm Street&#039;,&lt;br /&gt;
 &#039;Ronald Heathmore: 892.345.3428 436 Finley Avenue&#039;,&lt;br /&gt;
 &#039;Frank Burger: 925.541.7625 662 South Dogwood Way&#039;,&lt;br /&gt;
 &#039;Heather Albrecht: 548.326.4584 919 Park Place&#039;]&lt;br /&gt;
&lt;br /&gt;
Finally, split each entry into a list with first name, last name, telephone number, and address. We use the maxsplit parameter of split() because the address has spaces, our splitting pattern, in it:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; [re.split(&amp;quot;:? &amp;quot;, entry, 3) for entry in entries]&lt;br /&gt;
 [[&#039;Ross&#039;, &#039;McFluff&#039;, &#039;834.345.1254&#039;, &#039;155 Elm Street&#039;],&lt;br /&gt;
 [&#039;Ronald&#039;, &#039;Heathmore&#039;, &#039;892.345.3428&#039;, &#039;436 Finley Avenue&#039;],&lt;br /&gt;
 [&#039;Frank&#039;, &#039;Burger&#039;, &#039;925.541.7625&#039;, &#039;662 South Dogwood Way&#039;],&lt;br /&gt;
 [&#039;Heather&#039;, &#039;Albrecht&#039;, &#039;548.326.4584&#039;, &#039;919 Park Place&#039;]]&lt;br /&gt;
&lt;br /&gt;
==== replace cont by linebreaks====&lt;br /&gt;
 def replace_cont_by_linebreaks(s: str, regex: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Replace regex in s by the number of linebreaks it originally contained.&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     myMatches = re.findall(regex, s, flags=re.DOTALL)&lt;br /&gt;
     for match in myMatches:&lt;br /&gt;
         linebreaks = match.count(&amp;quot;\n&amp;quot;)&lt;br /&gt;
         s = s.replace(match, &amp;quot;\n&amp;quot; * linebreaks, 1)&lt;br /&gt;
     return s&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Image/Picture/Photo==&lt;br /&gt;
 from PIL import Image, ImageFilter  # pip install Pillow&lt;br /&gt;
 &lt;br /&gt;
 fileIn = &amp;quot;2018-02-09 13.56.25.jpg&amp;quot;&lt;br /&gt;
 # Read image&lt;br /&gt;
 img = Image.open(fileIn)&lt;br /&gt;
PROBLEM:&amp;lt;br/&amp;gt;&lt;br /&gt;
PIL Image.save() drops the IPTC data like tags, keywords, copywrite, ...&amp;lt;br/&amp;gt;&lt;br /&gt;
better using https://imagemagick.org instead when tags shall be kept&lt;br /&gt;
&lt;br /&gt;
====Resize====&lt;br /&gt;
 # Resize keeping aspect ration -&amp;gt; img.thumbnail&lt;br /&gt;
 # drops exif data, exif can be added from source file via exif= in save, see below&lt;br /&gt;
 size = 1920, 1920&lt;br /&gt;
 img.thumbnail(size, Image.ANTIALIAS)&lt;br /&gt;
&lt;br /&gt;
====Export file====&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-edit.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img = Image.open(fileIn)&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=&#039;keep&#039;)  # exif=dict_exif_bytes&lt;br /&gt;
     # JPEG Parameters&lt;br /&gt;
     # * qualitiy : &#039;keep&#039; or 1 (worst) to 95 (best), default = 75. Values above 95 should be avoided.&lt;br /&gt;
     # * dpi : tuple of integers representing the pixel density, (x,y)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     print(&amp;quot;cannot write file &#039;%s&#039;&amp;quot; % fileOut)&lt;br /&gt;
&lt;br /&gt;
====Export Progressive / web optimized JPEG====&lt;br /&gt;
 from PIL import ImageFile  # for MAXBLOCK for progressive export&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-progressive.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=80, optimize=True, progressive=True)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     ImageFile.MAXBLOCK = img.size[0] * img.size[1]&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;JPEG&amp;quot;, quality=80, optimize=True, progressive=True)&lt;br /&gt;
&lt;br /&gt;
====JPEG Meta Data: EXIF and IPTC====&lt;br /&gt;
=====IPTC: Tags/Keywords=====&lt;br /&gt;
 from iptcinfo3 import IPTCInfo  # this works in pyhton 3!&lt;br /&gt;
 iptc = IPTCInfo(fileIn)&lt;br /&gt;
 if len(iptc[&#039;keywords&#039;]) &amp;gt; 0:  # or supplementalCategories or contacts&lt;br /&gt;
     print(&#039;====&amp;gt; Keywords&#039;)&lt;br /&gt;
     for key in sorted(iptc[&#039;keywords&#039;]):&lt;br /&gt;
         s = key.decode(&#039;ascii&#039;)  # decode binary strings&lt;br /&gt;
         print(s)&lt;br /&gt;
&lt;br /&gt;
=====EXIF via piexif=====&lt;br /&gt;
 import piexif  # pip install piexif&lt;br /&gt;
 exif_dict = piexif.load(img.info[&#039;exif&#039;])&lt;br /&gt;
 print(exif_dict[&#039;GPS&#039;][piexif.GPSIFD.GPSAltitude])&lt;br /&gt;
 # returns list of 2 integers: value and donator  -&amp;gt; v / d&lt;br /&gt;
 # (340000, 1000) =&amp;gt; 340m&lt;br /&gt;
 # (51, 2) =&amp;gt; 25.5m&lt;br /&gt;
 &lt;br /&gt;
 # Modify altitude&lt;br /&gt;
 exif_dict[&#039;GPS&#039;][piexif.GPSIFD.GPSAltitude] = (140, 1)  # 140m&lt;br /&gt;
 &lt;br /&gt;
 # write to file&lt;br /&gt;
 exif_bytes = piexif.dump(exif_dict)&lt;br /&gt;
 fileOut = os.path.splitext(fileIn)[0] + &amp;quot;-modExif.jpg&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     img.save(fp=fileOut, format=&amp;quot;jpeg&amp;quot;, exif=exif_bytes, quality=&#039;keep&#039;)&lt;br /&gt;
 except IOError:&lt;br /&gt;
     print(&amp;quot;cannot write file &#039;%s&#039;&amp;quot; % fileOut)&lt;br /&gt;
or&lt;br /&gt;
 exif_dict = piexif.load(fileIn)&lt;br /&gt;
 for ifd in (&amp;quot;0th&amp;quot;, &amp;quot;Exif&amp;quot;, &amp;quot;GPS&amp;quot;, &amp;quot;1st&amp;quot;):&lt;br /&gt;
     print(&amp;quot;===&amp;quot; + ifd)&lt;br /&gt;
     for tag in exif_dict[ifd]:&lt;br /&gt;
         print(piexif.TAGS[ifd][tag][&amp;quot;name&amp;quot;], &amp;quot;\t&amp;quot;,&lt;br /&gt;
               tag, &amp;quot;\t&amp;quot;, exif_dict[ifd][tag])&lt;br /&gt;
 print(exif_dict[&#039;0th&#039;][306]) # 306 = DateTime&lt;br /&gt;
&lt;br /&gt;
=====EXIF via exifread=====&lt;br /&gt;
 # Open image file for reading (binary mode)&lt;br /&gt;
 fh = open(fileIn, &amp;quot;rb&amp;quot;)&lt;br /&gt;
 # Return Exif tags&lt;br /&gt;
 exif = exifread.process_file(fh)&lt;br /&gt;
 fh.close()&lt;br /&gt;
 # for tag in exif.keys():&lt;br /&gt;
 #     if tag not in (&#039;JPEGThumbnail&#039;, &#039;TIFFThumbnail&#039;, &#039;Filename&#039;, &#039;EXIF MakerNote&#039;):&lt;br /&gt;
 #         print(&amp;quot;%s\t%s&amp;quot; % (tag, exif[tag]))&lt;br /&gt;
 print(exif[&amp;quot;Image DateTime&amp;quot;])&lt;br /&gt;
 print(exif[&amp;quot;GPS GPSLatitude&amp;quot;])&lt;br /&gt;
 print(exif[&amp;quot;GPS GPSLongitude&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
=====EXIF GPS via PIL=====&lt;br /&gt;
 # from https://developer.here.com/blog/getting-started-with-geocoding-exif-image-metadata-in-python3&lt;br /&gt;
 def get_exif(filename):&lt;br /&gt;
     image = Image.open(filename)&lt;br /&gt;
     image.verify()&lt;br /&gt;
     image.close()&lt;br /&gt;
     return image._getexif()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_labeled_exif(exif):&lt;br /&gt;
     labeled = {}&lt;br /&gt;
     for (key, val) in exif.items():&lt;br /&gt;
         labeled[TAGS.get(key)] = val&lt;br /&gt;
     return labeled&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_geotagging(exif):&lt;br /&gt;
     if not exif:&lt;br /&gt;
         raise ValueError(&amp;quot;No EXIF metadata found&amp;quot;)&lt;br /&gt;
     geotagging = {}&lt;br /&gt;
     for (idx, tag) in TAGS.items():&lt;br /&gt;
         if tag == &amp;quot;GPSInfo&amp;quot;:&lt;br /&gt;
             if idx not in exif:&lt;br /&gt;
                 raise ValueError(&amp;quot;No EXIF geotagging found&amp;quot;)&lt;br /&gt;
             for (key, val) in GPSTAGS.items():&lt;br /&gt;
                 if key in exif[idx]:&lt;br /&gt;
                     geotagging[val] = exif[idx][key]&lt;br /&gt;
     return geotagging&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_decimal_from_dms(dms, ref):&lt;br /&gt;
     degrees = dms[0][0] / dms[0][1]&lt;br /&gt;
     minutes = dms[1][0] / dms[1][1] / 60.0&lt;br /&gt;
     seconds = dms[2][0] / dms[2][1] / 3600.0&lt;br /&gt;
     if ref in [&amp;quot;S&amp;quot;, &amp;quot;W&amp;quot;]:&lt;br /&gt;
         degrees = -degrees&lt;br /&gt;
         minutes = -minutes&lt;br /&gt;
         seconds = -seconds&lt;br /&gt;
     return round(degrees + minutes + seconds, 5)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def get_coordinates(geotags):&lt;br /&gt;
     lat = get_decimal_from_dms(geotags[&amp;quot;GPSLatitude&amp;quot;], geotags[&amp;quot;GPSLatitudeRef&amp;quot;])&lt;br /&gt;
     lon = get_decimal_from_dms(geotags[&amp;quot;GPSLongitude&amp;quot;], geotags[&amp;quot;GPSLongitudeRef&amp;quot;])&lt;br /&gt;
     return (lat, lon)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 exif = get_exif(fileIn)&lt;br /&gt;
 exif_labeled = get_labeled_exif(exif)&lt;br /&gt;
 print(exif_labeled[&amp;quot;DateTime&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
 geotags = get_geotagging(exif)&lt;br /&gt;
 print(get_coordinates(geotags))&lt;br /&gt;
&lt;br /&gt;
====Template Matching / Image Regocnition Using CV2 / OpenCV====&lt;br /&gt;
see [[Python - CV2]]&lt;br /&gt;
&lt;br /&gt;
====Optical Character Recognition (OCR) ====&lt;br /&gt;
see [[Python - OCR]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Templates/Snippets==&lt;br /&gt;
===Retry on Exception===&lt;br /&gt;
 for retry_no in range(MAX_RETRIES):&lt;br /&gt;
     try:&lt;br /&gt;
         if retry_no &amp;gt; 0:&lt;br /&gt;
             logger.warning(&amp;quot;retrying %d/%d...&amp;quot;, retry_no + 1, MAX_RETRIES)&lt;br /&gt;
         doit()&lt;br /&gt;
     except json.JSONDecodeError:&lt;br /&gt;
         logger.exception(&amp;quot;JSONDecodeError&amp;quot;)&lt;br /&gt;
         if retry_no == MAX_RETRIES - 1:&lt;br /&gt;
             # end of retries&lt;br /&gt;
             raise&lt;br /&gt;
&lt;br /&gt;
===Diff of 2 files===&lt;br /&gt;
 import difflib&lt;br /&gt;
 &lt;br /&gt;
 fh1 = p_file1.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 fh2 = p_file1.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 diff = difflib.ndiff(fh1.readlines(), fh2.readlines())&lt;br /&gt;
 delta = &amp;quot;&amp;quot;.join(line for line in diff if line.startswith((&amp;quot;+ &amp;quot;, &amp;quot;- &amp;quot;)))&lt;br /&gt;
 print(delta)&lt;br /&gt;
&lt;br /&gt;
===GPX parsing===&lt;br /&gt;
 import gpxpy&lt;br /&gt;
 import gpxpy.gpx&lt;br /&gt;
 # Elevation data by NASA: see lib at https://github.com/tkrajina/srtm.py&lt;br /&gt;
 fh_gpx_file = open(gpx_file_path, &#039;r&#039;)&lt;br /&gt;
 gpx = gpxpy.parse(fh_gpx_file)&lt;br /&gt;
 #  Loops for accessing the data&lt;br /&gt;
 for track in gpx.tracks:&lt;br /&gt;
     for segment in track.segments:&lt;br /&gt;
         for point in segment.points:&lt;br /&gt;
 for waypoint in gpx.waypoints:&lt;br /&gt;
 for route in gpx.routes:&lt;br /&gt;
     for point in route.points: &lt;br /&gt;
 # interesting properties of point / waypoint objects:&lt;br /&gt;
 point.time&lt;br /&gt;
 point.latitude&lt;br /&gt;
 point.longitude&lt;br /&gt;
 point.source&lt;br /&gt;
 waypoint.name&lt;br /&gt;
&lt;br /&gt;
===TypeHints===&lt;br /&gt;
 from typing import Any, Dict, cast&lt;br /&gt;
 creds = cast(dict[str, str], tomllib.load(f))  # type: ignore&lt;br /&gt;
 o = cast(Dict[str, Any], tomllib.load(f))  # type: ignore&lt;br /&gt;
 o[&amp;quot;sap&amp;quot;] = cast(Dict[str, str], o[&amp;quot;sap&amp;quot;])&lt;br /&gt;
 o[&amp;quot;settings&amp;quot;] = cast(Dict[str, str | int | bool], o[&amp;quot;settings&amp;quot;])&lt;br /&gt;
 o[&amp;quot;settings&amp;quot;][&amp;quot;sleep_time&amp;quot;] = cast(int, o[&amp;quot;settings&amp;quot;][&amp;quot;sleep_time&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
===Pylance/Pyright===&lt;br /&gt;
 import tomllib&lt;br /&gt;
 # shows warning: Import &amp;quot;xyz&amp;quot; could not be resolved&lt;br /&gt;
 # fix by &lt;br /&gt;
 import tomllib # pyright: ignore&lt;br /&gt;
&lt;br /&gt;
===asserts function argument validation===&lt;br /&gt;
aus [https://bildungsportal.sachsen.de/opal/auth/RepositoryEntry/12518195218/CourseNode/94506982489539?0 Python Kurs von Carsten Knoll]&lt;br /&gt;
 def eine_funktion(satz, ganzzahl, zahl2, liste):&lt;br /&gt;
   if not type(satz) == str:&lt;br /&gt;
     print &amp;quot;Datentpyfehler: satz&amp;quot;&lt;br /&gt;
     return -1&lt;br /&gt;
   if not isinstance(ganzzahl, int):&lt;br /&gt;
     print &amp;quot;Datentpyfehler: ganzzahl&amp;quot;&lt;br /&gt;
     return -2&lt;br /&gt;
   if not isinstance(liste, (tuple, list)):&lt;br /&gt;
     print &amp;quot;Datentpyfehler: liste&amp;quot;&lt;br /&gt;
     return -3&lt;br /&gt;
   # Kompakteste Variante (empfohlen): &lt;br /&gt;
   assert zahl2 &amp;gt; 0, &amp;quot;Error: zahl2 ist nicht &amp;gt; 0&amp;quot; # Assertation-Error bei Nichterfuellung&lt;br /&gt;
&lt;br /&gt;
 def F(x):&lt;br /&gt;
   if not isinstance(x, (float, int)):&lt;br /&gt;
     msg = &amp;quot;Zahl erwartet, %s bekommen&amp;quot; % type(x)&lt;br /&gt;
     raise ValueError(msg)&lt;br /&gt;
   return x**2&lt;br /&gt;
better:&lt;br /&gt;
 def F(x):&lt;br /&gt;
   assert isinstance(x, (float, int)), &amp;quot;Error: x is not of type float or int&amp;quot;&lt;br /&gt;
   return x**2&lt;br /&gt;
&lt;br /&gt;
 assert variant in [&lt;br /&gt;
     &amp;quot;normal&amp;quot;,&lt;br /&gt;
     &amp;quot;gray&amp;quot;,&lt;br /&gt;
     &amp;quot;cannyedge&amp;quot;,&lt;br /&gt;
 ], &amp;quot;Error: variant is not in &#039;normal&#039;, &#039;gray&#039;, &#039;cannyedge&#039;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===Exceptions===&lt;br /&gt;
see [https://medium.com/@saadjamilakhtar/5-best-practices-for-python-exception-handling-5e54b876a20]&lt;br /&gt;
&lt;br /&gt;
====Catching====&lt;br /&gt;
Catch keyboard interrupt and do a &amp;quot;save exit&amp;quot;&lt;br /&gt;
 try:&lt;br /&gt;
     i = 0&lt;br /&gt;
     while 1:&lt;br /&gt;
         i += 1&lt;br /&gt;
         print(i)&lt;br /&gt;
 except KeyboardInterrupt:&lt;br /&gt;
     print(&amp;quot;stopped&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
Catch &#039;&#039;&#039;all&#039;&#039;&#039; exceptions&lt;br /&gt;
 try:&lt;br /&gt;
   [...]&lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     print(&amp;quot;Exception: &amp;quot;, e)&lt;br /&gt;
&lt;br /&gt;
====Raising Exceptions====&lt;br /&gt;
 if resp.status_code != 200:  # noqa: PLR2004&lt;br /&gt;
     msg = f&amp;quot;Bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
     raise ValueError(msg) from None&lt;br /&gt;
&lt;br /&gt;
Custom Exceptions&lt;br /&gt;
 try: &lt;br /&gt;
   raise Exception(&amp;quot;HiHo&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
 raise ValueError¶&lt;br /&gt;
&lt;br /&gt;
===perl grep and map===&lt;br /&gt;
from [https://stackoverflow.com/questions/12845288/grep-on-elements-of-a-list]&lt;br /&gt;
 def grep(list, pattern):&lt;br /&gt;
     expr = re.compile(pattern)&lt;br /&gt;
     return [elem for elem in list if expr.match(elem)]&lt;br /&gt;
 or&lt;br /&gt;
 filteredList = filter(lambda x: x &amp;lt; 7 and x &amp;gt; 2, unfilteredList)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def map(list, was, womit):&lt;br /&gt;
     return list(map(lambda i: re.sub(was, womit, i), list))&lt;br /&gt;
     # was = &#039;.*&amp;quot;(\d+)&amp;quot;.*&#039;&lt;br /&gt;
     # womit = r&amp;quot;\1&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===unit testing using pytest===&lt;br /&gt;
install via&lt;br /&gt;
 pip install pytest&lt;br /&gt;
activate in vscode, see [https://code.visualstudio.com/docs/python/testing#_enable-a-test-framework]: To enable testing, use the Python: Configure Tests command on the Command Palette.&lt;br /&gt;
&lt;br /&gt;
see https://docs.pytest.org/en/6.2.x/assert.html#assert&lt;br /&gt;
&lt;br /&gt;
test_1.py:&lt;br /&gt;
 import myLib # my custom lib to test&lt;br /&gt;
 &lt;br /&gt;
 class TestClass:&lt;br /&gt;
     def test_one(self):&lt;br /&gt;
         x = &amp;quot;this&amp;quot;&lt;br /&gt;
         assert &amp;quot;h&amp;quot; in x&lt;br /&gt;
 &lt;br /&gt;
     def test_two(self):&lt;br /&gt;
         assert myLib.multiply(1, 2) == 2&lt;br /&gt;
 &lt;br /&gt;
     def test_three(self):&lt;br /&gt;
         assert myLib.multiply(2, 2) == 2&lt;br /&gt;
&lt;br /&gt;
====project structure====&lt;br /&gt;
=====simplest structore: test next to script=====&lt;br /&gt;
Alternative without tests dir:&lt;br /&gt;
 ./main.py&lt;br /&gt;
 ./helper.py &lt;br /&gt;
 ./helper_test.py&lt;br /&gt;
with helper_test.py:&lt;br /&gt;
 from helper import fnc1&lt;br /&gt;
&lt;br /&gt;
=====standalone model with src and tests dirs=====&lt;br /&gt;
for this dir structure&lt;br /&gt;
 src/helper.py &lt;br /&gt;
 tests/helper_test.py&lt;br /&gt;
helper_test.py needs&lt;br /&gt;
 import sys&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 sys.path.insert(0, (Path(__file__).parent.parent / &amp;quot;src&amp;quot;).as_posix())&lt;br /&gt;
&lt;br /&gt;
=====standalone model with tests dir=====&lt;br /&gt;
if the test files are placed inside a ./tests/ dir&lt;br /&gt;
 ./main.py&lt;br /&gt;
 ./helper.py &lt;br /&gt;
 tests/helper_test.py&lt;br /&gt;
helper_test.py needs &lt;br /&gt;
 sys.path.insert(0, Path(__file__).parent.parent.as_posix())&lt;br /&gt;
 from helper import fnc1&lt;br /&gt;
&lt;br /&gt;
====parametrize====&lt;br /&gt;
 import pytest&lt;br /&gt;
 @pytest.mark.parametrize(&lt;br /&gt;
     (&amp;quot;test_input&amp;quot;, &amp;quot;expected&amp;quot;),&lt;br /&gt;
     [(&amp;quot;&amp;quot;, None), (&amp;quot;PT30M&amp;quot;, 30), (&amp;quot;PT3H&amp;quot;, 3 * 60), (&amp;quot;PT2H30M&amp;quot;, 2 * 60 + 30)],&lt;br /&gt;
 )&lt;br /&gt;
 def test_task_est_to_minutes(test_input: str, expected: int) -&amp;gt; None:&lt;br /&gt;
     assert task_est_to_minutes(test_input) == expected&lt;br /&gt;
&lt;br /&gt;
=====fixures: prepare and cleanup test_data=====&lt;br /&gt;
to automatically run before and after each testcase&lt;br /&gt;
 @pytest.fixture(autouse=True)&lt;br /&gt;
 def _setup_tests():  # noqa: ANN202&lt;br /&gt;
     cache_prepare_lists()&lt;br /&gt;
     cache_prepare_tasks()&lt;br /&gt;
 &lt;br /&gt;
     yield&lt;br /&gt;
 &lt;br /&gt;
     cache_cleanup_test_data()&lt;br /&gt;
&lt;br /&gt;
====Test coverage report====&lt;br /&gt;
 pip install pytest-cov&lt;br /&gt;
 # console output&lt;br /&gt;
 pytest --cov&lt;br /&gt;
 &lt;br /&gt;
 # html report in coverage_report/index.html with details per file&lt;br /&gt;
 pytest --cov --cov-report=html:coverage_report&lt;br /&gt;
to exclude a code block from coverage report, add&lt;br /&gt;
 # pragma: no cover&lt;br /&gt;
&lt;br /&gt;
=== Sleep / Wait for input ===&lt;br /&gt;
sleep for a while&lt;br /&gt;
 import time&lt;br /&gt;
 time.sleep(60)&lt;br /&gt;
&lt;br /&gt;
wait for user input&lt;br /&gt;
 input(&amp;quot;press Enter to close&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
===Suppress Warnings===&lt;br /&gt;
see [https://docs.python.org/3/library/warnings.html#temporarily-suppressing-warnings]&lt;br /&gt;
 import warnings&lt;br /&gt;
 warnings.filterwarnings(&amp;quot;ignore&amp;quot;, message=&amp;quot;.*native_field_num.*not found in message.*&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Logging===&lt;br /&gt;
====V4 minimal stdout logging====&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.addLevelName(logging.DEBUG, &amp;quot;D&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.INFO, &amp;quot;I&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.WARNING, &amp;quot;W&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.ERROR, &amp;quot;E&amp;quot;)&lt;br /&gt;
 logging.addLevelName(logging.CRITICAL, &amp;quot;C&amp;quot;)&lt;br /&gt;
 logging.basicConfig(level=logging.INFO, format=&amp;quot;%(levelname)s: %(name)s: %(message)s&amp;quot;)&lt;br /&gt;
 LOGGER = logging.getLogger(__name__)&lt;br /&gt;
 LOGGER.setLevel(logging.INFO)&lt;br /&gt;
 logging.getLogger(&amp;quot;httpx&amp;quot;).setLevel(logging.WARNING)&lt;br /&gt;
 &lt;br /&gt;
 # usage of variables and rounding of numbers. (%d also rounds floats)&lt;br /&gt;
 LOGGER.info(&amp;quot;file %s has %d lines with avg %.1f char/line&amp;quot;, filename, lines, c_p_l)&lt;br /&gt;
&lt;br /&gt;
====V3 simple logging====&lt;br /&gt;
 # main file:&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.basicConfig(&lt;br /&gt;
     level=logging.INFO,&lt;br /&gt;
     format=&amp;quot;%(asctime)s\t%(levelname)s\t%(name)s\t%(message)s&amp;quot;,&lt;br /&gt;
     handlers=[&lt;br /&gt;
         RotatingFileHandler(&lt;br /&gt;
             Path(__file__).with_suffix(&amp;quot;.log&amp;quot;),&lt;br /&gt;
             maxBytes=10485760,  # 10 MB = 10*1024*1024,&lt;br /&gt;
             backupCount=1,&lt;br /&gt;
             encoding=&amp;quot;utf-8&amp;quot;,&lt;br /&gt;
         )&lt;br /&gt;
     ],&lt;br /&gt;
     datefmt=&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
 logger = logging.getLogger(Path(__file__).stem)&lt;br /&gt;
 logger.info(&amp;quot;Start&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # other files:&lt;br /&gt;
 import logging&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
&lt;br /&gt;
====V2: rotating file and STDOUT====&lt;br /&gt;
 # 1. setup&lt;br /&gt;
 import logging&lt;br /&gt;
 from logging.handlers import RotatingFileHandler&lt;br /&gt;
 &lt;br /&gt;
 logfile = &amp;quot;myApp.log&amp;quot;&lt;br /&gt;
 maxBytes = 20 * 1024 * 1024&lt;br /&gt;
 backupCount = 5&lt;br /&gt;
 loglevel_console = logging.INFO&lt;br /&gt;
 loglevel_file = logging.DEBUG&lt;br /&gt;
 &lt;br /&gt;
 # create logger&lt;br /&gt;
 logger = logging.getLogger(&amp;quot;root&amp;quot;)&lt;br /&gt;
 logger.setLevel(loglevel_file)&lt;br /&gt;
 &lt;br /&gt;
 # console handler&lt;br /&gt;
 ch = logging.StreamHandler()&lt;br /&gt;
 ch.setLevel(loglevel_console)&lt;br /&gt;
 &lt;br /&gt;
 # rotating file handler&lt;br /&gt;
 # fh = logging.FileHandler(logfile)&lt;br /&gt;
 fh = RotatingFileHandler(logfile, maxBytes=maxBytes, backupCount=backupCount)&lt;br /&gt;
 fh.setLevel(loglevel_file)&lt;br /&gt;
 &lt;br /&gt;
 # create formatter and add it to the handlers&lt;br /&gt;
 # %(name)s = LoggerName, %(threadName)s = TreadName&lt;br /&gt;
 formatter = logging.Formatter(&lt;br /&gt;
     &amp;quot;%(asctime)s - %(levelname)s - %(name)s - %(threadName)s - %(message)s &amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 fh.setFormatter(formatter)&lt;br /&gt;
 ch.setFormatter(formatter)&lt;br /&gt;
 &lt;br /&gt;
 # add the handlers to the logger&lt;br /&gt;
 logger.addHandler(ch)&lt;br /&gt;
 logger.addHandler(fh)&lt;br /&gt;
 &lt;br /&gt;
 logger.debug(&amp;quot;DebugMe&amp;quot;)&lt;br /&gt;
 logger.info(&amp;quot;Starting&amp;quot;)&lt;br /&gt;
 logger.warning(&amp;quot;Attention&amp;quot;)&lt;br /&gt;
 logger.error(&amp;quot;Something went wrong&amp;quot;)&lt;br /&gt;
 logger.critical(&amp;quot;Something seriously went wrong &amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 # 2. in other files/modules now use&lt;br /&gt;
 import logging&lt;br /&gt;
 &lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 logger.info(&amp;quot;text&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 ...&lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     logger.exception(&amp;quot;Unhandeled exception&amp;quot;)&lt;br /&gt;
     quit()&lt;br /&gt;
&lt;br /&gt;
====V1: Simple====&lt;br /&gt;
 import logging&lt;br /&gt;
 logging.basicConfig(&lt;br /&gt;
     level=logging.INFO,&lt;br /&gt;
     format=&amp;quot;%(asctime)s\t%(levelname)s\t%(message)s&amp;quot;,  # \t%(name)s&lt;br /&gt;
     filename=Path(__file__).with_suffix(&amp;quot;.log&amp;quot;), # uncomment to switch to stdout&lt;br /&gt;
     filemode=&amp;quot;a&amp;quot;,&lt;br /&gt;
 )&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 logger.debug(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.info(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.warning(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.error(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 logger.critical(&amp;quot;Some text&amp;quot;)&lt;br /&gt;
 try:&lt;br /&gt;
 ...&lt;br /&gt;
 except psycopg2.DatabaseError:&lt;br /&gt;
     logger.exception(&amp;quot;Database connection error: %s&amp;quot;)&lt;br /&gt;
     raise&lt;br /&gt;
&lt;br /&gt;
===Process Bar===&lt;br /&gt;
see [https://tqdm.github.io tqdm]&lt;br /&gt;
 from tqdm import tqdm&lt;br /&gt;
 for i in tqdm(range(10000)):&lt;br /&gt;
     ....&lt;br /&gt;
or&lt;br /&gt;
 with tqdm(total=total_iterations, desc=&amp;quot;Processing&amp;quot;) as progress_bar:&lt;br /&gt;
     ...&lt;br /&gt;
     progress_bar.update(1)&lt;br /&gt;
&lt;br /&gt;
===CGI Web development===&lt;br /&gt;
 # Print necessary headers.&lt;br /&gt;
 print(&amp;quot;Content-Type: text/html&amp;quot;)&lt;br /&gt;
 print()&lt;br /&gt;
 &lt;br /&gt;
 # errors and debugging info to browser&lt;br /&gt;
 import cgitb&lt;br /&gt;
 cgitb.enable()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Access URL or Form Parameters&lt;br /&gt;
 # V2 from https://www.tutorialspoint.com/python/python_cgi_programming.htm&lt;br /&gt;
 import cgi&lt;br /&gt;
 form = cgi.FieldStorage()&lt;br /&gt;
 username = form.getvalue(&#039;username&#039;)&lt;br /&gt;
 print(username)&lt;br /&gt;
&lt;br /&gt;
 # V1&lt;br /&gt;
 import sys&lt;br /&gt;
 import urllib.parse&lt;br /&gt;
 query = os.environ.get(&#039;QUERY_STRING&#039;)&lt;br /&gt;
 query = urllib.parse.unquote(query, errors=&amp;quot;surrogateescape&amp;quot;)&lt;br /&gt;
 d = dict(qc.split(&amp;quot;=&amp;quot;) for qc in query.split(&amp;quot;&amp;amp;&amp;quot;))&lt;br /&gt;
 print(d)&lt;br /&gt;
&lt;br /&gt;
====CGI Backend Returning JSONs====&lt;br /&gt;
 #!/usr/local/bin/python3.6&lt;br /&gt;
 # -*- coding: utf-8 -*-&lt;br /&gt;
 &lt;br /&gt;
 import cgi&lt;br /&gt;
 import json&lt;br /&gt;
 &lt;br /&gt;
 # Print necessary headers.&lt;br /&gt;
 print(&amp;quot;Content-type: application/json&amp;quot;)&lt;br /&gt;
 print()&lt;br /&gt;
 &lt;br /&gt;
 def get_form_parameter(para: str) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;asserts that a given parameter is set and returns its value&amp;quot;&lt;br /&gt;
     value = form.getvalue(para)&lt;br /&gt;
     assert value, f&amp;quot;Error: parameter {para} missing&amp;quot;&lt;br /&gt;
     assert value != &amp;quot;&amp;quot;, f&amp;quot;Error: parameter {para} missing&amp;quot;&lt;br /&gt;
     return value&lt;br /&gt;
  &lt;br /&gt;
 response = {}&lt;br /&gt;
 response[&#039;status&#039;] = &amp;quot;ok&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 try:&lt;br /&gt;
     action = get_form_parameter(&amp;quot;action&amp;quot;)&lt;br /&gt;
     response[&#039;action&#039;] = action&lt;br /&gt;
     if action == &amp;quot;myAction&amp;quot;:&lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     response[&#039;status&#039;] = &amp;quot;error&amp;quot;&lt;br /&gt;
     d = {&amp;quot;type&amp;quot;: str(type(e)), &amp;quot;text&amp;quot;: str(e)}&lt;br /&gt;
     response[&amp;quot;exception&amp;quot;] = d&lt;br /&gt;
 &lt;br /&gt;
 finally:&lt;br /&gt;
     print(json.dumps(response))&lt;br /&gt;
&lt;br /&gt;
==Databases==&lt;br /&gt;
===PostgreSQL===&lt;br /&gt;
====Improved helper function====&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Helper functions for DB access.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 import datetime as dt&lt;br /&gt;
 import logging&lt;br /&gt;
 &lt;br /&gt;
 import psycopg2&lt;br /&gt;
 import psycopg2.extras&lt;br /&gt;
 from creds_db import credentials as creds_db&lt;br /&gt;
 &lt;br /&gt;
 # Configure logging&lt;br /&gt;
 logger = logging.getLogger(__name__)&lt;br /&gt;
 &lt;br /&gt;
 connection: psycopg2.extensions.connection = None  # type: ignore&lt;br /&gt;
 cursor_query: psycopg2.extensions.cursor = None  # type: ignore&lt;br /&gt;
 cursor_write: psycopg2.extensions.cursor = None  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def connect() -&amp;gt; (&lt;br /&gt;
     tuple[&lt;br /&gt;
         psycopg2.extensions.connection,&lt;br /&gt;
         psycopg2.extensions.cursor,&lt;br /&gt;
         psycopg2.extensions.cursor,&lt;br /&gt;
     ]&lt;br /&gt;
 ):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Connect to the database.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     try:&lt;br /&gt;
         connection = psycopg2.connect(**creds_db)&lt;br /&gt;
         cursor_query = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)&lt;br /&gt;
         cursor_write = connection.cursor()&lt;br /&gt;
         logger.info(&lt;br /&gt;
             &amp;quot;Connected to database %s on host %s&amp;quot;,&lt;br /&gt;
             creds_db[&amp;quot;database&amp;quot;],&lt;br /&gt;
             creds_db[&amp;quot;host&amp;quot;],&lt;br /&gt;
         )&lt;br /&gt;
     except psycopg2.DatabaseError:&lt;br /&gt;
         logger.exception(&amp;quot;Database connection error: %s&amp;quot;)&lt;br /&gt;
         raise&lt;br /&gt;
     else:&lt;br /&gt;
         return connection, cursor_query, cursor_write&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def disconnect() -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Disconnect from the database.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     try:&lt;br /&gt;
         if cursor_query:&lt;br /&gt;
             cursor_query.close()&lt;br /&gt;
         if cursor_write:&lt;br /&gt;
             cursor_write.close()&lt;br /&gt;
         if connection:&lt;br /&gt;
             connection.close()&lt;br /&gt;
         logger.info(&amp;quot;Disconnected from the database&amp;quot;)&lt;br /&gt;
     except psycopg2.DatabaseError:&lt;br /&gt;
         logger.exception(&amp;quot;Error during disconnection: %s&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def exists(url: str, valid_hours: float) -&amp;gt; bool | None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     Check if a record exists.&lt;br /&gt;
 &lt;br /&gt;
     Returns&lt;br /&gt;
     -------&lt;br /&gt;
     None for missing record&lt;br /&gt;
     True for valid record&lt;br /&gt;
     False for expired record&lt;br /&gt;
 &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=valid_hours)&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT delete_at &amp;gt; %s AS &amp;quot;valid&amp;quot;&lt;br /&gt;
 FROM my_table&lt;br /&gt;
 WHERE url = %s LIMIT 1;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_query.execute(sql, (delete_at, url))&lt;br /&gt;
     res = cursor_query.fetchone()&lt;br /&gt;
     return res[0] if res else None&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def query1(url: str) -&amp;gt; dict | None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Execute a query.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT *&lt;br /&gt;
 FROM my_table&lt;br /&gt;
 WHERE url = %s LIMIT 1&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_query.execute(sql, (url,))&lt;br /&gt;
     return cursor_query.fetchone()  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def insert(url: str, response: str, delete_in_hours: float) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Insert a record.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=delete_in_hours)&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 INSERT INTO my_table (url, response, delete_at)&lt;br /&gt;
 VALUES (%s, %s, %s);&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (url, response, delete_at))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def delete(url: str) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Delete a record.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 DELETE FROM my_table&lt;br /&gt;
 WHERE url = %s;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (url,))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def delete_expired(in_hours: float) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Delete records that expire in less than in_hours from now.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     delete_at = dt.datetime.now(tz=dt.UTC) + dt.timedelta(hours=in_hours)&lt;br /&gt;
 &lt;br /&gt;
     sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 DELETE FROM my_table&lt;br /&gt;
 WHERE delete_at &amp;lt; %s;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cursor_write.execute(sql, (delete_at,))&lt;br /&gt;
     connection.commit()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 connection, cursor_query, cursor_write = connect()&lt;br /&gt;
&lt;br /&gt;
====Basics====&lt;br /&gt;
 import psycopg2&lt;br /&gt;
 import psycopg2.extras&lt;br /&gt;
 &lt;br /&gt;
 credentials = {&lt;br /&gt;
     &amp;quot;host&amp;quot;: &amp;quot;localhost&amp;quot;,&lt;br /&gt;
     &amp;quot;port&amp;quot;: 5432,&lt;br /&gt;
     &amp;quot;database&amp;quot;: &amp;quot;myDB&amp;quot;,&lt;br /&gt;
     &amp;quot;user&amp;quot;: &amp;quot;myUser&amp;quot;,&lt;br /&gt;
     &amp;quot;password&amp;quot;: &amp;quot;myPwd&amp;quot;,&lt;br /&gt;
 }&lt;br /&gt;
 connection = psycopg2.connect(**credentials)&lt;br /&gt;
 cursor = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)&lt;br /&gt;
 &lt;br /&gt;
 l_bind_vars = [[&amp;quot;A1&amp;quot;, &amp;quot;A2&amp;quot;], [&amp;quot;B1&amp;quot;, &amp;quot;B2&amp;quot;]]&lt;br /&gt;
 &lt;br /&gt;
 sql = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT * FROM myTable&lt;br /&gt;
 WHERE 1=1 &lt;br /&gt;
 AND status NOT IN (&#039;CLOSED&#039;) &lt;br /&gt;
 AND ColA = %s &lt;br /&gt;
 AND ColB = %s &lt;br /&gt;
 ORDER BY created DESC&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 cursor.execute(sql, l_bind_vars)&lt;br /&gt;
 d_data = dict(cursor.fetchone())&lt;br /&gt;
&lt;br /&gt;
====export result to csv file====&lt;br /&gt;
 sql1 = &amp;quot;SELECT * FROM table&amp;quot;&lt;br /&gt;
 sql2 = &amp;quot;COPY (&amp;quot; + sql1 + &amp;quot;) TO STDOUT WITH CSV HEADER DELIMITER &#039;\t&#039;&amp;quot;&lt;br /&gt;
         with open(&amp;quot;out.csv&amp;quot;, &amp;quot;w&amp;quot;) as file:&lt;br /&gt;
             cursor.copy_expert(sql2, file)&lt;br /&gt;
&lt;br /&gt;
====ReadConfigFile to connect to PostgreSQL====&lt;br /&gt;
from [http://www.postgresqltutorial.com/postgresql-python/connect/]&lt;br /&gt;
database.ini&lt;br /&gt;
 [postgresql]&lt;br /&gt;
 host=dbhost&lt;br /&gt;
 port=5432&lt;br /&gt;
 database=dbname&lt;br /&gt;
 user=dbuser&lt;br /&gt;
 password=dbpass&lt;br /&gt;
&lt;br /&gt;
 from configparser import ConfigParser&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def config(filename=&amp;quot;database.ini&amp;quot;, section=&amp;quot;postgresql&amp;quot;):&lt;br /&gt;
     parser = ConfigParser()&lt;br /&gt;
     parser.read(filename)&lt;br /&gt;
     # get section, default to postgresql&lt;br /&gt;
     db = {}&lt;br /&gt;
     if parser.has_section(section):&lt;br /&gt;
         params = parser.items(section)&lt;br /&gt;
         for param in params:&lt;br /&gt;
             db[param[0]] = param[1]&lt;br /&gt;
     else:&lt;br /&gt;
         raise Exception(&lt;br /&gt;
             &amp;quot;Section {0} not found in the {1} file&amp;quot;.format(section, filename)&lt;br /&gt;
         )&lt;br /&gt;
     return db&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
main.py&lt;br /&gt;
 import psycopg2&lt;br /&gt;
 from config import config&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def connect():&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Connect to the PostgreSQL database server&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     conn = None&lt;br /&gt;
     try:&lt;br /&gt;
         params = config()  # read connection parameters&lt;br /&gt;
         print(&amp;quot;Connecting to the PostgreSQL database...&amp;quot;)&lt;br /&gt;
         conn = psycopg2.connect(**params)&lt;br /&gt;
         cur = conn.cursor()  # create a cursor&lt;br /&gt;
         print(&amp;quot;PostgreSQL database version:&amp;quot;)&lt;br /&gt;
         cur.execute(&amp;quot;SELECT version()&amp;quot;)  # execute a statement&lt;br /&gt;
         db_version = cur.fetchone()&lt;br /&gt;
         print(db_version)&lt;br /&gt;
         cur.close()&lt;br /&gt;
     except (Exception, psycopg2.DatabaseError) as error:&lt;br /&gt;
         print(error)&lt;br /&gt;
     finally:&lt;br /&gt;
         if conn is not None:&lt;br /&gt;
             conn.close()&lt;br /&gt;
             print(&amp;quot;Database connection closed.&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     connect()&lt;br /&gt;
&lt;br /&gt;
===SQL Lite / SQLite===&lt;br /&gt;
see page [[SQLite]]&lt;br /&gt;
&lt;br /&gt;
===InfluxDB===&lt;br /&gt;
see [[InfluxDB]]&lt;br /&gt;
&lt;br /&gt;
==Internet Access==&lt;br /&gt;
===Send E-Mails===&lt;br /&gt;
see [[Python - eMail]]&lt;br /&gt;
&lt;br /&gt;
===Download data using browser UA / REST===&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 headers = {&lt;br /&gt;
     &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0&amp;quot;,&lt;br /&gt;
 }&lt;br /&gt;
 resp = requests.get(&lt;br /&gt;
     url,&lt;br /&gt;
     headers=headers,&lt;br /&gt;
     timeout=3,  # timeout in sec, requests should always have a timeout!&lt;br /&gt;
     # timeout=(1,3), # 1s connect-timout, 3s read-timeout&lt;br /&gt;
 )&lt;br /&gt;
 if resp.status_code != 200:  # noqa: PLR2004&lt;br /&gt;
     msg = f&amp;quot;E: bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
     raise Exception(msg)  # noqa: TRY002&lt;br /&gt;
&lt;br /&gt;
====Download only if cache is too old====&lt;br /&gt;
 import time&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 def fetch_url_or_cache(file_cache: Path, url) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Fetch URL and cache in a file.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if check_cache_file_available_and_recent(&lt;br /&gt;
         file_path=file_cache, max_age=3600, verbose=False&lt;br /&gt;
     ):&lt;br /&gt;
         with file_cache.open(mode=&amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as fh:&lt;br /&gt;
             s = fh.read()&lt;br /&gt;
     else:&lt;br /&gt;
         s = fetch(url=url)&lt;br /&gt;
         with file_cache.open(mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fh:&lt;br /&gt;
             fh.writelines(s)&lt;br /&gt;
     return s&lt;br /&gt;
 &lt;br /&gt;
 def check_cache_file_available_and_recent(&lt;br /&gt;
     file_path: Path,&lt;br /&gt;
     max_age: int = 3500,&lt;br /&gt;
 ) -&amp;gt; bool:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Check if cache file exists and is recent.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cache_good = False&lt;br /&gt;
     if file_path.exists() and (time.time() - file_path.stat().st_mtime &amp;lt; max_age):&lt;br /&gt;
         cache_good = True&lt;br /&gt;
     return cache_good&lt;br /&gt;
 &lt;br /&gt;
 # or&lt;br /&gt;
 def check_cache_file_available_and_recent_verbose(&lt;br /&gt;
     file_path: Path, max_age: int = 3600, *, verbose: bool = False&lt;br /&gt;
 ) -&amp;gt; bool:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Check if cache file exists and is recent.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     cache_good = True&lt;br /&gt;
     if not file_path.exists():&lt;br /&gt;
         if verbose:&lt;br /&gt;
             print(f&amp;quot;No Cache available: {file_path}&amp;quot;)&lt;br /&gt;
         cache_good = False&lt;br /&gt;
     if cache_good and time.time() - (time.time() - file_path.stat().st_mtime &amp;lt; max_age):&lt;br /&gt;
         if verbose:&lt;br /&gt;
             print(f&amp;quot;Cache too old: {file_path}&amp;quot;)&lt;br /&gt;
         cache_good = False&lt;br /&gt;
     return cache_good&lt;br /&gt;
 &lt;br /&gt;
 def fetch(url) -&amp;gt; str:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Fetch URL.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     headers = {&lt;br /&gt;
         &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0&amp;quot;,  # noqa: E501&lt;br /&gt;
     }&lt;br /&gt;
     resp = requests.get(url, headers=headers, timeout=5)&lt;br /&gt;
     if resp.status_code == 200:  # noqa: PLR2004&lt;br /&gt;
         return resp.content.decode(&amp;quot;ascii&amp;quot;)&lt;br /&gt;
     else:  # noqa: RET505&lt;br /&gt;
         msg = f&amp;quot;E: bad response. status code:{resp.status_code}, text:\n{resp.text}&amp;quot;&lt;br /&gt;
         raise Exception(msg)  # noqa: TRY002&lt;br /&gt;
&lt;br /&gt;
====Download using urllib.request====&lt;br /&gt;
 from urllib.request import urlopen&lt;br /&gt;
 &lt;br /&gt;
 url = &amp;quot;https://pomber.github.io/covid19/timeseries.json&amp;quot;&lt;br /&gt;
 with urlopen(url) as response:  # noqa: S310&lt;br /&gt;
     response_text = response.read()&lt;br /&gt;
&lt;br /&gt;
===HTML parsing and extracting of elements===&lt;br /&gt;
V2: via BeautifulSoup&lt;br /&gt;
 from bs4 import BeautifulSoup  # pip install beautifulsoup4&lt;br /&gt;
 &lt;br /&gt;
 soup = BeautifulSoup(cont, features=&amp;quot;html.parser&amp;quot;)&lt;br /&gt;
 my_element = soup.find(&amp;quot;div&amp;quot;, {&amp;quot;class&amp;quot;: &amp;quot;user-formatted-inner&amp;quot;})&lt;br /&gt;
 my_body = my_element.prettify()&lt;br /&gt;
 # my_body = my_element.encode()&lt;br /&gt;
 # my_body = str(my_element)&lt;br /&gt;
&lt;br /&gt;
V1: via lxml and xpath&lt;br /&gt;
 from lxml import html&lt;br /&gt;
 import requests&lt;br /&gt;
 &lt;br /&gt;
 page = requests.get(url)&lt;br /&gt;
 tree = html.fromstring(page.content)&lt;br /&gt;
 tbody_trs = tree.xpath(&amp;quot;//*/tbody/tr&amp;quot;)&lt;br /&gt;
 l_rows = []&lt;br /&gt;
 for tr in tbody_trs:&lt;br /&gt;
     l_columns = []&lt;br /&gt;
     if len(tr) != 15:&lt;br /&gt;
         continue&lt;br /&gt;
     for td in tr:&lt;br /&gt;
         l_columns.append(td.text_content())&lt;br /&gt;
         l_rows.append(list(l_columns))&lt;br /&gt;
&lt;br /&gt;
===HTML entities to unicode===&lt;br /&gt;
 import html&lt;br /&gt;
 cont = html.unescape(cont)&lt;br /&gt;
&lt;br /&gt;
==GUI Interactions==&lt;br /&gt;
===Take Screenshot===&lt;br /&gt;
 import pyautogui # (c:\Python\Scripts\)pip install pyautogui&lt;br /&gt;
 # pyautogui does only support screenshots on monitor #1&lt;br /&gt;
 ...&lt;br /&gt;
 screenshot = pyautogui.screenshot()&lt;br /&gt;
 # screenshot = pyautogui.screenshot(region=(screenshotX,screenshotY, screenshotW, screenshotH))&lt;br /&gt;
 screenshot = np.array(screenshot) &lt;br /&gt;
 # Convert RGB to BGR &lt;br /&gt;
 screenshot = screenshot[:, :, ::-1].copy()&lt;br /&gt;
&lt;br /&gt;
===Mouse Actions===&lt;br /&gt;
 def clickIt(x,y,key=&amp;quot;&amp;quot;) :&lt;br /&gt;
   x0, y0 = pyautogui.position()&lt;br /&gt;
   if key != &amp;quot;&amp;quot;: # crtl, shift&lt;br /&gt;
     pyautogui.keyDown(key)&lt;br /&gt;
   pyautogui.moveTo(x, y, duration=0.2)&lt;br /&gt;
   pyautogui.click(x=x , y=y, button=&#039;left&#039;, clicks=1, interval=0.1)&lt;br /&gt;
   if key != &amp;quot;&amp;quot;: # crtl, shift&lt;br /&gt;
     pyautogui.keyUp(key)&lt;br /&gt;
   pyautogui.moveTo(x0, y0)&lt;br /&gt;
&lt;br /&gt;
===Web Automation===&lt;br /&gt;
 from selenium import webdriver&lt;br /&gt;
 from selenium.webdriver.common.keys import Keys&lt;br /&gt;
 &lt;br /&gt;
 # from selenium.webdriver import Firefox&lt;br /&gt;
 from selenium.webdriver.firefox.options import Options&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 import glob&lt;br /&gt;
 &lt;br /&gt;
 class StravaUserMapDL():&lt;br /&gt;
     def __init__(self):&lt;br /&gt;
         self.driver = webdriver.Firefox()&lt;br /&gt;
 &lt;br /&gt;
     def login(self):&lt;br /&gt;
         driver = self.driver&lt;br /&gt;
         url = &amp;quot;https://www.somewebpage.com&amp;quot;&lt;br /&gt;
         email = &amp;quot;myemail&amp;quot;&lt;br /&gt;
         password = &amp;quot;mypassword&amp;quot;&lt;br /&gt;
         driver.get(url)&lt;br /&gt;
 &lt;br /&gt;
         title = driver.title&lt;br /&gt;
         urlIs = driver.current_url&lt;br /&gt;
         cont = driver.page_source #  as string&lt;br /&gt;
         FILE = open(filename,&amp;quot;w&amp;quot;) # w = overWrite file ; a = append to file&lt;br /&gt;
         FILE.write(cont)&lt;br /&gt;
         FILE.close()         &lt;br /&gt;
 &lt;br /&gt;
         # handle login if urlIs != url&lt;br /&gt;
         if (urlIs != url): &lt;br /&gt;
             # activate checkbox &#039;remember_me&#039;&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;remember_me&#039;)&lt;br /&gt;
             if (elem.is_selected() == False):&lt;br /&gt;
                 elem.click()&lt;br /&gt;
             assert elem.is_selected() == True&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;email&#039;)&lt;br /&gt;
             elem.send_keys(email)&lt;br /&gt;
             elem = driver.find_element_by_id(&#039;password&#039;)&lt;br /&gt;
             elem.send_keys(password)&lt;br /&gt;
             elem.send_keys(Keys.RETURN)&lt;br /&gt;
             # Wait until login pages is replaced by real page&lt;br /&gt;
             urlIs = driver.current_url&lt;br /&gt;
             while (urlIs != url):&lt;br /&gt;
                 time.sleep(1)&lt;br /&gt;
                 urlIs = driver.current_url&lt;br /&gt;
             print (urlIs)&lt;br /&gt;
 &lt;br /&gt;
             # results = driver.find_elements_by_class_name(&#039;following&#039;)&lt;br /&gt;
             # results = driver.find_elements_by_tag_name(&#039;li&#039;)&lt;br /&gt;
 &lt;br /&gt;
             # print(results[0].text)&lt;br /&gt;
         assert (urlIs == url)&lt;br /&gt;
&lt;br /&gt;
===Unit Tests using Web Automation===&lt;br /&gt;
 import unittest&lt;br /&gt;
 from selenium import webdriver&lt;br /&gt;
 from selenium.webdriver.common.keys import Keys&lt;br /&gt;
 &lt;br /&gt;
 #from selenium.webdriver import Firefox&lt;br /&gt;
 from selenium.webdriver.firefox.options import Options&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 class PythonOrgSearch(unittest.TestCase):&lt;br /&gt;
 #    def __init__(self,asdf):&lt;br /&gt;
 #        self.driver = webdriver.Firefox() &lt;br /&gt;
 &lt;br /&gt;
     def setUp(self):&lt;br /&gt;
         print (&amp;quot;setUp&amp;quot;)&lt;br /&gt;
         # headless mode:&lt;br /&gt;
         # opts = Options()&lt;br /&gt;
         # opts.set_headless()&lt;br /&gt;
         # assert opts.headless  # Operating in headless mode&lt;br /&gt;
         # self.driver = webdriver.Firefox(options=opts)&lt;br /&gt;
 &lt;br /&gt;
         self.driver = webdriver.Firefox()&lt;br /&gt;
 &lt;br /&gt;
     def test_search_in_python_org(self):&lt;br /&gt;
         driver = self.driver&lt;br /&gt;
         driver.get(&amp;quot;http://www.python.org&amp;quot;)&lt;br /&gt;
         self.assertIn(&amp;quot;Python&amp;quot;, driver.title)&lt;br /&gt;
         elem = driver.find_element_by_name(&amp;quot;q&amp;quot;)&lt;br /&gt;
         elem.send_keys(&amp;quot;pycon&amp;quot;)&lt;br /&gt;
         elem.send_keys(Keys.RETURN)&lt;br /&gt;
         assert &amp;quot;No results found.&amp;quot; not in driver.page_source&lt;br /&gt;
         print (&amp;quot;fertig: python_org&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     def tearDown(self):&lt;br /&gt;
         print (&amp;quot;tearDown&amp;quot;)&lt;br /&gt;
         print (&amp;quot;close Firefox&amp;quot;)&lt;br /&gt;
         self.driver.close() # close tab&lt;br /&gt;
         self.driver.quit() # quit browser&lt;br /&gt;
         # os._exit(1) # exit unittest without Exception&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     try:&lt;br /&gt;
         unittest.main()&lt;br /&gt;
     except SystemExit as e:&lt;br /&gt;
         os._exit(1)&lt;br /&gt;
&lt;br /&gt;
==Cryptography and Hashing==&lt;br /&gt;
====Hashing via SHA256====&lt;br /&gt;
 def gen_SHA256_string(s: str) -&amp;gt; str:&lt;br /&gt;
     m = hashlib.sha256()&lt;br /&gt;
     m.update(s.encode(&amp;quot;ascii&amp;quot;))&lt;br /&gt;
     return m.hexdigest()&lt;br /&gt;
&lt;br /&gt;
====Hashing via MD5====&lt;br /&gt;
(MD5 is not secure, better use SHA256)&lt;br /&gt;
 def gen_MD5_string(s: str) -&amp;gt; str:&lt;br /&gt;
     m = hashlib.md5()&lt;br /&gt;
     m.update(s.encode(&amp;quot;ascii&amp;quot;))&lt;br /&gt;
     return m.hexdigest()&lt;br /&gt;
&lt;br /&gt;
====Password hashing via bcrypt====&lt;br /&gt;
 import bcrypt&lt;br /&gt;
 pwd = &#039;geheim&#039;&lt;br /&gt;
 pwd = pwd.encode(&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 # or &lt;br /&gt;
 pwd = b&#039;geheim&#039;&lt;br /&gt;
 &lt;br /&gt;
 hashed = bcrypt.hashpw(pwd, bcrypt.gensalt())&lt;br /&gt;
 if bcrypt.checkpw(pwd, hashed):&lt;br /&gt;
     print(&amp;quot;It Matches!&amp;quot;)&lt;br /&gt;
     print(hashed.decode(&amp;quot;utf-8&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
To use version 2a instead of 2b (default):&lt;br /&gt;
 bcrypt.gensalt(prefix=b&amp;quot;2a&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Multiprocessing, subprocesses and Threading==&lt;br /&gt;
see [[Python_-_Multithreading]] as well&lt;br /&gt;
&lt;br /&gt;
use processes for CPU limited work &amp;lt;br/&amp;gt;&lt;br /&gt;
use threads for I/O limited work&lt;br /&gt;
&lt;br /&gt;
===Simple single process===&lt;br /&gt;
 import subprocess&lt;br /&gt;
 process = subprocess.run([&amp;quot;sudo&amp;quot;, &amp;quot;du&amp;quot;, &amp;quot;--max-depth=1&amp;quot;, mydir], capture_output=True, text=True)&lt;br /&gt;
 print (process.stdout)&lt;br /&gt;
old, depricated way:&lt;br /&gt;
 os.system( &amp;quot;gnuplot &amp;quot; + gpfile)&lt;br /&gt;
&lt;br /&gt;
===Multiprocessing===&lt;br /&gt;
see [[Python - Multithreading]] as well&lt;br /&gt;
&lt;br /&gt;
V2 using pool and starmap&lt;br /&gt;
 import multiprocessing&lt;br /&gt;
 import os&lt;br /&gt;
 &lt;br /&gt;
 def worker(i: int, s: str) -&amp;gt; list:&lt;br /&gt;
     result = (i, s, os.getpid())&lt;br /&gt;
     return result&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     # gen. pile of work&lt;br /&gt;
     l_pile_of_work = []&lt;br /&gt;
     for i in range(1_000):&lt;br /&gt;
         tup = (i, &amp;quot;n&amp;quot; + str(i))&lt;br /&gt;
         l_pile_of_work.append((tup))&lt;br /&gt;
     # gen pool of processes&lt;br /&gt;
     num_processes = min(multiprocessing.cpu_count(), len(l_pile_of_work))&lt;br /&gt;
     pool = multiprocessing.Pool(processes=num_processes)&lt;br /&gt;
     # start processes on pile of work&lt;br /&gt;
     l_results_unsorted = pool.starmap(&lt;br /&gt;
         func=worker, iterable=l_pile_of_work  # each item is a list of 2 parameters&lt;br /&gt;
     )&lt;br /&gt;
     &lt;br /&gt;
     # or if only one parameter:&lt;br /&gt;
     # l_results_unsorted = pool.map(doit_de_district, l_pile_of_work)&lt;br /&gt;
     &lt;br /&gt;
     l_results = sorted(l_results_unsorted)  # sort by i&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
V1&lt;br /&gt;
 import subprocess&lt;br /&gt;
 l_subprocesses = []  # queue list of subprocesses&lt;br /&gt;
 max_processes = 4&lt;br /&gt;
 &lt;br /&gt;
 def process_enqueue(new_process_parameters):&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     # wait for free slot&lt;br /&gt;
     while len(l_subprocesses) &amp;gt;= max_processes:&lt;br /&gt;
         process_remove_finished_from_queue()&lt;br /&gt;
         time.sleep(0.1)  # sleep 0.1s&lt;br /&gt;
     process = subprocess.Popen(new_process_parameters,&lt;br /&gt;
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE,&lt;br /&gt;
                                universal_newlines=True)&lt;br /&gt;
     l_subprocesses.append(process)&lt;br /&gt;
 &lt;br /&gt;
 def process_remove_finished_from_queue():&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     i = 0&lt;br /&gt;
     while i &amp;lt;= len(l_subprocesses) - 1:&lt;br /&gt;
         process = l_subprocesses[i]&lt;br /&gt;
         if process.poll != None:  # has already finished&lt;br /&gt;
             process_print_output(process)&lt;br /&gt;
             l_subprocesses.pop(i)&lt;br /&gt;
         else:  # still running&lt;br /&gt;
             i += 1&lt;br /&gt;
 &lt;br /&gt;
 def process_print_output(process):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;waits for process to finish and prints process output&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     stdout, stderr = process.communicate()&lt;br /&gt;
     if stdout != &#039;&#039;:&lt;br /&gt;
         print(f&#039;Out: {stdout}&#039;)&lt;br /&gt;
     if stderr != &#039;&#039;:&lt;br /&gt;
         print(f&#039;ERROR: {stderr}&#039;)&lt;br /&gt;
 &lt;br /&gt;
 def process_wait_for_all_finished():&lt;br /&gt;
     global l_subprocesses&lt;br /&gt;
     for process in l_subprocesses:&lt;br /&gt;
         process_print_output(process)&lt;br /&gt;
     l_subprocesses = []  # empty list of done subprocesses&lt;br /&gt;
 &lt;br /&gt;
 process_enqueue(l_parameters1)&lt;br /&gt;
 ...&lt;br /&gt;
 process_enqueue(l_parameters999)&lt;br /&gt;
 process_wait_for_all_finished()&lt;br /&gt;
&lt;br /&gt;
===Threading===&lt;br /&gt;
 import threading&lt;br /&gt;
 import queue&lt;br /&gt;
 import os&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 def worker(q_work: queue.Queue, results: dict):&lt;br /&gt;
     while not q_work.empty():&lt;br /&gt;
         i, s = q_work.get()&lt;br /&gt;
         time.sleep(.1)&lt;br /&gt;
         result = (i, s, os.getpid())&lt;br /&gt;
         results[i] = result&lt;br /&gt;
         q_work.task_done()&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &#039;__main__&#039;:&lt;br /&gt;
     d_results = {}  # threads can write into dict&lt;br /&gt;
     # gen. pile of work&lt;br /&gt;
     l_pile_of_work = []&lt;br /&gt;
     for i in range(1_000):&lt;br /&gt;
         tup = (i, &amp;quot;n&amp;quot;+str(i))&lt;br /&gt;
         l_pile_of_work.append((tup))&lt;br /&gt;
     # convert list of work to queue&lt;br /&gt;
     q_pile_of_work = queue.Queue(&lt;br /&gt;
         maxsize=len(l_pile_of_work))  # maxsize=0 -&amp;gt; unlimited&lt;br /&gt;
     for params in l_pile_of_work:&lt;br /&gt;
         q_pile_of_work.put(params)&lt;br /&gt;
     # gen threads&lt;br /&gt;
     num_threads = 100&lt;br /&gt;
     l_threads = []  # List of threads, not used here&lt;br /&gt;
     for i in range(num_threads):&lt;br /&gt;
         t = threading.Thread(name=&#039;myThread-&#039;+str(i),&lt;br /&gt;
                              target=worker,&lt;br /&gt;
                              args=(q_pile_of_work, d_results),&lt;br /&gt;
                              daemon=True)&lt;br /&gt;
         l_threads.append(t)&lt;br /&gt;
         t.start()&lt;br /&gt;
     q_pile_of_work.join()  # wait for all threas to complete&lt;br /&gt;
     l_results_unsorted = d_results.values()&lt;br /&gt;
     l_results = sorted(l_results_unsorted)  # sort by i&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===asyncio — Asynchronous I/O===&lt;br /&gt;
see https://docs.python.org/3/library/asyncio-task.html&lt;br /&gt;
 import asyncio&lt;br /&gt;
 import time&lt;br /&gt;
 &lt;br /&gt;
 # basics&lt;br /&gt;
 # task = asyncio.create_task(coro())&lt;br /&gt;
 # Wrap the coro coroutine into a Task and schedule its execution. Return the Task object.&lt;br /&gt;
 &lt;br /&gt;
 # sleep&lt;br /&gt;
 # await asyncio.sleep(1)&lt;br /&gt;
 &lt;br /&gt;
 # Running Tasks Concurrently and gathers the return values in list L&lt;br /&gt;
 # L = await asyncio.gather(coro(x1,y1), coro(x2,y2), coro(x3,y3))&lt;br /&gt;
 &lt;br /&gt;
 async def say_after(delay, what):&lt;br /&gt;
     # Coroutines declared with the async/await syntax&lt;br /&gt;
     await asyncio.sleep(delay)&lt;br /&gt;
     print(what)&lt;br /&gt;
 &lt;br /&gt;
 async def main():&lt;br /&gt;
     # The asyncio.create_task() function to run coroutines concurrently as asyncio Tasks.&lt;br /&gt;
     task1 = asyncio.create_task(&lt;br /&gt;
         say_after(1, &#039;hello&#039;))&lt;br /&gt;
 &lt;br /&gt;
     task2 = asyncio.create_task(&lt;br /&gt;
         say_after(1, &#039;world&#039;))&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;started at {time.strftime(&#039;%X&#039;)}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     # Wait until both tasks are completed&lt;br /&gt;
     await task1&lt;br /&gt;
     await task2&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;finished at {time.strftime(&#039;%X&#039;)}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 asyncio.run(main())&lt;br /&gt;
&lt;br /&gt;
==Pandas==&lt;br /&gt;
see [[Pandas]]&lt;br /&gt;
&lt;br /&gt;
==Matplotlib==&lt;br /&gt;
see [[Matplotlib]]&lt;br /&gt;
&lt;br /&gt;
== GUI via tkinter==&lt;br /&gt;
 import tkinter as tk  # no need to install via pip&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 class App(tk.Tk):&lt;br /&gt;
     def __init__(self):&lt;br /&gt;
         super().__init__()&lt;br /&gt;
         self.title(&amp;quot;robot-CClicker&amp;quot;)&lt;br /&gt;
         self.geometry(&amp;quot;200x200+0+1080&amp;quot;)&lt;br /&gt;
         self.resizable(width=False, height=False)&lt;br /&gt;
 &lt;br /&gt;
         self.l_buttons = []&lt;br /&gt;
         self.__create_widgets()&lt;br /&gt;
 &lt;br /&gt;
     def __create_widgets(self):&lt;br /&gt;
         self.btn_click500 = tk.Button(&lt;br /&gt;
             master=self,&lt;br /&gt;
             text=&amp;quot;500 clicks&amp;quot;,&lt;br /&gt;
             width=20,&lt;br /&gt;
             command=lambda: self.clickBigCookie(500),&lt;br /&gt;
         )&lt;br /&gt;
         self.l_buttons.append(self.btn_click500)&lt;br /&gt;
 &lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button.pack(anchor=tk.W)&lt;br /&gt;
 &lt;br /&gt;
     def clickBigCookie(self, num):&lt;br /&gt;
         # TODO: the disabling of the buttons is not working&lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button[&amp;quot;state&amp;quot;] = tk.DISABLED&lt;br /&gt;
         helper.clickIt(self.posBigCockie[0], self.posBigCockie[1], num=num)&lt;br /&gt;
         for button in self.l_buttons:&lt;br /&gt;
             button[&amp;quot;state&amp;quot;] = tk.NORMAL&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     app = App()&lt;br /&gt;
     app.mainloop()&lt;br /&gt;
&lt;br /&gt;
==Protobuf==&lt;br /&gt;
===convert .proto file to python class===&lt;br /&gt;
see https://www.datascienceblog.net/post/programming/essential-protobuf-guide-python/&lt;br /&gt;
 protoc my_message.proto --python_out ./ &lt;br /&gt;
&lt;br /&gt;
===read/decode protobuf message===&lt;br /&gt;
parse message from file&lt;br /&gt;
 import machine_message_pb2&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.bin&amp;quot;, &amp;quot;rb&amp;quot;) as f:&lt;br /&gt;
     my_message = my_message_pb2.my_message()&lt;br /&gt;
     my_message.ParseFromString(f.read())&lt;br /&gt;
 print(machine_message)&lt;br /&gt;
&lt;br /&gt;
===create/encode protobuf message===&lt;br /&gt;
 import machine_message_pb2&lt;br /&gt;
 &lt;br /&gt;
 my_message = my_message_pb2.my_message()&lt;br /&gt;
 my_message.data.field1 = 1&lt;br /&gt;
 my_message.data.field2 = &amp;quot;asdf&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.bin&amp;quot;, &amp;quot;wb&amp;quot;) as f:&lt;br /&gt;
     f.write(my_message.data.SerializeToString())&lt;br /&gt;
&lt;br /&gt;
==venv / virtual environments==&lt;br /&gt;
 pip install --upgrade pip&lt;br /&gt;
 python -m venv .venv --prompt $(basename $(pwd))&lt;br /&gt;
 source .venv/bin/activate&lt;br /&gt;
 pip install -r requirements.txt&lt;br /&gt;
 ...&lt;br /&gt;
 deactivate&lt;br /&gt;
&lt;br /&gt;
==Pydantic data validation==&lt;br /&gt;
===Function input parameter validation===&lt;br /&gt;
see https://docs.pydantic.dev/usage/validation_decorator/&lt;br /&gt;
 @validate_arguments&lt;br /&gt;
 def sum(x: int, y: float) -&amp;gt; float:&lt;br /&gt;
     print(x, y)&lt;br /&gt;
     return x + y&lt;br /&gt;
  &lt;br /&gt;
 print(sum(1.1, 1.1))&lt;br /&gt;
 # -&amp;gt; 2.1&lt;br /&gt;
&lt;br /&gt;
==Read config file==&lt;br /&gt;
===TOML===&lt;br /&gt;
see https://learnxinyminutes.com/docs/toml/&lt;br /&gt;
&lt;br /&gt;
minimal example:&lt;br /&gt;
config.toml&lt;br /&gt;
 # SAP API endpoint&lt;br /&gt;
 [general]&lt;br /&gt;
 sleep_time = 60&lt;br /&gt;
 use_proxy = true&lt;br /&gt;
tool.py&lt;br /&gt;
 try:&lt;br /&gt;
     import tomllib  # comes with python3.11&lt;br /&gt;
 except ModuleNotFoundError:&lt;br /&gt;
     import tomli as tomllib  # pip install tomli&lt;br /&gt;
 with open(&amp;quot;config.toml&amp;quot;, &amp;quot;rb&amp;quot;) as f:&lt;br /&gt;
    o = tomllib.load(f)  # type: ignore&lt;br /&gt;
 if o[&amp;quot;settings&amp;quot;][&amp;quot;use_proxy&amp;quot;]:&lt;br /&gt;
 ...&lt;br /&gt;
for validation, see https://realpython.com/python-toml/&lt;br /&gt;
&lt;br /&gt;
====TOML Write====&lt;br /&gt;
In for deployments I sometime want to modify a TOML config file for production, based on the dev version.&lt;br /&gt;
 import tomli_w  # pip install tomli-w&lt;br /&gt;
 p_in = Path(__file__).parent.parent / &amp;quot;.streamlit/config.toml&amp;quot;&lt;br /&gt;
 p_out = p_in.parent / &amp;quot;config-prod.toml&amp;quot; &lt;br /&gt;
 with p_in.open(&amp;quot;rb&amp;quot;) as fh:&lt;br /&gt;
     o = tomllib.load(fh)&lt;br /&gt;
 o[&amp;quot;server&amp;quot;][&amp;quot;fileWatcherType&amp;quot;] = &amp;quot;none&amp;quot;&lt;br /&gt;
 del o[&amp;quot;server&amp;quot;][&amp;quot;address&amp;quot;]&lt;br /&gt;
  with p_out.open(&amp;quot;wb&amp;quot;) as fh:&lt;br /&gt;
     tomli_w.dump(o, fh)&lt;br /&gt;
&lt;br /&gt;
===ConfigParser: INI Config File Reading===&lt;br /&gt;
better use TOML&lt;br /&gt;
&lt;br /&gt;
Config.ini&lt;br /&gt;
 [Section1]&lt;br /&gt;
 Cursor         = 205E18&lt;br /&gt;
 Grandma        =  18E18&lt;br /&gt;
 Farm           =  11E18&lt;br /&gt;
 Mine           = 514E18&lt;br /&gt;
 Factory        = 155E18&lt;br /&gt;
test.py&lt;br /&gt;
 from configparser import ConfigParser&lt;br /&gt;
 &lt;br /&gt;
 config = ConfigParser(&lt;br /&gt;
     interpolation=None&lt;br /&gt;
 )  # interpolation=None -&amp;gt; treats % in values as char % instead of interpreting it&lt;br /&gt;
 config.read(&amp;quot;Config.ini&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(config.getint(&amp;quot;Section1&amp;quot;, &amp;quot;key1&amp;quot;))&lt;br /&gt;
 print(config.getfloat(&amp;quot;Section1&amp;quot;, &amp;quot;key2&amp;quot;))&lt;br /&gt;
 print(config.get(&amp;quot;Section1&amp;quot;, &amp;quot;key3&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 for sec in config.sections():&lt;br /&gt;
     d_settings = {}&lt;br /&gt;
     for key in config.options(sec):&lt;br /&gt;
         value = config.get(sec, key)&lt;br /&gt;
         d_settings[key] = value&lt;br /&gt;
         print(&amp;quot;%15s : %s&amp;quot; % (key, value))&lt;br /&gt;
&lt;br /&gt;
==VCard / vcf==&lt;br /&gt;
 import codecs&lt;br /&gt;
 import vobject  # pip install vobject&lt;br /&gt;
 &lt;br /&gt;
 obj = vobject.readComponents(codecs.open(file_in, encoding=&amp;quot;utf-8&amp;quot;).read())  # type: ignore&lt;br /&gt;
 contacts: list[vobject.base.Component] = list(obj)  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 card = contacts[0]&lt;br /&gt;
 &lt;br /&gt;
 if &amp;quot;bday&amp;quot; not in card.contents:&lt;br /&gt;
     continue&lt;br /&gt;
 &lt;br /&gt;
 # bday: remove &#039;VALUE&#039;: [&#039;DATE&#039;]&lt;br /&gt;
 card.contents[&amp;quot;bday&amp;quot;][0].params = {}  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 # remove all fields but &amp;quot;bday&amp;quot;, &amp;quot;n&amp;quot;&lt;br /&gt;
 for key in card.contents.copy():  # loop over copy, to allow for deleting keys&lt;br /&gt;
     if key not in (&amp;quot;n&amp;quot;, &amp;quot;bday&amp;quot;):&lt;br /&gt;
         del card.contents[key]&lt;br /&gt;
 &lt;br /&gt;
 # recreate fn based on n&lt;br /&gt;
 card.add(&amp;quot;fn&amp;quot;)&lt;br /&gt;
 n = card.contents[&amp;quot;n&amp;quot;][0]&lt;br /&gt;
 fn = f&amp;quot;{n.value.given} {n.value.additional} {n.value.family}&amp;quot;  # type: ignore&lt;br /&gt;
 fn = re.sub(r&amp;quot;\s+&amp;quot;, &amp;quot; &amp;quot;, fn)&lt;br /&gt;
 card.fn.value = fn  # type: ignore&lt;br /&gt;
 &lt;br /&gt;
 with open(&amp;quot;out.vcf&amp;quot;, mode=&amp;quot;w&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;, newline=&amp;quot;\n&amp;quot;) as fhOut:&lt;br /&gt;
     fhOut.write(card.serialize())&lt;br /&gt;
&lt;br /&gt;
==Sentry Exception monitoring==&lt;br /&gt;
see [[Sentry]]&lt;br /&gt;
==MQTT==&lt;br /&gt;
 #!/usr/bin/env python3.12&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;Read power data from Tasmota device via MQTT.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 import json&lt;br /&gt;
 from typing import Any&lt;br /&gt;
 &lt;br /&gt;
 import paho.mqtt.client as mqtt&lt;br /&gt;
 from mqtt_credentials import hostname, password, port, username&lt;br /&gt;
 from paho.mqtt.reasoncodes import ReasonCode&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_connect(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     flags: dict[str, int],&lt;br /&gt;
     reason_code: ReasonCode,&lt;br /&gt;
     properties: Any,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when the client connects MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     if reason_code == 0:&lt;br /&gt;
         print(&amp;quot;Connected to MQTT&amp;quot;)&lt;br /&gt;
         # print(f&amp;quot;MQTT protocol version: {mqtt_client._protocol}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         # Subscribing in on_connect() means that if we lose the connection and&lt;br /&gt;
         # reconnect then subscriptions will be renewed.&lt;br /&gt;
         mqtt_client.message_callback_add(&amp;quot;tele/tasmota_MT681/SENSOR&amp;quot;, on_message)&lt;br /&gt;
         mqtt_client.subscribe([(&amp;quot;tele/tasmota_MT681/SENSOR&amp;quot;, 0)])&lt;br /&gt;
 &lt;br /&gt;
     else:&lt;br /&gt;
         print(f&amp;quot;Connection to MQTT broker failed. Error: {reason_code}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_disconnect(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     reason_code: ReasonCode,&lt;br /&gt;
     properties: Any,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when the client is disconnected from the MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     if reason_code &amp;gt; 0:&lt;br /&gt;
         print(f&amp;quot;Unexpected disconnection. Error: {reason_code}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_message(&lt;br /&gt;
     mqtt_client: mqtt.Client,&lt;br /&gt;
     userdata: Any,&lt;br /&gt;
     message: mqtt.MQTTMessage,&lt;br /&gt;
 ) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Callback when a Tasmota message is received from the MQTT broker.&amp;quot;&amp;quot;&amp;quot;  # noqa: D401&lt;br /&gt;
     s = message.payload.decode()&lt;br /&gt;
     data = json.loads(s)&lt;br /&gt;
     # {&#039;Time&#039;: &#039;2024-03-16T06:44:08&#039;, &#039;MT681&#039;: {&#039;Total_in&#039;: 16.4396, &#039;Power_cur&#039;: 80, &#039;Total_out&#039;: 14.6403}}  # noqa: E501&lt;br /&gt;
 &lt;br /&gt;
     total_i = data[&amp;quot;MT681&amp;quot;][&amp;quot;Total_in&amp;quot;]&lt;br /&gt;
     total_o = data[&amp;quot;MT681&amp;quot;][&amp;quot;Total_out&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
     print(f&amp;quot;In:\t{total_i}\nOut:\t{total_o}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     mqtt_client.disconnect()&lt;br /&gt;
     mqtt_client.loop_stop()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 try:&lt;br /&gt;
     # Create an MQTT client instance&lt;br /&gt;
     mqtt_client: mqtt.Client = mqtt.Client(&lt;br /&gt;
         mqtt.CallbackAPIVersion.VERSION2, &amp;quot;Python-Client-1&amp;quot;&lt;br /&gt;
     )  # type: ignore&lt;br /&gt;
     mqtt_client.username_pw_set(username, password)&lt;br /&gt;
 &lt;br /&gt;
     mqtt_client.on_connect = on_connect&lt;br /&gt;
 &lt;br /&gt;
     # Connect to the MQTT broker&lt;br /&gt;
     mqtt_client.connect(hostname, port, keepalive=60)&lt;br /&gt;
 &lt;br /&gt;
     # NOT: while True, as that eats the CPU !!!&lt;br /&gt;
     mqtt_client.loop_forever()&lt;br /&gt;
 except KeyboardInterrupt:&lt;br /&gt;
     print(&amp;quot;KeyboardInterrupt, disconnecting from MQTT broker.&amp;quot;)&lt;br /&gt;
     mqtt_client.disconnect()&lt;br /&gt;
     mqtt_client.loop_stop()&lt;br /&gt;
&lt;br /&gt;
==Caching Functions==&lt;br /&gt;
 from functools import lru_cache&lt;br /&gt;
 &lt;br /&gt;
 @lru_cache(maxsize=128)  # None equals to @cache declarator&lt;br /&gt;
 def fnc(n:int):&lt;br /&gt;
&lt;br /&gt;
==Performance/Resources==&lt;br /&gt;
===RAM/Memory consumption/bottleneck===&lt;br /&gt;
 import tracemalloc&lt;br /&gt;
 tracemalloc.start()&lt;br /&gt;
 &lt;br /&gt;
 # ...&lt;br /&gt;
 # Code&lt;br /&gt;
 # example:&lt;br /&gt;
 lst = list(range(1_000_000))&lt;br /&gt;
 # ...&lt;br /&gt;
 &lt;br /&gt;
 max_bytes = tracemalloc.get_traced_memory()[0]&lt;br /&gt;
 print(f&amp;quot;{round(max_bytes / 10**6)}MB&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 tracemalloc.stop()&lt;br /&gt;
&lt;br /&gt;
===Profiling / Count and Runtime per Function===&lt;br /&gt;
 call_stats = {}&lt;br /&gt;
 &lt;br /&gt;
 def track_function_usage(func: Callable) -&amp;gt; Callable:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Annotation for gathering runtime statistics.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     @wraps(func)&lt;br /&gt;
     def wrapper(*args: tuple, **kwargs: dict):  # noqa: ANN202&lt;br /&gt;
         start_time = time.time()&lt;br /&gt;
         result = func(*args, **kwargs)  # Call the original function&lt;br /&gt;
         end_time = time.time()&lt;br /&gt;
         call_stats[func.__name__][&amp;quot;calls&amp;quot;] += 1&lt;br /&gt;
         call_stats[func.__name__][&amp;quot;total_time&amp;quot;] += end_time - start_time&lt;br /&gt;
         return result&lt;br /&gt;
     return wrapper&lt;br /&gt;
&lt;br /&gt;
==JWT Token==&lt;br /&gt;
  try:&lt;br /&gt;
      decoded_token = jwt.decode(token, options={&amp;quot;verify_signature&amp;quot;: False})&lt;br /&gt;
  except jwt.ExpiredSignatureError:&lt;br /&gt;
      msg = &amp;quot;Token has expired.&amp;quot;&lt;br /&gt;
      logger.exception(msg)&lt;br /&gt;
  except jwt.InvalidTokenError:&lt;br /&gt;
      msg = &amp;quot;Invalid token.&amp;quot;&lt;br /&gt;
      logger.exception(msg)&lt;br /&gt;
&lt;br /&gt;
==Streamlit==&lt;br /&gt;
see https://github.com/entorb/streamlit-examples for a collection of my examples&lt;br /&gt;
&lt;br /&gt;
===Minimum Example===&lt;br /&gt;
 # src/app.py&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 import streamlit as st&lt;br /&gt;
 from streamlit.logger import get_logger&lt;br /&gt;
 logger = get_logger(Path(__file__).stem)&lt;br /&gt;
 logger.info(&amp;quot;Start&amp;quot;)&lt;br /&gt;
 st.set_page_config(page_title=&amp;quot;AppTitle&amp;quot;, page_icon=None, layout=&amp;quot;wide&amp;quot;)&lt;br /&gt;
 st.title(&amp;quot;MyPageTitle&amp;quot;)&lt;br /&gt;
 st.header(&amp;quot;MyHeader&amp;quot;)&lt;br /&gt;
 st.subheader(&amp;quot;MySubHeader&amp;quot;)&lt;br /&gt;
 sel_param = st.selectbox(&amp;quot;Parameter&amp;quot;, (&amp;quot;count&amp;quot;, &amp;quot;sum&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
 # .streamlit/config.toml&lt;br /&gt;
 [browser]&lt;br /&gt;
 gatherUsageStats = false&lt;br /&gt;
 &lt;br /&gt;
 [server]&lt;br /&gt;
 port = 8501&lt;br /&gt;
&lt;br /&gt;
 # run it&lt;br /&gt;
 streamlit run src/app.py&lt;br /&gt;
&lt;br /&gt;
====Reduce Logging for K8s Deployed Docker Containers====&lt;br /&gt;
 # reduce log output&lt;br /&gt;
 for logger_name in [&lt;br /&gt;
     &amp;quot;streamlit.runtime.caching.cache_utils&amp;quot;,&lt;br /&gt;
     &amp;quot;streamlit.runtime.caching.storage.in_memory_cache_storage_wrapper&amp;quot;,&lt;br /&gt;
     &amp;quot;streamlit.runtime.runtime&amp;quot;,&lt;br /&gt;
     &amp;quot;urllib3.connectionpool&amp;quot;,&lt;br /&gt;
 ]:&lt;br /&gt;
     get_logger(logger_name).setLevel(logging.WARNING)&lt;br /&gt;
&lt;br /&gt;
===Chart via Altair===&lt;br /&gt;
====Line Chart====&lt;br /&gt;
 chart = st.line_chart(df[&amp;quot;value&amp;quot;])&lt;br /&gt;
 # corresponds to&lt;br /&gt;
 import altair as alt&lt;br /&gt;
 c = (&lt;br /&gt;
    alt.Chart(df)&lt;br /&gt;
    .mark_line()&lt;br /&gt;
    .encode(&lt;br /&gt;
        x=alt.X(&amp;quot;created&amp;quot;, title=None),&lt;br /&gt;
        y=alt.Y(&amp;quot;value&amp;quot;, title=None),&lt;br /&gt;
    )&lt;br /&gt;
 )&lt;br /&gt;
 st.altair_chart(c, use_container_width=True)&lt;br /&gt;
&lt;br /&gt;
====Bar Chart====&lt;br /&gt;
 chart = (&lt;br /&gt;
     alt.Chart(df)&lt;br /&gt;
     .mark_bar()&lt;br /&gt;
     .encode(&lt;br /&gt;
         x=alt.X(&amp;quot;yearmonth(date):T&amp;quot;, title=&amp;quot;Month&amp;quot;),&lt;br /&gt;
         y=alt.Y(f&amp;quot;count:Q&amp;quot;, title=None),&lt;br /&gt;
         color=&amp;quot;status:N&amp;quot;,&lt;br /&gt;
         tooltip=[&amp;quot;yearmonth(date):T&amp;quot;, &amp;quot;status:N&amp;quot;, &amp;quot;cnt:Q&amp;quot;],&lt;br /&gt;
     )&lt;br /&gt;
     # .properties(width=600, height=400)&lt;br /&gt;
 )&lt;br /&gt;
 st.altair_chart(chart, use_container_width=True)&lt;br /&gt;
&lt;br /&gt;
 :T stands for Temporal. It indicates that the field contains date or time values.&lt;br /&gt;
 :N stands for Nominal. It indicates that the field contains categorical data, which represents discrete categories or labels.&lt;br /&gt;
 :Q stands for Quantitative. It indicates that the field contains numerical data that can be measured and compared.&lt;br /&gt;
&lt;br /&gt;
===Excel Download===&lt;br /&gt;
 def excel_download_buttons(df: pd.DataFrame, file_name: str = &amp;quot;export.xlsx&amp;quot;) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Show prepare data and download buttons.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     if st.button(label=&amp;quot;Click to prepare download&amp;quot;):&lt;br /&gt;
         buffer = io.BytesIO()&lt;br /&gt;
         with pd.ExcelWriter(buffer, engine=&amp;quot;xlsxwriter&amp;quot;) as writer:&lt;br /&gt;
             df.to_excel(writer, sheet_name=&amp;quot;Sheet1&amp;quot;, index=False)&lt;br /&gt;
             writer.close()&lt;br /&gt;
 &lt;br /&gt;
             st.download_button(&lt;br /&gt;
                 label=&amp;quot;Download data as Excel&amp;quot;,&lt;br /&gt;
                 data=buffer,&lt;br /&gt;
                 file_name=file_name,&lt;br /&gt;
                 mime=&amp;quot;application/vnd.ms-excel&amp;quot;,&lt;br /&gt;
             )&lt;br /&gt;
&lt;br /&gt;
==UV Package Manager==&lt;br /&gt;
 # create/update lockfile&lt;br /&gt;
 uv lock --upgrade&lt;br /&gt;
 # sync venv&lt;br /&gt;
 uv sync --upgrade&lt;br /&gt;
 # extract package info to requirements.txt&lt;br /&gt;
 uv export --format requirements.txt --no-dev --no-hashes -o requirements.txt&lt;br /&gt;
&lt;br /&gt;
Update Python version for my current project&lt;br /&gt;
 uv venv --python=3.13.7&lt;br /&gt;
&lt;br /&gt;
===Update UV===&lt;br /&gt;
 uv self update&lt;br /&gt;
 # or&lt;br /&gt;
 brew update &amp;amp;&amp;amp; brew upgrade uv&lt;br /&gt;
&lt;br /&gt;
===Update Python Versions===&lt;br /&gt;
 uv python upgrade&lt;br /&gt;
&lt;br /&gt;
===UV + Docker on readOnlyRootFilesystem===&lt;br /&gt;
Example 1: https://github.com/SWE-Group-23/CM22007---Source/blob/10d02004f3a4208f7b30d37a0a87e89532a23575/create-service.sh#L117&lt;br /&gt;
&lt;br /&gt;
Dockerfile&lt;br /&gt;
 WORKDIR /app&lt;br /&gt;
 COPY . .&lt;br /&gt;
 &lt;br /&gt;
 ENV CASS_DRIVER_NO_EXTENSIONS=1&lt;br /&gt;
 &lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache/uv \\&lt;br /&gt;
     --mount=type=bind,source=uv.lock,target=uv.lock \\&lt;br /&gt;
     --mount=type=bind,source=pyproject.toml,target=pyproject.toml \\&lt;br /&gt;
     uv sync --frozen --no-install-project&lt;br /&gt;
     &lt;br /&gt;
 RUN uv pip install -e shared/&lt;br /&gt;
 &lt;br /&gt;
 RUN addgroup -S group1 &amp;amp;&amp;amp; adduser -S user1 -G group1 -u 1000&lt;br /&gt;
 RUN chown -R user1:group1 /app&lt;br /&gt;
 USER user1:group1&lt;br /&gt;
 &lt;br /&gt;
 CMD [&amp;quot;uv&amp;quot;, &amp;quot;run&amp;quot;, &amp;quot;main.py&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
Deployment&lt;br /&gt;
 securityContext:&lt;br /&gt;
   readOnlyRootFilesystem: true&lt;br /&gt;
   allowPrivilegeEscalation: false&lt;br /&gt;
   runAsNonRoot: true&lt;br /&gt;
   runAsUser: 1000&lt;br /&gt;
   capabilities:&lt;br /&gt;
     drop:&lt;br /&gt;
       - ALL&lt;br /&gt;
   seccompProfile:&lt;br /&gt;
     type: RuntimeDefault&lt;br /&gt;
 volumeMounts:&lt;br /&gt;
   - mountPath: /home/user1/.cache/uv&lt;br /&gt;
     name: uv&lt;br /&gt;
   - mountPath: /tmp&lt;br /&gt;
     name: temp&lt;br /&gt;
&lt;br /&gt;
Example 2: [https://hynek.me/articles/docker-uv/ Production-ready Python Docker Containers with uv]&lt;br /&gt;
&lt;br /&gt;
Dockerfile (without the comments)&lt;br /&gt;
 ENV UV_LINK_MODE=copy \&lt;br /&gt;
     UV_COMPILE_BYTECODE=1 \&lt;br /&gt;
     UV_PYTHON_DOWNLOADS=never \&lt;br /&gt;
     UV_PYTHON=python3.12 \&lt;br /&gt;
     UV_PROJECT_ENVIRONMENT=/app&lt;br /&gt;
 &lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache \&lt;br /&gt;
     --mount=type=bind,source=uv.lock,target=uv.lock \&lt;br /&gt;
     --mount=type=bind,source=pyproject.toml,target=pyproject.toml \&lt;br /&gt;
     uv sync \&lt;br /&gt;
         --locked \&lt;br /&gt;
         --no-dev \&lt;br /&gt;
         --no-install-project&lt;br /&gt;
 &lt;br /&gt;
 COPY . /src&lt;br /&gt;
 WORKDIR /src&lt;br /&gt;
 RUN --mount=type=cache,target=/root/.cache \&lt;br /&gt;
     uv sync \&lt;br /&gt;
         --locked \&lt;br /&gt;
         --no-dev \&lt;br /&gt;
         --no-editable&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Linux_Bash_Shell_Scripting&amp;diff=5386</id>
		<title>Linux Bash Shell Scripting</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Linux_Bash_Shell_Scripting&amp;diff=5386"/>
		<updated>2026-01-17T05:26:58Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Parameters */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Linux]]&lt;br /&gt;
===Bash commands===&lt;br /&gt;
 rm -rf dir   # do not show warning if not existing&lt;br /&gt;
 mkdir -p dir # do not print warning if already existing AND make needed parents&lt;br /&gt;
&lt;br /&gt;
===Output===&lt;br /&gt;
View File &lt;br /&gt;
 less File&lt;br /&gt;
Print last line of file&lt;br /&gt;
 tail -1 File&lt;br /&gt;
Print last line, column 2 of file (cut expects tab separating)&lt;br /&gt;
 tail -1 File | cut -f2&lt;br /&gt;
Follow changes in (log)file&lt;br /&gt;
 tail -f File&lt;br /&gt;
Print file contents to std output&lt;br /&gt;
 cat File&lt;br /&gt;
Filter contents of file to std output&lt;br /&gt;
 grep &amp;quot;ERROR&amp;quot; File&lt;br /&gt;
Filter contents of file to std output (case-insensitive)&lt;br /&gt;
 grep -i &amp;quot;error&amp;quot; File&lt;br /&gt;
Filter contents of file to std output, +2 lines before +1 following lines each&lt;br /&gt;
 grep -B2 -A1 &amp;quot;ERROR&amp;quot; File&lt;br /&gt;
Print file contents to std output, starting at key word&lt;br /&gt;
 grep -A9999999 &amp;quot;2016-04-19T10:38&amp;quot; File&lt;br /&gt;
Save program output (script/grep/cat...) into file&lt;br /&gt;
 sh script.sh &amp;gt; log.txt      # std output, (errors to terminal)&lt;br /&gt;
 sh script.sh 1&amp;gt; log.txt 2&amp;gt; log-errors.txt  # separate files&lt;br /&gt;
 sh script.sh &amp;amp;&amp;gt; log.txt     # std and errors both to 1 file&lt;br /&gt;
Send output to file and terminal&lt;br /&gt;
 touch log.txt &amp;amp; tail -f log.txt &amp;amp; sh script.sh &amp;gt; log.txt&lt;br /&gt;
Piping &lt;br /&gt;
 sh script.sh | less &lt;br /&gt;
Filtering of output&lt;br /&gt;
 sh script.sh | grep tmenke&lt;br /&gt;
&lt;br /&gt;
====Search file contents in dir recursively====&lt;br /&gt;
From [https://stackoverflow.com/posts/16957078/timeline]&lt;br /&gt;
 grep -rn &#039;/path/to/somewhere/&#039; -e &#039;pattern&#039;&lt;br /&gt;
&lt;br /&gt;
===Input===&lt;br /&gt;
====Enter a value====&lt;br /&gt;
 echo &amp;quot;Enter to continue, CTRL+C to cancel&amp;quot;&lt;br /&gt;
 read ok&lt;br /&gt;
 &lt;br /&gt;
 echo -n &amp;quot;Was? &amp;quot;&lt;br /&gt;
 read var&lt;br /&gt;
 echo &amp;quot;$var&amp;quot;&lt;br /&gt;
&lt;br /&gt;
====Dialog Input====&lt;br /&gt;
 dialog --yesno &amp;quot;Cleanup Old Backups First&amp;quot; 0 0&lt;br /&gt;
 CleanUpBackups=$?&lt;br /&gt;
 # yes -&amp;gt; 0 # no -&amp;gt; 1&lt;br /&gt;
 if [ $CleanUpBackups -eq 0 ] # spaces needed!!!&lt;br /&gt;
   then&lt;br /&gt;
   perl /save/_save/backupPlatteAufraemen/loeschen2.pl&lt;br /&gt;
 fi&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Loops===&lt;br /&gt;
Zip all subdirs&lt;br /&gt;
 for D in `ls -d */`&lt;br /&gt;
 do&lt;br /&gt;
     # remove tailing /&lt;br /&gt;
     D=${D%/}&lt;br /&gt;
     echo === $D ====&lt;br /&gt;
     cd $D&lt;br /&gt;
     zip -rq9 ~/sicher/html/$D.zip .&lt;br /&gt;
     # tar cfz ~/sicher/html/$D.tgz .&lt;br /&gt;
     cd ..&lt;br /&gt;
 done&lt;br /&gt;
&lt;br /&gt;
Check if variable in list&lt;br /&gt;
 if [[ &amp;quot;$D&amp;quot; =~ ^(hpmor-en/|DukeTypem2D/)$ ]]; then&lt;br /&gt;
     echo skipping $D&lt;br /&gt;
&lt;br /&gt;
Good overview of parameters for if [http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_07_01.html]&lt;br /&gt;
&amp;lt;br&amp;gt;If&lt;br /&gt;
 if [ -d &amp;quot;backup.0&amp;quot; ]; then&lt;br /&gt;
   echo &amp;quot;gibts&amp;quot;&lt;br /&gt;
 else &lt;br /&gt;
   echo &amp;quot;gibts nicht&amp;quot;&lt;br /&gt;
 fi&lt;br /&gt;
one-liner:&lt;br /&gt;
  if [ -d &amp;quot;backup.0&amp;quot; ] ; then sudo mv backup.0 backup.1 ; fi&lt;br /&gt;
&lt;br /&gt;
Check if process is running&lt;br /&gt;
 if [ ! &amp;quot;$(pidof workrave)&amp;quot; ] ; then&lt;br /&gt;
   nohup workrave&lt;br /&gt;
 fi&lt;br /&gt;
&lt;br /&gt;
While&amp;lt;br&amp;gt;&lt;br /&gt;
Normal loop&lt;br /&gt;
 i=1&lt;br /&gt;
 while [ $i -le 4 ] do&lt;br /&gt;
 (( i++ ))&lt;br /&gt;
 done&lt;br /&gt;
Endless loop&lt;br /&gt;
 while true; do&lt;br /&gt;
     # ...&lt;br /&gt;
     echo &amp;quot;Enter to continue, CTRL+C to cancel&amp;quot;&lt;br /&gt;
     read user_input&lt;br /&gt;
 done&lt;br /&gt;
For loop&lt;br /&gt;
 for FILE in *.txt; &lt;br /&gt;
   do &lt;br /&gt;
   mv &amp;quot;$FILE&amp;quot; `echo $FILE | tr &#039; &#039; &#039;_&#039;`&lt;br /&gt;
   done&lt;br /&gt;
&lt;br /&gt;
Check if files matching *.xml exist, store Number as string and print it&lt;br /&gt;
 NUM=$(find $TRANSPORT/*.xml -type f 2&amp;gt; /dev/null |wc -l)&lt;br /&gt;
 echo $NUM&lt;br /&gt;
 if [ $NUM != &amp;quot;0&amp;quot; ] ; then&lt;br /&gt;
 echo &amp;quot;$NUM files found, copying&amp;quot; &lt;br /&gt;
 else &lt;br /&gt;
 echo &amp;quot;No files found, exiting&amp;quot; &lt;br /&gt;
 exit 0&lt;br /&gt;
 fi&lt;br /&gt;
&lt;br /&gt;
====Loop over contents / columns of file====&lt;br /&gt;
file contains tab separated columns $1 -&amp;gt; column1 etc.&lt;br /&gt;
 while read line ; do&lt;br /&gt;
     set $line&lt;br /&gt;
 	myCommit=$1&lt;br /&gt;
 	myDate=$2&lt;br /&gt;
     echo &amp;quot;$myCommit ; $myDate&amp;quot;&lt;br /&gt;
 done &amp;lt;&amp;quot;history-change.tsv&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===Exit upon Error===&lt;br /&gt;
 set -e&lt;br /&gt;
 touch /tmp/not_found.txt&lt;br /&gt;
 echo will not reach here&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
from [http://stackoverflow.com/questions/3534280/how-can-i-pass-a-file-argument-to-my-bash-script-using-a-terminal-command-in-lin]&lt;br /&gt;
 OPTIONS=$(getopt -o f:t -l file:,titlepage -- &amp;quot;$@&amp;quot;)&lt;br /&gt;
 if [ $? -ne 0 ]; then&lt;br /&gt;
   echo &amp;quot;getopt error&amp;quot;&lt;br /&gt;
   exit 1&lt;br /&gt;
 fi&lt;br /&gt;
 eval set -- $OPTIONS&lt;br /&gt;
 while true; do&lt;br /&gt;
   case &amp;quot;$1&amp;quot; in&lt;br /&gt;
     -f|--file) FILE=&amp;quot;$2&amp;quot; ; shift ;;&lt;br /&gt;
     -t|--titlepage) TITLEPAGE=1 ;;&lt;br /&gt;
     --)        shift ; break ;;&lt;br /&gt;
     *)         echo &amp;quot;unknown option: $1&amp;quot; ; exit 1 ;;&lt;br /&gt;
   esac&lt;br /&gt;
   shift&lt;br /&gt;
 done&lt;br /&gt;
 if [ $# -ne 0 ]; then&lt;br /&gt;
   echo &amp;quot;unknown option(s): $@&amp;quot;&lt;br /&gt;
   exit 1&lt;br /&gt;
 fi&lt;br /&gt;
 &lt;br /&gt;
 if [ ! $FILE ]; then&lt;br /&gt;
   echo &amp;quot;no file given as parameter&amp;quot;&lt;br /&gt;
   exit 1&lt;br /&gt;
 fi&lt;br /&gt;
 if [ ! -f $FILE ]; then&lt;br /&gt;
   echo &amp;quot;file &#039;$FILE&#039; not found&amp;quot;&lt;br /&gt;
   exit 1&lt;br /&gt;
 fi&lt;br /&gt;
 # echo &amp;quot;titlepage: $TITLEPAGE&amp;quot;&lt;br /&gt;
 # echo &amp;quot;file: $FILE&amp;quot;&lt;br /&gt;
 # exit 1&lt;br /&gt;
&lt;br /&gt;
===Combine find and some other command e.g chmod===&lt;br /&gt;
 # chmod for dirs&lt;br /&gt;
 find -type d -exec chmod g+xs {} \;&lt;br /&gt;
 &lt;br /&gt;
 # make scripts excecuteable&lt;br /&gt;
 find . -name &amp;quot;*.sh&amp;quot; -exec chmod 744 {} \;&lt;br /&gt;
 &lt;br /&gt;
 # remove .zip files older 30 days&lt;br /&gt;
 find ./R*/logs/*.zip -mtime +30 -exec rm {} \;&lt;br /&gt;
 &lt;br /&gt;
 # Copy empty Directory Structure&lt;br /&gt;
 mkdir /Zielverzeichnis &lt;br /&gt;
 cd Quellverzeichnis&lt;br /&gt;
 find . -type d -depth | cpio -pvdma /Zielverzeichnis/&lt;br /&gt;
&lt;br /&gt;
===sed===&lt;br /&gt;
If you want to replace the text &#039;asdf&#039; by &#039;qwert&#039; in all &#039;*.txt&#039; files in a directory at once, use the following command:&lt;br /&gt;
 sed -i -e &#039;s/asdf/qwert/g&#039; *.txt&lt;br /&gt;
&lt;br /&gt;
add a line to file&lt;br /&gt;
 sed -i &#039;8i\\\input{layout/hp-format}\n\\input{layout/hp-markup}\n&#039; $target_file&lt;br /&gt;
&lt;br /&gt;
delete/&amp;quot;grep out&amp;quot; lines&lt;br /&gt;
 sed -i &#039;/\\\input{layout\/hp-contents}/d&#039; $target_file&lt;br /&gt;
&lt;br /&gt;
insert file into file at marker&lt;br /&gt;
 sed -i -e &#039;/&amp;lt;style/r ebook/epub.css&#039; $target_file&lt;br /&gt;
&lt;br /&gt;
 # -z changes the delimiter to null characters (\0) to allow for multiline matching&lt;br /&gt;
 sed -i -z &#039;s/&amp;lt;\/header&amp;gt;.*&amp;lt; p &amp;gt;HARRY POTTER&amp;lt;\/p&amp;gt;/&amp;lt;\/header&amp;gt;\n&amp;lt; p &amp;gt;HARRY POTTER&amp;lt;\/p&amp;gt;/&#039; $target_file&lt;br /&gt;
&lt;br /&gt;
===get filename dirname etc===&lt;br /&gt;
cd to parent dir&lt;br /&gt;
 script_dir=$(dirname $0)&lt;br /&gt;
 cd $script_dir/..&lt;br /&gt;
&lt;br /&gt;
 dirname file&lt;br /&gt;
 basename file&lt;br /&gt;
 &lt;br /&gt;
 myPath=data/de-districts/de-district_timeseries-02000.tsv&lt;br /&gt;
 myDir=$(dirname -- &amp;quot;$myPath&amp;quot;)&lt;br /&gt;
 myFileName=$(basename -- &amp;quot;$myPath&amp;quot;)&lt;br /&gt;
 myFileExt=&amp;quot;${myFileName##*.}&amp;quot;&lt;br /&gt;
 myFileName=&amp;quot;${myFileName%.*}&amp;quot;&lt;br /&gt;
&lt;br /&gt;
filename without extension&lt;br /&gt;
 FILEOLD=&amp;quot;${FILE%.pdf}-alt.pdf&amp;quot;&lt;br /&gt;
&lt;br /&gt;
 for file in *&lt;br /&gt;
 do&lt;br /&gt;
   if [ -f $file ] ; then&lt;br /&gt;
     name=${file%\.*} # name without extension&lt;br /&gt;
     echo ${name}&lt;br /&gt;
   fi ;&lt;br /&gt;
 done&lt;br /&gt;
&lt;br /&gt;
 name=`basename /path/filename.ext .ext` # -&amp;gt; filename&lt;br /&gt;
 for i in *.jpeg; do mv &amp;quot;$i&amp;quot; &amp;quot;`basename &amp;quot;$i&amp;quot; .jpeg`.jpg&amp;quot;; done&lt;br /&gt;
&lt;br /&gt;
Path of current working dir&lt;br /&gt;
 dir=$PWD&lt;br /&gt;
&lt;br /&gt;
Name of current directory without path&lt;br /&gt;
 dirname=${PWD##*/}&lt;br /&gt;
&lt;br /&gt;
Location of this script&lt;br /&gt;
 dir=&amp;quot;$( cd &amp;quot;$( dirname &amp;quot;${BASH_SOURCE[0]}&amp;quot; )&amp;quot; &amp;amp;&amp;amp; pwd )&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===Get Date as String===&lt;br /&gt;
 DATE=`date +&amp;quot;%y-%m-%d_%H-%M&amp;quot;`&lt;br /&gt;
 echo $DATE&lt;br /&gt;
&lt;br /&gt;
===Count Files in Folder===&lt;br /&gt;
 find . | wc -l&lt;br /&gt;
Check free inodes on partition&lt;br /&gt;
 df -i&lt;br /&gt;
&lt;br /&gt;
===Tar===&lt;br /&gt;
get rid of message &amp;quot;removing leading /&amp;quot;: use option P&lt;br /&gt;
&lt;br /&gt;
===Pause===&lt;br /&gt;
 read -p &amp;quot;Press any key to start backup...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===fstab mount discs harddrive partitions===&lt;br /&gt;
read UUID&lt;br /&gt;
 sudo blkid&lt;br /&gt;
 sudo blkid /dev/sda3&lt;br /&gt;
&lt;br /&gt;
UUID=D4AE21F4AE21D032 /media/win ntfs auto,user,uid=torben,gid=torben,fmask=0177,dmask=0077,ro 0 0&lt;br /&gt;
&lt;br /&gt;
UUID=f103902f-bbd6-49af-b983-b1dddedd361a /media/linux ext3 auto,user,rw 0 0&lt;br /&gt;
&lt;br /&gt;
UUID=EA06-C29C /media/files vfat auto,user,utf8,uid=torben,gid=torben,fmask=0177,dmask=0077,rw 0 0&lt;br /&gt;
&lt;br /&gt;
===Network Tests===&lt;br /&gt;
 ping 10.10.10.10 -c 1&lt;br /&gt;
 netcat / ncat / nc 10.10.10.10 8080&lt;br /&gt;
 telnet 10.10.10.10 8080&lt;br /&gt;
&lt;br /&gt;
====netcat====&lt;br /&gt;
Netcat is pretty cool, as it can be used to send messages between two machines, or even set files, see [https://www.digitalocean.com/community/tutorials/how-to-use-netcat-to-establish-and-test-tcp-and-udp-connections-on-a-vps]&lt;br /&gt;
&lt;br /&gt;
 # host1: start to listen on a port &lt;br /&gt;
 netcat -l &amp;lt;PORT&amp;gt;&lt;br /&gt;
 # host2: connect to host1&lt;br /&gt;
 netcat &amp;lt;IP-host1&amp;gt; &amp;lt;PORT&amp;gt;&lt;br /&gt;
 #  now messages can be send in both ways&lt;br /&gt;
Transfer file&lt;br /&gt;
 # host1: start to listen on a port &lt;br /&gt;
 netcat -l &amp;lt;PORT&amp;gt; &amp;gt; received_file&lt;br /&gt;
 # host2: send a file&lt;br /&gt;
 netcat &amp;lt;IP-host1&amp;gt; &amp;lt;PORT&amp;gt; &amp;lt; original_file&lt;br /&gt;
&lt;br /&gt;
====wget====&lt;br /&gt;
 wget --no-check-certificate https://192.168.0.123:8080&lt;br /&gt;
 wget --no-check-certificate --user=myUser --password=myPasswd https://10.10.10.10/url.xml&lt;br /&gt;
 # DynDNS&lt;br /&gt;
 wget -d -v &amp;quot;http://192.168.0.123:8080/nic/update?hostname=myhost.10.10.1.100&amp;amp;myip=10.10.10.12&amp;amp;localip=10.10.10.12&amp;quot; --user=&amp;quot;u123456&amp;quot; --password=&amp;quot;p123456&amp;quot; -O output.xml&lt;br /&gt;
 wget --ca-certificate=MyCert.pem --user=myUser --password=myPwd https://myUrl:80/file.xml&lt;br /&gt;
&lt;br /&gt;
====curl====&lt;br /&gt;
 curl --trace /dev/stdout \&lt;br /&gt;
  --cacert MyCert.pem \&lt;br /&gt;
  https://myUrl:80/file.xml \&lt;br /&gt;
  -u myUser:myPwd \&lt;br /&gt;
  --header &amp;quot;Content-Type: text/xml;charset=UTF-8&amp;quot; \&lt;br /&gt;
  --header &amp;quot;SOAPAction: http://MySoapActionURL&amp;quot; \&lt;br /&gt;
  --header &amp;quot;Accept-Encoding: gzip, deflate&amp;quot; \&lt;br /&gt;
  --data &#039;&amp;lt;MySoapData&amp;gt;&#039;&lt;br /&gt;
&lt;br /&gt;
Access Strava API and retrieve JSON format&lt;br /&gt;
 curl -X GET &amp;quot;https://www.strava.com/api/v3/athlete?access_token=1234567890&amp;quot; -H  &amp;quot;accept: application/json&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==Examples==&lt;br /&gt;
Search for a word in some files (here *.txt)&lt;br /&gt;
 if [ -z $1 ]; then&lt;br /&gt;
   echo -n &amp;quot;Was? &amp;quot;&lt;br /&gt;
   read was&lt;br /&gt;
 else &lt;br /&gt;
   was=$1&lt;br /&gt;
   echo &amp;quot;$was&amp;quot;&lt;br /&gt;
 fi&lt;br /&gt;
 grep --color=auto -i $was *.txt&lt;br /&gt;
&lt;br /&gt;
===Crontab===&lt;br /&gt;
 # every 5 minutes&lt;br /&gt;
 */5     *       *       *       *       script1.sh&lt;br /&gt;
 # every 5 minutes + 1&lt;br /&gt;
 1-56/5  *       *       *       *       script2.sh&lt;br /&gt;
&lt;br /&gt;
===systemctl Services===&lt;br /&gt;
 # create service&lt;br /&gt;
 sudo vim /etc/systemd/system/mqtt_influx.service&lt;br /&gt;
&lt;br /&gt;
 [Unit]&lt;br /&gt;
 Description=My MQTT to InfluxDB Service&lt;br /&gt;
 &lt;br /&gt;
 [Service]&lt;br /&gt;
 ExecStart=/usr/bin/env python3.9 /home/pi/influx-collectors/mqtt.py&lt;br /&gt;
 WorkingDirectory=/home/pi/influx-collectors&lt;br /&gt;
 Restart=always&lt;br /&gt;
 User=pi&lt;br /&gt;
 Group=pi&lt;br /&gt;
 Nice=10&lt;br /&gt;
 &lt;br /&gt;
 [Install]&lt;br /&gt;
 WantedBy=multi-user.target&lt;br /&gt;
&lt;br /&gt;
 # activate and start service&lt;br /&gt;
 sudo systemctl daemon-reload&lt;br /&gt;
 sudo systemctl enable mqtt_influx.service&lt;br /&gt;
 sudo systemctl start mqtt_influx.service&lt;br /&gt;
 sudo systemctl restart mqtt_influx.service&lt;br /&gt;
 sudo systemctl stop mqtt_influx.service&lt;br /&gt;
 sudo systemctl status mqtt_influx.service&lt;br /&gt;
 # access log (-f = follow)&lt;br /&gt;
 sudo journalctl -u mqtt_influx.service -f&lt;br /&gt;
&lt;br /&gt;
===Clean Harddisk by filling with Zeros===&lt;br /&gt;
Caution: double check which device you are cleaning...&lt;br /&gt;
 sudo dd if=/dev/zero of=/dev/sdd1 bs=1M&lt;br /&gt;
&lt;br /&gt;
===Screen===&lt;br /&gt;
 # liste anzeigen&lt;br /&gt;
 screen -list&lt;br /&gt;
 # neuen starten&lt;br /&gt;
 screen -R name&lt;br /&gt;
 # screen in Hintergrund&lt;br /&gt;
 STRG+a d -&amp;gt; detact screen&lt;br /&gt;
Screen Start Skript in new Screen&lt;br /&gt;
 screen -A -m -d -S myScrName ./start.sh&lt;br /&gt;
kill Screen Session&lt;br /&gt;
 STRG+d&lt;br /&gt;
kill Background Screen Session without entering&lt;br /&gt;
 screen -X -S myScrName quit&lt;br /&gt;
&lt;br /&gt;
===PID store and kill===&lt;br /&gt;
Set the name of the process &lt;br /&gt;
 bash -c &amp;quot;exec -a &amp;lt;MyName&amp;gt; &amp;lt;Command&amp;gt;&amp;quot;&lt;br /&gt;
kill the process by name&lt;br /&gt;
 pkill -f MyName&lt;br /&gt;
&lt;br /&gt;
get and store PID&lt;br /&gt;
 /home/user/bin/script.sh &amp;amp; echo $! &amp;gt; script.pid&lt;br /&gt;
kill process using PID&lt;br /&gt;
 kill $(cat script.pid)&lt;br /&gt;
&lt;br /&gt;
===Check if running as root/sudo===&lt;br /&gt;
 if [[ $EUID -ne 0 ]]; then&lt;br /&gt;
   echo &amp;quot;E: run via sudo!!!&amp;quot; 2&amp;gt;&amp;amp;1&lt;br /&gt;
   exit 1&lt;br /&gt;
 fi&lt;br /&gt;
&lt;br /&gt;
check for username&lt;br /&gt;
 if [ `whoami` != &#039;torben&#039; ]&lt;br /&gt;
   then&lt;br /&gt;
     echo &amp;quot;You must be torben to do this.&amp;quot;&lt;br /&gt;
     exit&lt;br /&gt;
 fi&lt;br /&gt;
&lt;br /&gt;
===Add Group and User and Set Password===&lt;br /&gt;
 groupadd testuser&lt;br /&gt;
 useradd testuser -g testuser&lt;br /&gt;
 echo -e &amp;quot;testpass\ntestpass&amp;quot; | (passwd --stdin testuser)&lt;br /&gt;
&lt;br /&gt;
===Monitoring open files of a process===&lt;br /&gt;
 ## check system limit&lt;br /&gt;
 # ulimit -n &lt;br /&gt;
 DATE=`date +&amp;quot;%y-%m-%d_%H-%M&amp;quot;`&lt;br /&gt;
 ID=`pgrep -U username -f ProcessName`&lt;br /&gt;
 FILES=`ls -1 /proc/$ID/fd/ | wc -l` &lt;br /&gt;
 echo &amp;quot;$DATE  $FILES  &amp;quot;&amp;gt;&amp;gt; /home/user/logfile.log&lt;br /&gt;
 ## check process limit&lt;br /&gt;
 # cat /proc/$ID/limits&lt;br /&gt;
&lt;br /&gt;
===Backup Script for folder using tar and filters===&lt;br /&gt;
 #!/bin/bash&lt;br /&gt;
 initialdir=$PWD&lt;br /&gt;
 DATE=`date +&amp;quot;%Y-%m-%d_%H-%M&amp;quot;`&lt;br /&gt;
 &lt;br /&gt;
 # cd to &amp;quot;path of this file /..&amp;quot;&lt;br /&gt;
 dir=&amp;quot;$( cd &amp;quot;$( dirname &amp;quot;${BASH_SOURCE[0]}&amp;quot; )&amp;quot; &amp;amp;&amp;amp; pwd )&amp;quot;&lt;br /&gt;
 cd $dir/..&lt;br /&gt;
 # name of current folder = name to use for backup&lt;br /&gt;
 name=${PWD##*/}&lt;br /&gt;
 backupSource=$PWD&lt;br /&gt;
 &lt;br /&gt;
 cd $backupSource/../backup/&lt;br /&gt;
 zipDir=$(pwd)&lt;br /&gt;
 zipTarget=$zipDir/$name-$DATE.tgz&lt;br /&gt;
 &lt;br /&gt;
 cd $backupSource&lt;br /&gt;
 tar cfvz $zipTarget ./ --exclude=&#039;logs/*&#039; --exclude=&#039;import/*&#039;&lt;br /&gt;
 &lt;br /&gt;
 cd $initialdir&lt;br /&gt;
&lt;br /&gt;
===Zip dir including hidden files===&lt;br /&gt;
 zip -r9q myfile.zip . -x &#039;.git/*&#039;&lt;br /&gt;
&lt;br /&gt;
===Start Stop Script for Java Application===&lt;br /&gt;
 #!/bin/bash&lt;br /&gt;
 # Start and Stop Linux Service for Java application&lt;br /&gt;
 # uses java paramerter -Dname=$PROGRAM&lt;br /&gt;
 #      pgrep -f $PROGRAM &lt;br /&gt;
 #      pkill -f $PROGRAM&lt;br /&gt;
 # with $PROGRAM = Name of directory above location of this script: &lt;br /&gt;
 # /home/torben/Tool/bin/startscript.sh -&amp;gt; Tool&lt;br /&gt;
 # STDOUT and STDERR are written into ./logs/$PROGRAM-STDOUT.log&lt;br /&gt;
 &lt;br /&gt;
 # cd to &amp;quot;path of this file /..&amp;quot; to get dirname to use as service name&lt;br /&gt;
 dir=&amp;quot;$( cd &amp;quot;$( dirname &amp;quot;${BASH_SOURCE[0]}&amp;quot; )&amp;quot; &amp;amp;&amp;amp; pwd )&amp;quot;&lt;br /&gt;
 cd $dir/..&lt;br /&gt;
 dirname=${PWD##*/}&lt;br /&gt;
 # Attention, name has to be unique on the system -&amp;gt; added -Service !!!&lt;br /&gt;
 PROGRAM=$dirname-Service&lt;br /&gt;
 &lt;br /&gt;
 # ensure that this script can only be started as user torben and not accidently as root&lt;br /&gt;
 username=torben &lt;br /&gt;
 &lt;br /&gt;
 jar=&amp;quot;./MyJavaApplication.jar&amp;quot;&lt;br /&gt;
 log_conf=&amp;quot;./config/log4j2.xml&amp;quot;&lt;br /&gt;
 app_params=&amp;quot;-conf ./config/config.xml&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 vm_params=&amp;quot; \&lt;br /&gt;
 -Dname=$PROGRAM \&lt;br /&gt;
 -Dlog4j.configurationFile=$log_conf \&lt;br /&gt;
 -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector \&lt;br /&gt;
 &amp;quot;&lt;br /&gt;
 server_params=&amp;quot; \&lt;br /&gt;
 -server -Xms512m -Xmx2048m \&lt;br /&gt;
 -XX:+UseParallelGC \&lt;br /&gt;
 -XX:+AggressiveOpts \&lt;br /&gt;
 -XX:+UseFastAccessorMethods \&lt;br /&gt;
 -XX:-OmitStackTraceInFastThrow \&lt;br /&gt;
 -XX:+HeapDumpOnOutOfMemoryError \&lt;br /&gt;
 -XX:HeapDumpPath=./logs/rss-heap-$(date +%Y%m%d_%H%M%S).hprof \&lt;br /&gt;
 &amp;quot;&lt;br /&gt;
 # for remote jmx access (disabled by default)&lt;br /&gt;
 jmx_params=&amp;quot; \&lt;br /&gt;
 -Djava.rmi.server.hostname=172.19.13.200 \&lt;br /&gt;
 -Dcom.sun.management.jmxremote.port=1100 \&lt;br /&gt;
 -Dcom.sun.management.jmxremote.local.only=false \&lt;br /&gt;
 -Dcom.sun.management.jmxremote.authenticate=false \&lt;br /&gt;
 -Dcom.sun.management.jmxremote.ssl=false \&lt;br /&gt;
 &amp;quot;&lt;br /&gt;
 JAVAPROGRAM=&amp;quot;\&lt;br /&gt;
 $vm_params \&lt;br /&gt;
 -jar $jar $app_params \&lt;br /&gt;
 $server_params \&lt;br /&gt;
 &amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # for remote jmx access add&lt;br /&gt;
 # $jmx_params &lt;br /&gt;
 &lt;br /&gt;
 # for problems with IPv6 add (i.e. if Startup takes more than 10 minute until first log entry)&lt;br /&gt;
 #-Djava.net.preferIPv4Stack=true&lt;br /&gt;
 #-Djava.net.preferIPv6Addresses=false&lt;br /&gt;
 &lt;br /&gt;
 # for Java debugging:&lt;br /&gt;
 # -Xverbose:codegen &lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 # do not modify below here #&lt;br /&gt;
 &lt;br /&gt;
 # Check user&lt;br /&gt;
 if [ `whoami` != $username ]&lt;br /&gt;
   then&lt;br /&gt;
     echo &amp;quot;ERROR: Only user $username is allowed to modify this service&amp;quot;&lt;br /&gt;
     exit&lt;br /&gt;
 fi&lt;br /&gt;
 &lt;br /&gt;
 # Functions&lt;br /&gt;
 check() {&lt;br /&gt;
    pgrep -U $username -f $PROGRAM &amp;gt;/dev/null&lt;br /&gt;
 }&lt;br /&gt;
 kill() {&lt;br /&gt;
    pkill -U $username -f $PROGRAM&lt;br /&gt;
 }&lt;br /&gt;
 stop() {&lt;br /&gt;
    check&lt;br /&gt;
    if [ $? -eq 0 ]; then&lt;br /&gt;
       kill &amp;gt;/dev/null&lt;br /&gt;
       # Make sure it is stopped before returning&lt;br /&gt;
       until [ $? -ne 0 ]; do&lt;br /&gt;
          sleep 1&lt;br /&gt;
          check&lt;br /&gt;
       done&lt;br /&gt;
       check&lt;br /&gt;
       if [ $? -eq 0 ]; then&lt;br /&gt;
          echo &amp;quot;ERROR stopping application $PROGRAM&amp;quot;&lt;br /&gt;
          exit 1&lt;br /&gt;
       else&lt;br /&gt;
          echo &amp;quot;Application $PROGRAM stopped&amp;quot;&lt;br /&gt;
          #rm -f /var/lock/subsys/[init script name] &amp;lt;--- Perhaps, this lock file has to be removed, too.&lt;br /&gt;
       fi&lt;br /&gt;
    else&lt;br /&gt;
       echo &amp;quot;Application $PROGRAM is not running&amp;quot;&lt;br /&gt;
    fi&lt;br /&gt;
 }&lt;br /&gt;
 start() {&lt;br /&gt;
    check&lt;br /&gt;
    if [ $? -eq 0 ]; then&lt;br /&gt;
       echo &amp;quot;Application $PROGRAM is already running&amp;quot;&lt;br /&gt;
       exit 0&lt;br /&gt;
    fi&lt;br /&gt;
    export LC_ALL=&amp;quot;de_DE@euro&amp;quot;;&lt;br /&gt;
    export LANG=german;&lt;br /&gt;
    nohup java $JAVAPROGRAM 0&amp;lt;&amp;amp;- &amp;amp;&amp;gt;./logs/$PROGRAM-STDOUT.log &amp;amp;&lt;br /&gt;
    echo &amp;quot;Application $PROGRAM started&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
 restart() {&lt;br /&gt;
    stop&lt;br /&gt;
    start&lt;br /&gt;
 }&lt;br /&gt;
 status() {&lt;br /&gt;
    check&lt;br /&gt;
    if [ $? -eq 0 ]; then&lt;br /&gt;
       echo &amp;quot;Application $PROGRAM is running&amp;quot;&lt;br /&gt;
    else&lt;br /&gt;
       echo &amp;quot;Application $PROGRAM is stopped&amp;quot;&lt;br /&gt;
 	  exit 1&lt;br /&gt;
    fi&lt;br /&gt;
 }&lt;br /&gt;
 #Actions&lt;br /&gt;
 case &amp;quot;$1&amp;quot; in&lt;br /&gt;
 start)&lt;br /&gt;
    start&lt;br /&gt;
 ;;&lt;br /&gt;
 stop)&lt;br /&gt;
    stop&lt;br /&gt;
 ;;&lt;br /&gt;
 restart)&lt;br /&gt;
    restart&lt;br /&gt;
 ;;&lt;br /&gt;
 status)&lt;br /&gt;
    status&lt;br /&gt;
 ;;&lt;br /&gt;
 *)&lt;br /&gt;
    echo &amp;quot;Usage: $0 {start|stop|restart|status}&amp;quot;&lt;br /&gt;
    exit 1&lt;br /&gt;
 esac&lt;br /&gt;
 exit 0&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Mac_Photos&amp;diff=5385</id>
		<title>Mac Photos</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Mac_Photos&amp;diff=5385"/>
		<updated>2026-01-12T09:03:31Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* 2. Add missing locations */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Software]][[Category:Apple]]&lt;br /&gt;
==MacOS Photos App==&lt;br /&gt;
&lt;br /&gt;
===My Cleanup Workflow===&lt;br /&gt;
====1. Organize all photos into albums====&lt;br /&gt;
Using Smart Album to find missing:&lt;br /&gt;
 Album is not Any&lt;br /&gt;
Album examples:&lt;br /&gt;
* 2024/240615 Some Event&lt;br /&gt;
* Documents&lt;br /&gt;
* Home&lt;br /&gt;
* Kid 1&lt;br /&gt;
* Kid 2&lt;br /&gt;
* Kids&lt;br /&gt;
* Me&lt;br /&gt;
* People&lt;br /&gt;
* Screenshots&lt;br /&gt;
* Sports/Jogging&lt;br /&gt;
* Sports/Cycling&lt;br /&gt;
&lt;br /&gt;
====2. Add missing locations====&lt;br /&gt;
Using Smart Album to find missing:&lt;br /&gt;
 Photo is not tagged with GPS&lt;br /&gt;
ways to set locations:&lt;br /&gt;
* manual Copy &amp;amp; Paste from other photo in Photo App via Menu -&amp;gt; Image -&amp;gt; Location (works also for multiple photos)&lt;br /&gt;
* manual set location text in Photo App via (I) -&amp;gt; Location, via copy paste of common locations from text/Excel file (works also for multiple photos)&lt;br /&gt;
* using Python osxphotos, see below&lt;br /&gt;
 # pip install --upgrade osxphotos&lt;br /&gt;
 osxphotos add-locations --selected --window 1H&lt;br /&gt;
&lt;br /&gt;
====3. Add missing faces====&lt;br /&gt;
Using Smart Album to find missing:&lt;br /&gt;
 Person is &lt;br /&gt;
(with empty input)&lt;br /&gt;
&lt;br /&gt;
===Interact with Python===&lt;br /&gt;
&lt;br /&gt;
====Copy missing locations from neighboring photos====&lt;br /&gt;
Install osxphotos&lt;br /&gt;
 python3 -m pip install osxphotos&lt;br /&gt;
Update later&lt;br /&gt;
 python3 -m pip install --upgrade osxphotos&lt;br /&gt;
&lt;br /&gt;
Run in dry-run mode on selected photos&lt;br /&gt;
 osxphotos add-locations --selected --window 1H&lt;br /&gt;
&lt;br /&gt;
====Copy locations from GPX file====&lt;br /&gt;
Install&lt;br /&gt;
 osxphotos install gpxpy&lt;br /&gt;
 wget https://raw.githubusercontent.com/RhetTbull/add_photo_locations_from_gpx/refs/heads/main/add_photo_locations_from_gpx.py&lt;br /&gt;
&lt;br /&gt;
Run in dry-run mode on selected photos&lt;br /&gt;
 osxphotos run add_photo_locations_from_gpx.py --selected myFile.gpx&lt;br /&gt;
if time zone info is missing in photo, set UTC offset parameter like --offset +05:45:00&lt;br /&gt;
&lt;br /&gt;
====Extract exif data e.g. gps location====&lt;br /&gt;
 osxphotos query --selected --json | jq &#039;.[].exif_info&#039;&lt;br /&gt;
via Python and copy location to clipboard&lt;br /&gt;
 import subprocess&lt;br /&gt;
 import osxphotos&lt;br /&gt;
 &lt;br /&gt;
 def copy_to_clipboard(text) -&amp;gt; None:&lt;br /&gt;
     process = subprocess.Popen([&amp;quot;pbcopy&amp;quot;], stdin=subprocess.PIPE)&lt;br /&gt;
     process.communicate(input=text.encode(&amp;quot;utf-8&amp;quot;))&lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     photosdb = osxphotos.PhotosDB()&lt;br /&gt;
     try:&lt;br /&gt;
         while 1:&lt;br /&gt;
             results = photosdb.query(osxphotos.QueryOptions(selected=True))&lt;br /&gt;
             for photo in results:&lt;br /&gt;
                 print(photo.original_filename, photo.date)&lt;br /&gt;
                 s = f&amp;quot;{photo.latitude},{photo.longitude}&amp;quot;  # type: ignore&lt;br /&gt;
                 print(s)&lt;br /&gt;
                 copy_to_clipboard(s)&lt;br /&gt;
             input(&amp;quot;Press Enter for next image...&amp;quot;)&lt;br /&gt;
     except KeyboardInterrupt:&lt;br /&gt;
         pass&lt;br /&gt;
&lt;br /&gt;
====Links====&lt;br /&gt;
* https://www.reddit.com/r/ApplePhotos/comments/133vca9/geotag_photos_with_gpx_file/&lt;br /&gt;
* https://github.com/RhetTbull/osxphotos&lt;br /&gt;
* https://github.com/RhetTbull/add_photo_locations_from_gpx&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Media_Streaming_Recording&amp;diff=5384</id>
		<title>Media Streaming Recording</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Media_Streaming_Recording&amp;diff=5384"/>
		<updated>2025-11-29T13:33:06Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Legal:&lt;br /&gt;
* Es ist in Deutschland grundsätzlich erlaubt, eine Privatkopie anzufertigen&lt;br /&gt;
* Die Allgemeinen Geschäftsbedingungen von Spotify etc. untersagen das aber&lt;br /&gt;
* [https://praxistipps.chip.de/musik-legal-bei-spotify-aufnehmen-alle-infos-im-ueberblick_135696 Quelle]&lt;br /&gt;
&lt;br /&gt;
== Audio ==&lt;br /&gt;
e.g. Spotify, Deezer, Google Music, etc.&lt;br /&gt;
&lt;br /&gt;
=== Mac Pre-Requirement ===&lt;br /&gt;
* install [https://github.com/ExistentialAudio/BlackHole BlackHole: macOS audio loopback driver]&lt;br /&gt;
 brew install blackhole-2ch&lt;br /&gt;
* Reboot&lt;br /&gt;
* Set the new blackhole-2ch audio device as playback device&lt;br /&gt;
&lt;br /&gt;
=== Recording ===&lt;br /&gt;
* install Audacity from https://www.audacityteam.org/download/mac/ (use the Version without Muse Hub, called &amp;quot;Universal dmg&amp;quot;)&lt;br /&gt;
* set source to the blackhole-2ch audio device&lt;br /&gt;
&lt;br /&gt;
=== Post-Recoring ===&lt;br /&gt;
* Split by Tracks&lt;br /&gt;
** Use Audacity [https://manual.audacityteam.org/man/label_sounds.html Analyze -&amp;gt; Label Sounds] feature&lt;br /&gt;
** Save to multiple files [https://support.audacityteam.org/audio-editing/splitting-a-recording-into-separate-tracks] via File -&amp;gt; Export Audio -&amp;gt; Export Range = Multiple Files&lt;br /&gt;
* Rename files via txt Track list&lt;br /&gt;
* Set ID3 Metadata&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;br /&gt;
Chrome Browser Settings&lt;br /&gt;
-&amp;gt; System -&amp;gt; Disable Graphics Acceleration&lt;br /&gt;
prevents black screen recoding&lt;br /&gt;
&lt;br /&gt;
Recording Software: [https://obsproject.com/ OBS Studio]&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Lines_of_Code_(cloc)&amp;diff=5369</id>
		<title>Lines of Code (cloc)</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Lines_of_Code_(cloc)&amp;diff=5369"/>
		<updated>2025-11-02T06:28:59Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;see https://github.com/AlDanial/cloc&lt;br /&gt;
&lt;br /&gt;
Install on MacOS&lt;br /&gt;
 brew install cloc&lt;br /&gt;
Run on git repo&lt;br /&gt;
 cloc --vcs=git&lt;br /&gt;
Note, that it counts the package manager lock files.&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Npm&amp;diff=5364</id>
		<title>Npm</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Npm&amp;diff=5364"/>
		<updated>2025-10-25T05:29:03Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Globally Installed packages */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==pnpm==&lt;br /&gt;
 # better and faster than npm&lt;br /&gt;
 brew install pnpm&lt;br /&gt;
 # or &lt;br /&gt;
 npm install -g pnpm&lt;br /&gt;
 # update single package&lt;br /&gt;
 pnpm up vite&lt;br /&gt;
 # update all dependencies&lt;br /&gt;
 pnpm up&lt;br /&gt;
&lt;br /&gt;
==npm Packagemanager for Javascript Packages==&lt;br /&gt;
===Globally Installed packages===&lt;br /&gt;
 brew install node&lt;br /&gt;
 &lt;br /&gt;
 npm install -g prettier eslint cspell&lt;br /&gt;
 # list global packages&lt;br /&gt;
 npm ls -g&lt;br /&gt;
 # update all&lt;br /&gt;
 npm update -g&lt;br /&gt;
&lt;br /&gt;
===Install a package===&lt;br /&gt;
 # install as main package&lt;br /&gt;
 npm install --save vue&lt;br /&gt;
 # install as dev package&lt;br /&gt;
 npm install --save-dev cypress&lt;br /&gt;
&lt;br /&gt;
===Update a package===&lt;br /&gt;
 npm update cypress&lt;br /&gt;
force update&lt;br /&gt;
 npm remove cypress&lt;br /&gt;
 npm install --save-dev cypress&lt;br /&gt;
&lt;br /&gt;
alternative: npm-check-updates&lt;br /&gt;
 npm i -g npm-check-updates&lt;br /&gt;
 ncu -u cypress&lt;br /&gt;
 npm install&lt;br /&gt;
 # or update all packages&lt;br /&gt;
 ncu -u&lt;br /&gt;
 npm install&lt;br /&gt;
&lt;br /&gt;
===Windows Error about Execution_Policies===&lt;br /&gt;
 npm.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies&lt;br /&gt;
&lt;br /&gt;
open Admin PowerShell and run&lt;br /&gt;
 Set-ExecutionPolicy RemoteSigned&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Uberspace&amp;diff=5358</id>
		<title>Uberspace</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Uberspace&amp;diff=5358"/>
		<updated>2025-10-18T22:35:57Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Python FastAPI */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Webserver]][[Category:Linux]]&lt;br /&gt;
==Manuals==&lt;br /&gt;
https://manual.uberspace.de and https://lab.uberspace.de&lt;br /&gt;
&lt;br /&gt;
==PHP==&lt;br /&gt;
see https://manual.uberspace.de/lang-php/&lt;br /&gt;
&lt;br /&gt;
to restart&lt;br /&gt;
 uberspace tools restart php&lt;br /&gt;
&lt;br /&gt;
===php-fpm===&lt;br /&gt;
 less /opt/uberspace/etc/$USER/php-fpm.conf&lt;br /&gt;
&lt;br /&gt;
==Python FastAPI==&lt;br /&gt;
see https://lab.uberspace.de/guide_fastapi/&lt;br /&gt;
 &lt;br /&gt;
 # var 1: venv&lt;br /&gt;
 python3.11 -m venv venv&lt;br /&gt;
 source venv/bin/activate&lt;br /&gt;
 pip install fastapi uvicorn&lt;br /&gt;
 pip install fastapi.responses&lt;br /&gt;
 pip install gunicorn uvloop httptools&lt;br /&gt;
 deactivate&lt;br /&gt;
 &lt;br /&gt;
 # var 2: not use venv&lt;br /&gt;
 pip3.11 install --user fastapi uvicorn fastapi.responses gunicorn uvloop httptools&lt;br /&gt;
 &lt;br /&gt;
 # config backend&lt;br /&gt;
 uberspace web backend set /strava-be --http --port 9001&lt;br /&gt;
 # --remove-prefix&lt;br /&gt;
 uberspace web backend list&lt;br /&gt;
 &lt;br /&gt;
 supervisorctl restart fastapi&lt;br /&gt;
 &lt;br /&gt;
~/fastapi/conf.py&lt;br /&gt;
 #!/usr/bin/env python3.10&lt;br /&gt;
 &lt;br /&gt;
 # apply changes via&lt;br /&gt;
 # supervisorctl restart fastapi&lt;br /&gt;
 &lt;br /&gt;
 import os&lt;br /&gt;
 &lt;br /&gt;
 app_path = os.environ[&amp;quot;HOME&amp;quot;] + &amp;quot;/fastapi&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # Gunicorn configuration&lt;br /&gt;
 wsgi_app = &amp;quot;main:api&amp;quot;&lt;br /&gt;
 bind = &amp;quot;:9001&amp;quot;&lt;br /&gt;
 chdir = app_path&lt;br /&gt;
 workers = 1&lt;br /&gt;
 worker_class = &amp;quot;uvicorn.workers.UvicornWorker&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 # errorlog = app_path + &amp;quot;/errors.log&amp;quot;&lt;br /&gt;
 # accesslog = app_path + &amp;quot;/access.log&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
~/fastapi/main.py&lt;br /&gt;
 #!/usr/bin/env python3.10&lt;br /&gt;
 &lt;br /&gt;
 # apply changes via&lt;br /&gt;
 # supervisorctl restart fastapi&lt;br /&gt;
 &lt;br /&gt;
 from fastapi import FastAPI&lt;br /&gt;
 from fastapi.responses import JSONResponse&lt;br /&gt;
 from pydantic import BaseModel&lt;br /&gt;
 &lt;br /&gt;
 import subprocess&lt;br /&gt;
  &lt;br /&gt;
 class StravaSession(BaseModel):&lt;br /&gt;
     sessionId: str&lt;br /&gt;
 &lt;br /&gt;
 api = FastAPI()&lt;br /&gt;
  &lt;br /&gt;
 @api.post(&amp;quot;/strava-be/activityStats2/&amp;quot;)&lt;br /&gt;
 async def activityStats2(session: StravaSession):&lt;br /&gt;
     # return {session}&lt;br /&gt;
     response = {}&lt;br /&gt;
     # response = {&amp;quot;session&amp;quot;: session.sessionId}&lt;br /&gt;
     process = subprocess.run(&lt;br /&gt;
         [&lt;br /&gt;
             &amp;quot;python3.10&amp;quot;,&lt;br /&gt;
             &amp;quot;/var/www/virtual/entorb/html/strava/activityStats2.py&amp;quot;,&lt;br /&gt;
             session.sessionId,&lt;br /&gt;
         ],&lt;br /&gt;
         capture_output=True,&lt;br /&gt;
     )&lt;br /&gt;
 &lt;br /&gt;
     if process.returncode == 0:&lt;br /&gt;
         response[&amp;quot;status&amp;quot;] = &amp;quot;ok&amp;quot;&lt;br /&gt;
         response_code = 200&lt;br /&gt;
         # response[&amp;quot;process_stdout&amp;quot;] = process.stdout.decode()&lt;br /&gt;
     else:&lt;br /&gt;
         response[&amp;quot;status&amp;quot;] = &amp;quot;error&amp;quot;&lt;br /&gt;
         response_code = 400&lt;br /&gt;
         # response[&amp;quot;process_stdout&amp;quot;] = process.stdout.decode()&lt;br /&gt;
         response[&amp;quot;error_message&amp;quot;] = process.stderr.decode()&lt;br /&gt;
     return JSONResponse(content=response, status_code=response_code)&lt;br /&gt;
 &lt;br /&gt;
 # To test this app locally, uncomment:&lt;br /&gt;
 # import uvicorn&lt;br /&gt;
 # uvicorn.run(api, host=&amp;quot;localhost&amp;quot;, port=8001)&lt;br /&gt;
 &lt;br /&gt;
 # curl -i -X POST &amp;quot;https://entorb.net/strava-be/activityStats2/&amp;quot; -H &amp;quot;Content-Type: application/json&amp;quot; -d &#039;{&amp;quot;sessionId&amp;quot;: &amp;quot;1234&amp;quot;}&#039;&lt;br /&gt;
&lt;br /&gt;
==Checkliste neuer U7 Account==&lt;br /&gt;
 ~/.bashrc und ~/.bash_profile von altem angeglichen&lt;br /&gt;
Logs aktiviert&lt;br /&gt;
 uberspace web log access enable&lt;br /&gt;
 uberspace web log apache_error enable&lt;br /&gt;
 uberspace web log php_error enable&lt;br /&gt;
 uberspace web errorpage 500 disable&lt;br /&gt;
&lt;br /&gt;
==Probleme bei der Migration Uberspace U6 zu U7==&lt;br /&gt;
===0. E-Mails===&lt;br /&gt;
Es scheinen keine E-Mails mehr rein und raus zu gehen. (Account xxx@entorb.net). &lt;br /&gt;
&lt;br /&gt;
Neuer Webmailer ist https://webmail.uberspace.de , dieser zeigt aber erst nach Abschluss der Migration auf das neue Postfach. Solange die Migration nicht abgeschlossen ist, muss im Mail Client (wie Thunderbird) dieser Login für den U7 Mail Account verwendet werden:&lt;br /&gt;
statt xxx@entorb.net -&amp;gt; xxx@entorb.uber.space&lt;br /&gt;
&lt;br /&gt;
===1. HTTPS Zertifikat===&lt;br /&gt;
HTTPS Zertifikat war auf einmal nicht mehr gültig für https://www.entorb.net . Das hatte erst auch auf dem U7 funktioniert, nun plötzlich nicht mehr. UPDATE: Problem verschwand von alleine, hing vielleicht mit dem DNS Umzug zusammen.&lt;br /&gt;
&lt;br /&gt;
===2. bestehende ezmlm Mailinglisten===&lt;br /&gt;
ezmlm ist gemäß [https://lab.uberspace.de/guide_ezmlm.html Anleitung] installiert, allerdings sagt mir&lt;br /&gt;
 ezmlm-make -+ ~/ezmlm/oldlist ~/.qmail-oldlist oldlist entorb.uber.space&lt;br /&gt;
 &amp;gt; ezmlm-make: fatal: unable to stat /etc/ezmlm/de: file does not exist&lt;br /&gt;
&lt;br /&gt;
Wenn ich hingegen testweise einen neuen Verteiler anlege tut es:&lt;br /&gt;
 ezmlm-make -A -u -m -5 xxx@entorb.net ~/ezmlm/mylist ~/.qmail-mylist&lt;br /&gt;
mylist entorb.uber.space&lt;br /&gt;
und diesen kann ich dann auch bearbeiten via&lt;br /&gt;
 ezmlm-make -+ ~/ezmlm/mylist ~/.qmail-mylist mylist entorb.uber.space&lt;br /&gt;
&lt;br /&gt;
Lösung:&lt;br /&gt;
 in ezmlm/oldlist/ezmlmrc die entsprechende Zeile löschen&lt;br /&gt;
&lt;br /&gt;
===3. Zugriff auf Dateien außerhab von ~/html===&lt;br /&gt;
Ich hatte einige Dateien (wie zB eine sqlite DB) in meinem Home (~/) liegen. Auf diese sollen Skripte die sich unter&lt;br /&gt;
 ~/html-&amp;gt;/var/www/virtual/entorb/html &lt;br /&gt;
befinden zugreifen können. Das funktionierte nicht mehr, da SELinux hier rein grätscht &lt;br /&gt;
&lt;br /&gt;
Beste Lösung: Die Dateien auf die der httpd zugreifen soll nach&lt;br /&gt;
 /var/www/virtual/entorb/ &lt;br /&gt;
verschieben und im Home Symlink hinterlassen.&lt;br /&gt;
&lt;br /&gt;
Erster Ansatz: der SELinux Kontexte lässt sich so setzen (Siehe [https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/selinux_users_and_administrators_guide/sect-security-enhanced_linux-working_with_selinux-selinux_contexts_labeling_files]&lt;br /&gt;
).&lt;br /&gt;
 chcon -t httpd_sys_content_t file-name&lt;br /&gt;
half hier aber nicht:&lt;br /&gt;
 ls -laZ /home/ |grep ento&lt;br /&gt;
 drwx------. entorb      entorb      unconfined_u:object_r:user_home_dir_t:s0 entorb&lt;br /&gt;
&lt;br /&gt;
===4. Perl Module, wie zB Excel::Writer::XLSX===&lt;br /&gt;
Installiert via&lt;br /&gt;
 cpanm Excel::Writer::XLSX&lt;br /&gt;
&lt;br /&gt;
Führe ich das Skript ~/html/test.pl in der Shell aus, wird das Modul&lt;br /&gt;
gefunden und geladen. Im Browser via https://entorb.net/test.pl wird&lt;br /&gt;
das Modul nicht gefunden.&lt;br /&gt;
&lt;br /&gt;
Ursache 4.1 war dass SELinux den Zugriff des httpd auf /home/entorb/perl5 blockierte&lt;br /&gt;
&lt;br /&gt;
Lösung:&lt;br /&gt;
~/perl5 Verzeichnis nach &lt;br /&gt;
 /var/www/virtual/entorb/perl5&lt;br /&gt;
verschoben und im Home einen Symlink hinterlassen. &lt;br /&gt;
&lt;br /&gt;
Dann zuerst lib::local installiert, dann andere Module.&lt;br /&gt;
&lt;br /&gt;
Ursache 4.2 war dass ich in der .bashrc dies gesetzt habe:&lt;br /&gt;
 eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)&lt;br /&gt;
das musste ich im Perl Skript ebenfalls hinterlegen:&lt;br /&gt;
 use lib (&#039;/var/www/virtual/entorb/perl5/lib/perl5&#039;);&lt;br /&gt;
 # aber !kein! use local::lib; !!!&lt;br /&gt;
&lt;br /&gt;
===5. sendmail===&lt;br /&gt;
ich stellte fest, dass ich im U7 aus Python oder Perl CGI Skripten nicht mehr auf sendmail zugreifen kann. Über die Shell kommt die Mail sofort an. Beispiel:&lt;br /&gt;
8&amp;lt;---&lt;br /&gt;
 import os&lt;br /&gt;
 SENDMAIL = &amp;quot;/usr/sbin/sendmail&amp;quot;&lt;br /&gt;
 to = &amp;quot;xxx@entorb.net&amp;quot;&lt;br /&gt;
 subject   = &amp;quot;testmail&amp;quot;&lt;br /&gt;
 sender = &amp;quot;U7 &amp;lt;no-reply@entorb.net&amp;gt;&amp;quot;&lt;br /&gt;
 body = &amp;quot;leer&amp;quot;&lt;br /&gt;
 mail = f&amp;quot;To: {to}\nSubject: {subject}\nFrom: {sender}\nContent-Type:&lt;br /&gt;
 text/plain; charset=\&amp;quot;utf-8\&amp;quot;\n\n{body}&amp;quot;&lt;br /&gt;
 p = os.popen(f&amp;quot;{SENDMAIL} -t -i&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
 p.write(mail)&lt;br /&gt;
 p.close()&lt;br /&gt;
 8&amp;lt;---&lt;br /&gt;
Lösung: statt Versand aus Web-Skripten/Seiten via sendmail, besser aus den Skripten ein Insert in eine SQLite DB machen und diese via cronjob und php mail() Funktion periodisch abarbeiten.&lt;br /&gt;
&lt;br /&gt;
===6. Wiederholung der Migration===&lt;br /&gt;
Entweder einfach Migration auf dem U6 nochmal starten via&lt;br /&gt;
 uberspace-move-account -u entorb&lt;br /&gt;
oder in Dashboard den Umzug abbrechen und neu starten -&amp;gt; anderer Server.&lt;br /&gt;
&lt;br /&gt;
===7. MySQL DB Passwort angeblich zu kurz===&lt;br /&gt;
Das Migrationsskript hatte behauptet, dass mein MySQL Passwort zu kurz sei. Stimmte nicht, vermutlich kam es mit den Sonderzeichen durcheinander. Habe daher ein neues langes ohne Sonderzeichen vergeben.&lt;br /&gt;
&lt;br /&gt;
==Streamlit==&lt;br /&gt;
see also [https://github.com/entorb/strava-streamlit/blob/main/README.md strava-streamlit readme]&lt;br /&gt;
&lt;br /&gt;
Installation&lt;br /&gt;
 pip3.11 install --user streamlit&lt;br /&gt;
&lt;br /&gt;
Setup dir and config&lt;br /&gt;
 mkdir ~/strava-streamlit&lt;br /&gt;
 cd ~/strava-streamlit&lt;br /&gt;
 mkdir .streamlit&lt;br /&gt;
 vim .streamlit/config.toml&lt;br /&gt;
&lt;br /&gt;
 [browser]&lt;br /&gt;
 gatherUsageStats = false&lt;br /&gt;
 &lt;br /&gt;
 [server]&lt;br /&gt;
 headless = true&lt;br /&gt;
 port = 8501&lt;br /&gt;
 baseUrlPath = &amp;quot;/strava-streamlit&amp;quot;&lt;br /&gt;
&lt;br /&gt;
baseUrlPath is important when not running in web server document root &lt;br /&gt;
&lt;br /&gt;
create minimal Streamlit app&lt;br /&gt;
 vim src/app.py&lt;br /&gt;
&lt;br /&gt;
 import streamlit as st&lt;br /&gt;
 st.title(&amp;quot;Test&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
start it manually as testrun (stop by ctrl+c)&lt;br /&gt;
 streamlit run src/app.py&lt;br /&gt;
&lt;br /&gt;
Add web backend&lt;br /&gt;
 uberspace web backend set /strava-streamlit --http --port 8501&lt;br /&gt;
&lt;br /&gt;
Access it via browser&lt;br /&gt;
https://entorb.net/strava-streamlit&lt;br /&gt;
&lt;br /&gt;
Create Service&lt;br /&gt;
 vim ~/etc/services.d/strava-streamlit.ini&lt;br /&gt;
&lt;br /&gt;
 [program:strava-streamlit]&lt;br /&gt;
 directory=%(ENV_HOME)s/strava-streamlit&lt;br /&gt;
 command=streamlit run src/app.py&lt;br /&gt;
 # or &lt;br /&gt;
 # command=python3.11 -O -m streamlit run src/app.py&lt;br /&gt;
 loglevel=info&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Start Service&lt;br /&gt;
 supervisorctl reread&lt;br /&gt;
 supervisorctl update&lt;br /&gt;
 supervisorctl status&lt;br /&gt;
 supervisorctl restart strava-streamlit&lt;br /&gt;
&lt;br /&gt;
Check log&lt;br /&gt;
 supervisorctl tail -f strava-streamlit&lt;br /&gt;
 # as this is empty:&lt;br /&gt;
 tail -f ~/logs/supervisord.log&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Raspbian&amp;diff=5355</id>
		<title>Raspbian</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Raspbian&amp;diff=5355"/>
		<updated>2025-10-17T15:48:45Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* MQTT */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Raspi]][[Category:Linux]]&lt;br /&gt;
Stuff for my Raspi / Raspi3 / Raspi4&lt;br /&gt;
===basics===&lt;br /&gt;
&lt;br /&gt;
default login: pi/raspberry&lt;br /&gt;
&lt;br /&gt;
initial package updates&lt;br /&gt;
 sudo apt-get update&lt;br /&gt;
 sudo apt-get dist-upgrade&lt;br /&gt;
 sudo apt purge -y cloud-init &lt;br /&gt;
 sudo apt-get autoremove --purge&lt;br /&gt;
 sudo apt-get install vim mc git&lt;br /&gt;
&lt;br /&gt;
===raspi-config===&lt;br /&gt;
 sudo raspi-config&lt;br /&gt;
 - Update&lt;br /&gt;
 - Network : set hostname&lt;br /&gt;
 - Interfacing options : Enable SSH&lt;br /&gt;
 - Localization -&amp;gt; change Keyboard Layout&lt;br /&gt;
 - advanced -&amp;gt; disable logging (to reduce SD usage)&lt;br /&gt;
 - Locale -&amp;gt; en.US.UTF-8&lt;br /&gt;
&lt;br /&gt;
for locale issues run&lt;br /&gt;
 sudo dpkg-reconfigure locales&lt;br /&gt;
&lt;br /&gt;
===SSH password-free-login===&lt;br /&gt;
 ssh-copy-id pi@raspi&lt;br /&gt;
&lt;br /&gt;
===Static IP===&lt;br /&gt;
(not used, set via DHCP server FritzBox instead)&lt;br /&gt;
 sudo nmtui&lt;br /&gt;
&lt;br /&gt;
===NTP Enable Time Sync===&lt;br /&gt;
 sudo apt-get install systemd-timesyncd&lt;br /&gt;
 sudo timedatectl status&lt;br /&gt;
 # sudo timedatectl set-ntp true&lt;br /&gt;
&lt;br /&gt;
===pi-hole, unbound, log in ram, ... ===&lt;br /&gt;
 https://forum.kuketz-blog.de/viewtopic.php?f=53&amp;amp;t=8759&lt;br /&gt;
which IPv6 address?&lt;br /&gt;
 ip a ls |grep fd00&lt;br /&gt;
&lt;br /&gt;
===config.txt===&lt;br /&gt;
====Location====&lt;br /&gt;
Raspbian old:&lt;br /&gt;
 sudo vim /boot/firmware/config.txt &lt;br /&gt;
&lt;br /&gt;
Raspbian old:&lt;br /&gt;
 sudo vim /boot/config.txt &lt;br /&gt;
&lt;br /&gt;
Libreelec:&lt;br /&gt;
 mount -o remount,rw /flash&lt;br /&gt;
 nano /flash/config.txt&lt;br /&gt;
 mount -o remount,ro /flas&lt;br /&gt;
&lt;br /&gt;
====Power Saving====&lt;br /&gt;
see https://blues.io/blog/tips-tricks-optimizing-raspberry-pi-power/&lt;br /&gt;
&lt;br /&gt;
====disable Wifi and Bluetooth====&lt;br /&gt;
 [all]&lt;br /&gt;
 # disable wifi&lt;br /&gt;
 dtoverlay=disable-wifi&lt;br /&gt;
 # disable bluetooth&lt;br /&gt;
 dtoverlay=disable-bt&lt;br /&gt;
&lt;br /&gt;
====Disable Onboard LEDs====&lt;br /&gt;
 [pi4]&lt;br /&gt;
 # Disable the PWR LED&lt;br /&gt;
 dtparam=pwr_led_trigger=none&lt;br /&gt;
 dtparam=pwr_led_activelow=off&lt;br /&gt;
 # Disable the Activity LED&lt;br /&gt;
 dtparam=act_led_trigger=none&lt;br /&gt;
 dtparam=act_led_activelow=off&lt;br /&gt;
 # Disable ethernet port LEDs&lt;br /&gt;
 dtparam=eth_led0=4&lt;br /&gt;
 dtparam=eth_led1=4&lt;br /&gt;
&lt;br /&gt;
===cmdline.txt===&lt;br /&gt;
add &amp;quot;quiet&amp;quot; to remove kernel boot messages&lt;br /&gt;
 sudo vim /boot/firmware/cmdline.txt&lt;br /&gt;
 console=tty1 root=PARTUUID=eedf3243-02 rootfstype=ext4 fsck.repair=yes rootwait quiet cfg80211.ieee80211_regdom=DE&lt;br /&gt;
&lt;br /&gt;
===CPU===&lt;br /&gt;
from https://raspberrypi.stackexchange.com/questions/103653/setting-the-maximum-clock-speed&lt;br /&gt;
&lt;br /&gt;
set powersave at runtime&lt;br /&gt;
 sudo apt install cpufrequtils&lt;br /&gt;
 sudo cpufreq-set -g powersave&lt;br /&gt;
&lt;br /&gt;
===systemctl_Services===&lt;br /&gt;
see [[Linux_Bash_Shell_Scripting#systemctl_Services]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Install uv===&lt;br /&gt;
https://docs.astral.sh/uv/getting-started/installation/&lt;br /&gt;
 # install&lt;br /&gt;
 curl -LsS f https://astral.sh/uv/install.sh | sh&lt;br /&gt;
 # update&lt;br /&gt;
 uv self update&lt;br /&gt;
&lt;br /&gt;
===MQTT===&lt;br /&gt;
 sudo apt install mosquitto mosquitto-clients&lt;br /&gt;
 &lt;br /&gt;
 sudo vim /etc/mosquitto/mosquitto.conf&lt;br /&gt;
 per_listener_settings true&lt;br /&gt;
 allow_anonymous false&lt;br /&gt;
 listener 1883&lt;br /&gt;
 password_file /etc/mosquitto/passwd&lt;br /&gt;
 # see http://www.steves-internet-guide.com/mqtt-username-password-example/&lt;br /&gt;
  &lt;br /&gt;
 sudo vim /etc/environment&lt;br /&gt;
 PW_MQTT=XXX&lt;br /&gt;
&lt;br /&gt;
===Other===&lt;br /&gt;
grant access to tty to user pi&lt;br /&gt;
 sudo usermod -a -G tty pi&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Pi-hole&amp;diff=5348</id>
		<title>Pi-hole</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Pi-hole&amp;diff=5348"/>
		<updated>2025-10-17T12:39:13Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Install */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Raspi]][[Category:Software]]&lt;br /&gt;
===Install===&lt;br /&gt;
see https://docs.pi-hole.net/main/basic-install/&lt;br /&gt;
 git clone --depth 1 https://github.com/pi-hole/pi-hole.git Pi-hole&lt;br /&gt;
 cd &amp;quot;Pi-hole/automated install/&amp;quot;&lt;br /&gt;
 sudo bash basic-install.sh&lt;br /&gt;
afterwards&lt;br /&gt;
 pihole setpassword&lt;br /&gt;
&lt;br /&gt;
===Config /etc/pihole/pihole-FTL.conf===&lt;br /&gt;
 #; default = 1000/60&lt;br /&gt;
 RATE_LIMIT=3000/60 &lt;br /&gt;
 &lt;br /&gt;
 #; I do not need long-time stats (default is 365 days!)&lt;br /&gt;
 # MAXDBDAYS=2&lt;br /&gt;
 MAXDBDAYS=0&lt;br /&gt;
 &lt;br /&gt;
 #; frequency in minutes to update the DB&lt;br /&gt;
 DBINTERVAL=60&lt;br /&gt;
 &lt;br /&gt;
 #; DB location better keep in /etc/pihole/ where also the gravity.db is&lt;br /&gt;
 #; DBFILE=/var/pihole/pihole-FTL.db&lt;br /&gt;
 &lt;br /&gt;
 #; show all data&lt;br /&gt;
 PRIVACYLEVEL=0&lt;br /&gt;
&lt;br /&gt;
===Adlists===&lt;br /&gt;
 https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts	&lt;br /&gt;
 https://raw.githubusercontent.com/PolishFiltersTeam/KADhosts/master/KADhosts.txt	&lt;br /&gt;
 https://raw.githubusercontent.com/FadeMind/hosts.extras/master/add.Spam/hosts	&lt;br /&gt;
 https://v.firebog.net/hosts/static/w3kbl.txt	&lt;br /&gt;
 https://raw.githubusercontent.com/matomo-org/referrer-spam-blacklist/master/spammers.txt	&lt;br /&gt;
 https://someonewhocares.org/hosts/zero/hosts	&lt;br /&gt;
 https://raw.githubusercontent.com/VeleSila/yhosts/master/hosts	&lt;br /&gt;
 https://winhelp2002.mvps.org/hosts.txt	&lt;br /&gt;
 https://v.firebog.net/hosts/neohostsbasic.txt	&lt;br /&gt;
 https://raw.githubusercontent.com/RooneyMcNibNug/pihole-stuff/master/SNAFU.txt	&lt;br /&gt;
 https://paulgb.github.io/BarbBlock/blacklists/hosts-file.txt	&lt;br /&gt;
 https://adaway.org/hosts.txt	&lt;br /&gt;
 https://v.firebog.net/hosts/AdguardDNS.txt	&lt;br /&gt;
 https://v.firebog.net/hosts/Admiral.txt	&lt;br /&gt;
 https://raw.githubusercontent.com/anudeepND/blacklist/master/adservers.txt	&lt;br /&gt;
 https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt	&lt;br /&gt;
 https://v.firebog.net/hosts/Easylist.txt	&lt;br /&gt;
 https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&amp;amp;showintro=0&amp;amp;mimetype=plaintext	&lt;br /&gt;
 https://raw.githubusercontent.com/FadeMind/hosts.extras/master/UncheckyAds/hosts	&lt;br /&gt;
 https://raw.githubusercontent.com/bigdargon/hostsVN/master/hosts	&lt;br /&gt;
 https://raw.githubusercontent.com/jdlingyu/ad-wars/master/hosts	&lt;br /&gt;
 https://v.firebog.net/hosts/Easyprivacy.txt	&lt;br /&gt;
 https://v.firebog.net/hosts/Prigent-Ads.txt	&lt;br /&gt;
 https://raw.githubusercontent.com/FadeMind/hosts.extras/master/add.2o7Net/hosts	&lt;br /&gt;
 https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/spy.txt	&lt;br /&gt;
 https://hostfiles.frogeye.fr/firstparty-trackers-hosts.txt	&lt;br /&gt;
 https://hostfiles.frogeye.fr/multiparty-trackers-hosts.txt	&lt;br /&gt;
 https://www.github.developerdan.com/hosts/lists/ads-and-tracking-extended.txt	&lt;br /&gt;
 https://raw.githubusercontent.com/Perflyst/PiHoleBlocklist/master/android-tracking.txt	&lt;br /&gt;
 https://raw.githubusercontent.com/Perflyst/PiHoleBlocklist/master/SmartTV.txt	&lt;br /&gt;
 https://raw.githubusercontent.com/Perflyst/PiHoleBlocklist/master/AmazonFireTV.txt	&lt;br /&gt;
 https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-blocklist.txt	&lt;br /&gt;
 https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareHosts.txt	&lt;br /&gt;
 https://osint.digitalside.it/Threat-Intel/lists/latestdomains.txt	&lt;br /&gt;
 https://s3.amazonaws.com/lists.disconnect.me/simple_malvertising.txt	&lt;br /&gt;
 https://v.firebog.net/hosts/Prigent-Crypto.txt	&lt;br /&gt;
 https://bitbucket.org/ethanr/dns-blacklists/raw/8575c9f96e5b4a1308f2f12394abd86d0927a4a0/bad_lists/Mandiant_APT1_Report_Appendix_D.txt	&lt;br /&gt;
 https://phishing.army/download/phishing_army_blocklist_extended.txt	&lt;br /&gt;
 https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-malware.txt	&lt;br /&gt;
 https://raw.githubusercontent.com/Spam404/lists/master/main-blacklist.txt	&lt;br /&gt;
 https://raw.githubusercontent.com/FadeMind/hosts.extras/master/add.Risk/hosts	&lt;br /&gt;
 https://urlhaus.abuse.ch/downloads/hostfile/	&lt;br /&gt;
 https://v.firebog.net/hosts/Prigent-Malware.txt	&lt;br /&gt;
 https://v.firebog.net/hosts/Shalla-mal.txt	&lt;br /&gt;
 https://zerodot1.gitlab.io/CoinBlockerLists/hosts_browser	&lt;br /&gt;
 https://raw.githubusercontent.com/mhhakim/pihole-blocklist/master/porn.txt	&lt;br /&gt;
 https://raw.githubusercontent.com/BlackJack8/iOSAdblockList/master/Regular%20Hosts.txt&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=InfluxDB&amp;diff=5344</id>
		<title>InfluxDB</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=InfluxDB&amp;diff=5344"/>
		<updated>2025-10-17T11:48:06Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Raspi]][[Category:Coding]][[Category:Python]]&lt;br /&gt;
==Install on Raspberry Pi in Raspbian==&lt;br /&gt;
===2025===&lt;br /&gt;
 sudo apt install influxdb influxdb-client &lt;br /&gt;
&lt;br /&gt;
Create DB and Users&lt;br /&gt;
 # 1. create admin user&lt;br /&gt;
 influx&lt;br /&gt;
 create user uadmin with password &#039;password1&#039; WITH ALL PRIVILEGES&lt;br /&gt;
 exit&lt;br /&gt;
 &lt;br /&gt;
 # 2. create DB and other users&lt;br /&gt;
 export INFLUX_USERNAME=uadmin&lt;br /&gt;
 export INFLUX_PASSWORD=password1&lt;br /&gt;
 &lt;br /&gt;
 influx&lt;br /&gt;
 USE raspi&lt;br /&gt;
 create user uwrite with password &#039;password2&#039;&lt;br /&gt;
 create user uread  with password &#039;password3&#039;&lt;br /&gt;
 grant read on raspi to uread&lt;br /&gt;
 grant write on raspi to uwrite&lt;br /&gt;
 exit&lt;br /&gt;
&lt;br /&gt;
=== Old version===&lt;br /&gt;
Nice Howto: [https://www.circuits.dk/install-grafana-influxdb-raspberry/]&lt;br /&gt;
&lt;br /&gt;
 wget -qO- https://repos.influxdata.com/influxdb.key | sudo apt-key add -&lt;br /&gt;
 source /etc/os-release&lt;br /&gt;
 test $VERSION_ID = &amp;quot;7&amp;quot; &amp;amp;&amp;amp; echo &amp;quot;deb https://repos.influxdata.com/debian wheezy stable&amp;quot; | sudo tee /etc/apt/sources.list.d/influxdb.list&lt;br /&gt;
 test $VERSION_ID = &amp;quot;8&amp;quot; &amp;amp;&amp;amp; echo &amp;quot;deb https://repos.influxdata.com/debian jessie stable&amp;quot; | sudo tee /etc/apt/sources.list.d/influxdb.list&lt;br /&gt;
 test $VERSION_ID = &amp;quot;9&amp;quot; &amp;amp;&amp;amp; echo &amp;quot;deb https://repos.influxdata.com/debian stretch stable&amp;quot; | sudo tee /etc/apt/sources.list.d/influxdb.list&lt;br /&gt;
 sudo apt-get install influxdb&lt;br /&gt;
 sudo service influxdb restart&lt;br /&gt;
&lt;br /&gt;
==Setup==&lt;br /&gt;
===influxdb.conf===&lt;br /&gt;
 sudo vim /etc/influxdb/influxdb.conf&lt;br /&gt;
 &lt;br /&gt;
 [http]&lt;br /&gt;
 # Determines whether HTTP endpoint is enabled.&lt;br /&gt;
 enabled = true&lt;br /&gt;
 # require login of user&lt;br /&gt;
 auth-enabled = true&lt;br /&gt;
 # prevent logging of each HTTP request to reduce load on ssd&lt;br /&gt;
 log-enabled = false&lt;br /&gt;
 &lt;br /&gt;
 [logging]&lt;br /&gt;
 level = &amp;quot;warn&amp;quot;&lt;br /&gt;
&lt;br /&gt;
in ~/.bashrc&lt;br /&gt;
 export INFLUX_USERNAME=admin&lt;br /&gt;
 export INFLUX_PASSWORD=password1&lt;br /&gt;
&lt;br /&gt;
Runs per default on port 8086&lt;br /&gt;
&lt;br /&gt;
==Administration==&lt;br /&gt;
===CLI: command line interface===&lt;br /&gt;
 sudo apt-get install influxdb-client&lt;br /&gt;
 influx -precision rfc3339 # for human readable time format&lt;br /&gt;
 # or &lt;br /&gt;
 influx&lt;br /&gt;
 precision rfc3339&lt;br /&gt;
&lt;br /&gt;
===User permissions===&lt;br /&gt;
user permissions are defined per database, so if you need only one level of permissions, one database might be enough&lt;br /&gt;
 CREATE DATABASE raspi&lt;br /&gt;
 USE raspi&lt;br /&gt;
suggestion: one user per permission&lt;br /&gt;
 create user uadmin with password &#039;password1&#039; WITH ALL PRIVILEGES&lt;br /&gt;
 create user uwrite with password &#039;password2&#039;&lt;br /&gt;
 create user uread  with password &#039;password3&#039;&lt;br /&gt;
 grant read on raspi to uread&lt;br /&gt;
 grant write on raspi to uwrite&lt;br /&gt;
&lt;br /&gt;
===Backup and Restore===&lt;br /&gt;
 influxd backup  -portable mypath&lt;br /&gt;
 influxd restore -portable mypath&lt;br /&gt;
&lt;br /&gt;
===Renaming a Database via creating a copy of its tables===&lt;br /&gt;
Attention: this will convert tags to fields is group by * missing&lt;br /&gt;
 CREATE DATABASE mydb2&lt;br /&gt;
 SELECT * INTO mydb2.autogen.table1 from mydb1.autogen.table1 group by *&lt;br /&gt;
 DROP DATABASE mydb1&lt;br /&gt;
&lt;br /&gt;
==General commands==&lt;br /&gt;
&lt;br /&gt;
===SHOW commands===&lt;br /&gt;
 SHOW DATABASES&lt;br /&gt;
 SHOW MEASUREMENTS&lt;br /&gt;
 SHOW RETENTION POLICIES&lt;br /&gt;
 # display the field types of &amp;quot;table&amp;quot; myMeasurement&lt;br /&gt;
 SHOW FIELD KEYS FROM myMeasurement&lt;br /&gt;
 # show tags&lt;br /&gt;
 SHOW TAG   KEYS FROM myMeasurement&lt;br /&gt;
 # for example to be used in Grafana variables of type Query&lt;br /&gt;
 SHOW TAG VALUES FROM myMeasurement WITH KEY =~ //&lt;br /&gt;
 # CQ&lt;br /&gt;
 SHOW CONTINUOUS QUERIES&lt;br /&gt;
&lt;br /&gt;
===SELECT statement===&lt;br /&gt;
 precision rfc3339&lt;br /&gt;
 # for human readable timeformat&lt;br /&gt;
 USE myDB&lt;br /&gt;
 SELECT * FROM myMeasurement WHERE time &amp;gt; now() - 3d&lt;br /&gt;
 SELECT * FROM myMeasurement WHERE myField &amp;gt; 12.2&lt;br /&gt;
 &lt;br /&gt;
 # cast field into different type into new measurement&lt;br /&gt;
 SELECT watt::float INTO myMeasurement2 FROM myMeasurement1&lt;br /&gt;
 &lt;br /&gt;
 # copy into new measurement: &lt;br /&gt;
 SELECT watt::float, kWh_total_in::float, kWh_total_out::float INTO tasmota_MT681 FROM tmp&lt;br /&gt;
 # use GROUP BY * to keep field types AND tags&lt;br /&gt;
 SELECT watt_now, watt_last, kWh_total INTO tmp FROM Shelly GROUP BY *&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Last value per day&lt;br /&gt;
 SELECT last(&amp;quot;kWh_total_in&amp;quot;) as kWh_total_in, last(&amp;quot;kWh_total_out&amp;quot;) as kWh_total_out&lt;br /&gt;
 FROM &amp;quot;tasmota_MT681&amp;quot;&lt;br /&gt;
 WHERE 1=1&lt;br /&gt;
 -- AND time &amp;gt; now() -2d&lt;br /&gt;
 AND time &amp;lt; &#039;2024-07-13T00:00:00+02:00&#039;&lt;br /&gt;
 GROUP BY time(1d) fill(previous)&lt;br /&gt;
&lt;br /&gt;
===DELETE alternative===&lt;br /&gt;
DELETE is not supported in Influx V1&lt;br /&gt;
&lt;br /&gt;
alternative:&lt;br /&gt;
 # filter data into new tmp measurement and cast to float instead&lt;br /&gt;
 # use GROUP BY * to keep field types and tags&lt;br /&gt;
 SELECT watt::float, kWh_total_in, kWh_total_out INTO tmp FROM tasmota_MT681 WHERE kWh_total_in &amp;gt;0 GROUP BY *&lt;br /&gt;
 # drop original measurement&lt;br /&gt;
 DROP measurement tasmota_MT681&lt;br /&gt;
 # restore from tmp&lt;br /&gt;
 SELECT watt, kWh_total_in, kWh_total_out INTO tasmota_MT681 FROM tmp GROUP BY *&lt;br /&gt;
 # cleanup&lt;br /&gt;
 DROP MEASUREMENT tmp&lt;br /&gt;
&lt;br /&gt;
==Retention Policies==&lt;br /&gt;
 SHOW RETENTION POLICIES&lt;br /&gt;
 # keep data for 10 years instead of 6 days (default)&lt;br /&gt;
 CREATE RETENTION POLICY &amp;quot;years10&amp;quot; ON raspi DURATION 520w REPLICATION 1 DEFAULT&lt;br /&gt;
 DROP   RETENTION POLICY &amp;quot;years10&amp;quot; ON raspi&lt;br /&gt;
 &lt;br /&gt;
 # infinite &lt;br /&gt;
 CREATE RETENTION POLICY &amp;quot;inf&amp;quot; ON &amp;quot;raspi&amp;quot; DURATION INF REPLICATION 1&lt;br /&gt;
 &lt;br /&gt;
 # if switching the default RETENTION POLICY old data will require a specific select&lt;br /&gt;
 # altering the default RP:&lt;br /&gt;
 ALTER RETENTION POLICY autogen ON raspi DURATION 90d SHARD DURATION 1d REPLICATION 1 DEFAULT&lt;br /&gt;
 ALTER RETENTION POLICY autogen ON collectd DURATION 7d SHARD DURATION 1d REPLICATION 1 DEFAULT&lt;br /&gt;
&lt;br /&gt;
==Continuous Query / Auto aggregation==&lt;br /&gt;
 SHOW CONTINUOUS QUERIES&lt;br /&gt;
 &lt;br /&gt;
 CREATE CONTINUOUS QUERY &amp;quot;cq_mt681_1d&amp;quot; ON &amp;quot;raspi&amp;quot; BEGIN \&lt;br /&gt;
  INTO &amp;quot;inf&amp;quot;.&amp;quot;tasmota_MT681_day_end&amp;quot; \&lt;br /&gt;
  SELECT last(&amp;quot;kWh_total_in&amp;quot;) as kWh_total_in, last(&amp;quot;kWh_total_out&amp;quot;) as kWh_total_out \&lt;br /&gt;
  FROM &amp;quot;tasmota_MT681&amp;quot; \&lt;br /&gt;
 GROUP BY time(1d) fill(previous) \&lt;br /&gt;
 END&lt;br /&gt;
 &lt;br /&gt;
 DROP CONTINUOUS QUERY &amp;quot;cq_mt681_1d&amp;quot; ON &amp;quot;raspi&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==Connection via CURL==&lt;br /&gt;
 curl -G http://raspi3:8086/query -u uwrite:password2 --data-urlencode &amp;quot;q=SHOW DATABASES&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===Insert via REST API and CURL===&lt;br /&gt;
 # precision=s (second) is important for performance, if higher accuracy is needed use ms, not ns which is the default!)&lt;br /&gt;
 VALUE=&amp;quot;123.123&amp;quot;&lt;br /&gt;
 curl -i \&lt;br /&gt;
        -u uwrite:password2 \&lt;br /&gt;
        -XPOST &amp;quot;http://localhost:8086/write?db=raspi&amp;amp;precision=s&amp;quot; \&lt;br /&gt;
        --data-binary &amp;quot;myTable,myTag=SourceA myValue=$VALUE&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==SELECT, INSERT and UPDATE via Python==&lt;br /&gt;
===SELECT into Pandas DataFrame===&lt;br /&gt;
 def read_data() -&amp;gt; pd.DataFrame:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Read data from Influx DB into DataFrame.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     client = connect2_df(credentials_read)&lt;br /&gt;
     query = &amp;quot;SELECT * FROM {retention}.{measurement} WHERE ShellyNo = $ShellyNo&amp;quot;&lt;br /&gt;
     result = client.query(&lt;br /&gt;
         query.format(retention=retention, measurement=measurement),&lt;br /&gt;
         bind_params={&amp;quot;ShellyNo&amp;quot;: shelly_no},&lt;br /&gt;
     )&lt;br /&gt;
     df: pd.DataFrame = result[MEASUREMENT]  # type: ignore&lt;br /&gt;
     df.index.name = &amp;quot;time&amp;quot;&lt;br /&gt;
     client.close()&lt;br /&gt;
     return df&lt;br /&gt;
&lt;br /&gt;
===INSERT / UPDATE via Pandas DataFrame===&lt;br /&gt;
 def insert(df: pd.DataFrame) -&amp;gt; None:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Insert of df into Influx DB.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     client = connect2_df(credentials_write)&lt;br /&gt;
     if len(df) &amp;gt; 0:&lt;br /&gt;
         client.write_points(&lt;br /&gt;
             df,&lt;br /&gt;
             MEASUREMENT,&lt;br /&gt;
             tag_columns=[&amp;quot;room&amp;quot;],&lt;br /&gt;
             protocol=&amp;quot;line&amp;quot;,&lt;br /&gt;
             batch_size=1000,&lt;br /&gt;
             retention_policy=RETENTION,&lt;br /&gt;
             time_precision=&amp;quot;s&amp;quot;, # ns .. s&lt;br /&gt;
         )&lt;br /&gt;
     client.close()&lt;br /&gt;
&lt;br /&gt;
===INSERT JSON data via InfluxDBClient===&lt;br /&gt;
see [https://www.influxdata.com/blog/getting-started-python-influxdb/]&lt;br /&gt;
 from influxdb import InfluxDBClient&lt;br /&gt;
 client = InfluxDBClient(host=&#039;192.168.178.31&#039;, port=8086, username=&#039;write&#039;, password=&#039;password2 &#039;)&lt;br /&gt;
 # client.create_database(&#039;raspi&#039;)&lt;br /&gt;
 print(client.get_list_database())&lt;br /&gt;
 client.switch_database(&#039;raspi&#039;)&lt;br /&gt;
 &lt;br /&gt;
 json = [&lt;br /&gt;
     {&lt;br /&gt;
         &amp;quot;measurement&amp;quot;: &amp;quot;brushEvents&amp;quot;,&lt;br /&gt;
         &amp;quot;tags&amp;quot;: {&lt;br /&gt;
             &amp;quot;user&amp;quot;: &amp;quot;Carol&amp;quot;,&lt;br /&gt;
             &amp;quot;brushId&amp;quot;: &amp;quot;6c89f539-71c6-490d-a28d-6c5d84c0ee2f&amp;quot;&lt;br /&gt;
         },&lt;br /&gt;
         &amp;quot;time&amp;quot;: &amp;quot;2018-03-28T8:01:00Z&amp;quot;,&lt;br /&gt;
         &amp;quot;fields&amp;quot;: {&lt;br /&gt;
             &amp;quot;duration&amp;quot;: 127&lt;br /&gt;
         }&lt;br /&gt;
     },&lt;br /&gt;
     {&lt;br /&gt;
         &amp;quot;measurement&amp;quot;: &amp;quot;brushEvents&amp;quot;,&lt;br /&gt;
         &amp;quot;tags&amp;quot;: {&lt;br /&gt;
             &amp;quot;user&amp;quot;: &amp;quot;Carol&amp;quot;,&lt;br /&gt;
             &amp;quot;brushId&amp;quot;: &amp;quot;6c89f539-71c6-490d-a28d-6c5d84c0ee2f&amp;quot;&lt;br /&gt;
         },&lt;br /&gt;
         &amp;quot;time&amp;quot;: &amp;quot;2018-03-29T8:04:00Z&amp;quot;,&lt;br /&gt;
         &amp;quot;fields&amp;quot;: {&lt;br /&gt;
             &amp;quot;duration&amp;quot;: 132&lt;br /&gt;
         }&lt;br /&gt;
     }&lt;br /&gt;
 ]&lt;br /&gt;
 &lt;br /&gt;
 if client.write_points(json, time_precision=&amp;quot;s&amp;quot;) != True:&lt;br /&gt;
     print (&amp;quot;ERROR: Write to InfluxDB not successful&amp;quot;)&lt;br /&gt;
 else: &lt;br /&gt;
     print (&amp;quot;data sent&amp;quot;)&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Grafana&amp;diff=5342</id>
		<title>Grafana</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Grafana&amp;diff=5342"/>
		<updated>2025-10-17T11:34:52Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Raspi]][[Category:Software]]&lt;br /&gt;
===Install Grafana on Raspberry Pi in Raspbian===&lt;br /&gt;
[https://grafana.com/docs/grafana/latest/setup-grafana/installation/debian/ Grafana installation tutorial]&lt;br /&gt;
&lt;br /&gt;
===Connect to InfluxDB===&lt;br /&gt;
[https://www.circuits.dk/install-grafana-influxdb-raspberry/ Howto combine Influx and Grafana (old)]&lt;br /&gt;
&lt;br /&gt;
===Setup===&lt;br /&gt;
====grafana.ini====&lt;br /&gt;
in /etc/grafana/grafana.ini set the admin user and password&lt;br /&gt;
 [dashboards]&lt;br /&gt;
 # Number dashboard versions to keep (per dashboard). Default: 20, Minimum: 1&lt;br /&gt;
 versions_to_keep = 5&lt;br /&gt;
&lt;br /&gt;
====service====&lt;br /&gt;
 # Start Grafana by running:&lt;br /&gt;
 sudo service grafana-server start&lt;br /&gt;
&lt;br /&gt;
Autostart at boot time&lt;br /&gt;
 sudo systemctl enable grafana-server.service&lt;br /&gt;
 sudo update-rc.d grafana-server defaults&lt;br /&gt;
&lt;br /&gt;
default port: http://raspi:3000&lt;br /&gt;
&lt;br /&gt;
===Migration from host to host===&lt;br /&gt;
 # 1. adjust grafana.ini on target to match the one from source&lt;br /&gt;
 sudo vim /etc/grafana/grafana.ini&lt;br /&gt;
 &lt;br /&gt;
 # source&lt;br /&gt;
 DIR=/var/lib/grafana&lt;br /&gt;
 sudo scp -r $DIR user@target:incoming/&lt;br /&gt;
 &lt;br /&gt;
 # target&lt;br /&gt;
 DIR=/var/lib/grafana&lt;br /&gt;
 sudo service grafana-server stop&lt;br /&gt;
 sudo chown -R grafana:grafana incoming/grafana&lt;br /&gt;
 sudo rm -rf $DIR&lt;br /&gt;
 sudo mv incoming/grafana $DIR&lt;br /&gt;
 sudo service grafana-server start&lt;br /&gt;
&lt;br /&gt;
===Queries===&lt;br /&gt;
Display Table of Influx DB data&lt;br /&gt;
field &amp;quot;active&amp;quot; contains 1 if a given &amp;quot;hostname&amp;quot; is online&lt;br /&gt;
Query&lt;br /&gt;
 WHERE time &amp;gt; now()-5m and field(active) != null&lt;br /&gt;
 SELECT field(active) distinct() alias(online)&lt;br /&gt;
 GROUP BY time($__interval), tag(hostname)&lt;br /&gt;
 FORMAT AS Table&lt;br /&gt;
 Min time interval = 5m&lt;br /&gt;
Visualization&lt;br /&gt;
 Column Style -&amp;gt; Type Hidden for columns named online&lt;br /&gt;
&lt;br /&gt;
===Convert cummulated kWh data to hourly or daily differences===&lt;br /&gt;
 SELECT spread(&amp;quot;kWh_total&amp;quot;) FROM &amp;quot;Shelly3&amp;quot; WHERE (&amp;quot;room&amp;quot;::tag =~ /^$room$/) AND $timeFilter GROUP BY time($__interval) fill(null)&lt;br /&gt;
With &amp;quot;Query Options&amp;quot; -&amp;gt; &amp;quot;Min interval&amp;quot;=1h or 1d and maybe &amp;quot;Relative time&amp;quot; = now-7d&lt;br /&gt;
&lt;br /&gt;
===Color of fill area based on threshold (e.g. red for negative values)===&lt;br /&gt;
 Gradient mode -&amp;gt; Schema&lt;br /&gt;
 Color scheme -&amp;gt; From threshold&lt;br /&gt;
 Fill opacity -&amp;gt; 25&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=SSH&amp;diff=5341</id>
		<title>SSH</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=SSH&amp;diff=5341"/>
		<updated>2025-10-17T10:52:00Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Linux]]&lt;br /&gt;
see [https://help.github.com/articles/generating-ssh-keys] [https://en.wikipedia.org/wiki/Ssh-keygen]&lt;br /&gt;
&lt;br /&gt;
===SSH password-free-login===&lt;br /&gt;
all in 1 go (exceute on ssh client machine)&lt;br /&gt;
 ssh-copy-id user@remotehost&lt;br /&gt;
&lt;br /&gt;
====On the ssh-client machine====&lt;br /&gt;
Use ssh-keygen to create private and public keys in the folder ~/.ssh/&lt;br /&gt;
 ssh-keygen -t rsa&lt;br /&gt;
or&lt;br /&gt;
 ssh-keygen -t rsa -C &amp;quot;email@host.com&amp;quot;&lt;br /&gt;
 # ( or -t dsa )&lt;br /&gt;
passphrase can be empty&lt;br /&gt;
The public key is in the file ~/.ssh/id_rsa.pub &lt;br /&gt;
&lt;br /&gt;
adding or changing passphrase&lt;br /&gt;
 ssh-keygen -p&lt;br /&gt;
&lt;br /&gt;
storing passphrase of client via ssh-agent (linux)&lt;br /&gt;
 ssh-agent bash&lt;br /&gt;
 ssh-add&lt;br /&gt;
&lt;br /&gt;
====On the ssh server machine====&lt;br /&gt;
* append your public key to ~/.ssh/authorized_keys (you may have to create this file) &lt;br /&gt;
* set that files permissions to 0640&lt;br /&gt;
* set .ssh/ permissions to 0640&lt;br /&gt;
&lt;br /&gt;
====Windows Client using Putty and PAgent====&lt;br /&gt;
* use PuTTYgen to generate a RSA key&lt;br /&gt;
** set password&lt;br /&gt;
** set comment, for example hostname of client machine&lt;br /&gt;
** store private and public keys&lt;br /&gt;
* edit generated public key&lt;br /&gt;
** remove linebreaks from key&lt;br /&gt;
** prepend ssh-rsa to key followed by a space. So it looks like ssh-rsa AAAAB3Nza...&lt;br /&gt;
* double click on private key to load it into Pageant (key manager)&lt;br /&gt;
* in Putty&lt;br /&gt;
** load your ssh connection&lt;br /&gt;
** put private key to Connection-&amp;gt;SSH-&amp;gt;Auth&lt;br /&gt;
** save connection&lt;br /&gt;
* on Server&lt;br /&gt;
** place public key without comments in ~/.ssh/authorized_keys, see above&lt;br /&gt;
&lt;br /&gt;
===Mount using ssh===&lt;br /&gt;
 apt-get install sshfs&lt;br /&gt;
now you can run&lt;br /&gt;
 /usr/bin/sshfs -o idmap=user server:/home/USER /mount/DIR&lt;br /&gt;
The option idmap=user is important to map the servers and clients user id&lt;br /&gt;
&lt;br /&gt;
(this does not work from fstab...)&lt;br /&gt;
&lt;br /&gt;
===Fix for warning: setlocale: LC_CTYPE===&lt;br /&gt;
 bash: warning: setlocale: LC_CTYPE: cannot change locale (UTF-8): No such file or directory&lt;br /&gt;
can be fixed on the SSH host by&lt;br /&gt;
 sudo vim /etc/ssh/sshd_config&lt;br /&gt;
 # remove this:&lt;br /&gt;
 AcceptEnv LANG LC_*&lt;br /&gt;
 # now restart ssh:&lt;br /&gt;
 sudo systemctl restart ssh&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Games_for_Kids&amp;diff=5296</id>
		<title>Games for Kids</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Games_for_Kids&amp;diff=5296"/>
		<updated>2025-09-21T11:50:28Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Zocken]]&lt;br /&gt;
==Links==&lt;br /&gt;
===Mobile Devices===&lt;br /&gt;
* [https://www.stardewvalley.net Stardew Valley] [https://play.google.com/store/apps/details?id=com.chucklefish.stardewvalley @Google] [https://apps.apple.com/us/app/stardew-valley/id1406710800 @Apple]&lt;br /&gt;
* [https://pocketcitygame.com Pocket City 2] [https://play.google.com/store/apps/details?id=com.codebrewgames.pocketcity2 @Google] [https://apps.apple.com/us/app/pocket-city-2/id1533709428 @Apple]&lt;br /&gt;
* Minecraft [https://play.google.com/store/apps/details?id=com.mojang.minecraftpe @Google] [https://apps.apple.com/us/app/minecraft-play-with-friends/id479516143 @Apple]&lt;br /&gt;
* Angry Birds Classic [https://apps.apple.com/us/app/reds-first-flight/id1596736236 @Apple] (removed from Google)&lt;br /&gt;
* 2 Player Games [https://play.google.com/store/apps/details?id=com.JindoBlu.TwoPlayerGamesChallenge @Google] [https://apps.apple.com/app/2-player-games-the-challenge/id1465731199 @Apple]&lt;br /&gt;
* 1 2 3 4 Player Games [https://play.google.com/store/apps/details?id=com.JindoBlu.TwoPlayerGamesChallenge @Goolge] [https://apps.apple.com/app/1-2-3-4-player-games/id1635978552 @Apple]&lt;br /&gt;
&lt;br /&gt;
===PC===&lt;br /&gt;
sorted recent to old&lt;br /&gt;
# [https://store.steampowered.com/app/1656930/Coridden/ Coridden]&lt;br /&gt;
# [https://store.steampowered.com/app/252110/Lovers_in_a_Dangerous_Spacetime/ Lovers in a Dangerous Spacetime]&lt;br /&gt;
# [https://www.stardewvalley.net Stardew Valley]&lt;br /&gt;
# Lego Games at Steam&lt;br /&gt;
## [https://store.steampowered.com/app/311770/LEGO_Pirates_of_the_Caribbean_The_Video_Game/ Pirates of the Caribbean]&lt;br /&gt;
## [https://store.steampowered.com/app/21130/LEGO_Harry_Potter_Years_14/ Harry Potter 1-4]&lt;br /&gt;
## [https://store.steampowered.com/app/204120/LEGO_Harry_Potter_Years_57/ Harry Potter 5-7]&lt;br /&gt;
## [https://store.steampowered.com/app/214510/LEGO_The_Lord_of_the_Rings/ Lord of the Rings]&lt;br /&gt;
## [https://store.steampowered.com/app/285160/LEGO_The_Hobbit/ Hobbit]&lt;br /&gt;
# [https://supertuxkart.net SuperTuxkart]&lt;br /&gt;
&lt;br /&gt;
==Suggestions 2025 in German==&lt;br /&gt;
Hardware: PC mit 4 Xbox Controllern und Steam Games Store&lt;br /&gt;
&lt;br /&gt;
Spiele: Fast ausschließlich kooperativ im SplitScreen als Familienevent. Kinder alleine auf Handy oder Tablett fast gar nicht.&lt;br /&gt;
&lt;br /&gt;
===Kooperativ im SplitScreen===&lt;br /&gt;
Sortierung: für kleine Kinder oben&lt;br /&gt;
&lt;br /&gt;
1 SuperTuxKart&amp;lt;br/&amp;gt;&lt;br /&gt;
Open Source Mario Kart Clone, viel weniger überladen als das Original&amp;lt;br/&amp;gt;&lt;br /&gt;
War unser erstes Spiel &amp;lt;br/&amp;gt;&lt;br /&gt;
https://supertuxkart.net/&lt;br /&gt;
&lt;br /&gt;
2 Stardew Valley&amp;lt;br/&amp;gt;&lt;br /&gt;
unser all time Favorit, sehr schönes Indie Spiel&amp;lt;br/&amp;gt;&lt;br /&gt;
(mit einem Tag-Nacht Rhythmus von 15 Minuten, mir das Ausschalten mit wenig Streit ermöglicht)&amp;lt;br/&amp;gt;&lt;br /&gt;
Gibt es für alle Plattformen siehe &amp;lt;br/&amp;gt;&lt;br /&gt;
https://www.stardewvalley.net/ &amp;lt;br/&amp;gt;&lt;br /&gt;
(Glaube Mehrspieler geht nicht auf Habdy / Tablet)&lt;br /&gt;
&lt;br /&gt;
3 Lego Spiele&lt;br /&gt;
Diverse Filme in Form von Lego Männchen. Fluch der Karibik, Harry Potter, Herr der Ringe.&amp;lt;br/&amp;gt;&lt;br /&gt;
Nicht: Marvel und Ninjago, die waren mir zu sehr überladen &amp;lt;br/&amp;gt;&lt;br /&gt;
Immer als 2-Spieler aufgesetzt, sprich ich muss leider zuschauen&lt;br /&gt;
&lt;br /&gt;
4 Lovers in a Dangerous Spacetime&amp;lt;br/&amp;gt;&lt;br /&gt;
Allein schon des Titels wegen&amp;lt;br/&amp;gt;&lt;br /&gt;
mit bis zu 4 Spielern ein Raumschiff steuern und wie verrückt ballern um Hasen zu befreien&amp;lt;br/&amp;gt;&lt;br /&gt;
https://store.steampowered.com/app/252110/Lovers_in_a_Dangerous_Spacetime/&lt;br /&gt;
&lt;br /&gt;
5 Coridden&amp;lt;br/&amp;gt;&lt;br /&gt;
Diablo Clone ohne Blut, dafür mit Gestaltwandlern für 4 Spieler. Erst vor einem Monat entdeckt und unser aktueller Favorit &amp;lt;br/&amp;gt;&lt;br /&gt;
https://store.steampowered.com/app/1656930/Coridden/&lt;br /&gt;
&lt;br /&gt;
===„Alleine Spiele“===&lt;br /&gt;
1 Pocket City 2&amp;lt;br/&amp;gt;&lt;br /&gt;
Simcity für Kinder, Indie Spiel, schön gemacht&amp;lt;br/&amp;gt;&lt;br /&gt;
https://pocketcitygame.com/&lt;br /&gt;
&lt;br /&gt;
2 Stardew Valley&amp;lt;br/&amp;gt;&lt;br /&gt;
(Siehe oben)&lt;br /&gt;
&lt;br /&gt;
3 Minecraft&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Docker&amp;diff=5286</id>
		<title>Docker</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Docker&amp;diff=5286"/>
		<updated>2025-07-18T09:43:06Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Build you own image */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Coding]][[Category:Linux]]&lt;br /&gt;
==Run container of public image==&lt;br /&gt;
 docker run --name my_nginx -v ./html://usr/share/nginx/html -d -p 8080:80 nginx&lt;br /&gt;
 # -v mounts/links ./html as volume&lt;br /&gt;
 # -d (detach) runs container in background&lt;br /&gt;
 # -p port mapping&lt;br /&gt;
&lt;br /&gt;
==Build you own image==&lt;br /&gt;
Dockerfile = Building instruction for Docker image&lt;br /&gt;
&lt;br /&gt;
 # syntax=docker/dockerfile:1&lt;br /&gt;
 &lt;br /&gt;
 FROM ubuntu:latest&lt;br /&gt;
 &lt;br /&gt;
 # set timezone&lt;br /&gt;
 ENV TZ=Europe/Berlin&lt;br /&gt;
 &lt;br /&gt;
 # prevent keyboard input requests in apt install&lt;br /&gt;
 ARG DEBIAN_FRONTEND=noninteractive&lt;br /&gt;
 &lt;br /&gt;
 # install core packages&lt;br /&gt;
 RUN apt-get update&lt;br /&gt;
 RUN apt-get dist-upgrade -y&lt;br /&gt;
 RUN apt-get install -y python3&lt;br /&gt;
&lt;br /&gt;
Build Docker Image from Dockerfile:&lt;br /&gt;
 docker build -t myImage .&lt;br /&gt;
&lt;br /&gt;
To delete the image&lt;br /&gt;
 docker rmi myImage&lt;br /&gt;
&lt;br /&gt;
===Create Container from Image===&lt;br /&gt;
Create container and mount current working dir to /src&lt;br /&gt;
 # Linux/MacOS:&lt;br /&gt;
 docker run --name myContainer -it --mount type=bind,src=&amp;quot;$(pwd)&amp;quot;,target=/src myImage bash&lt;br /&gt;
 # Windows:&lt;br /&gt;
 docker run --name myContainer -it --mount type=bind,src=&amp;quot;%cd%&amp;quot;,target=/src myImage bash&lt;br /&gt;
&lt;br /&gt;
Run Container&lt;br /&gt;
 docker start -ai myContainer&lt;br /&gt;
&lt;br /&gt;
To delete the container&lt;br /&gt;
 docker rm myContainer&lt;br /&gt;
&lt;br /&gt;
==Check Docker Resources==&lt;br /&gt;
 sudo -i&lt;br /&gt;
 # list of containers&lt;br /&gt;
 docker ps&lt;br /&gt;
 # top for containers&lt;br /&gt;
 docker stats&lt;br /&gt;
 # stats of single container&lt;br /&gt;
 docker stats &amp;lt;id&amp;gt;&lt;br /&gt;
 # disk space&lt;br /&gt;
 docker system df&lt;br /&gt;
 # Unused docker images&lt;br /&gt;
 docker images --filter &amp;quot;dangling=true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==Hacks==&lt;br /&gt;
===Windows Docker-Desktop: move image location===&lt;br /&gt;
from [https://www.kindacode.com/article/docker-desktop-change-images-containers-directory/?utm_content=cmp-true#The_Steps_For_Windows_Users_with_WSL_2_Backend]&lt;br /&gt;
&lt;br /&gt;
 first Exit Docker Desktop, than:&lt;br /&gt;
 wsl --shutdown&lt;br /&gt;
 wsl --export docker-desktop-data C:\tmp\docker-data.tar&lt;br /&gt;
 wsl --unregister docker-desktop-data&lt;br /&gt;
 wsl --import docker-desktop-data E:\docker\ C:\tmp\docker-data.tar --version 2&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Git&amp;diff=5277</id>
		<title>Git</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Git&amp;diff=5277"/>
		<updated>2025-07-07T02:46:55Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Modifiying History */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Coding]]&lt;br /&gt;
&lt;br /&gt;
==Git Basics==&lt;br /&gt;
===Basics===&lt;br /&gt;
====clone a repository====&lt;br /&gt;
 git clone git@github.com:entorb/rememberthemilk.git&lt;br /&gt;
&lt;br /&gt;
====perform a commit====&lt;br /&gt;
 git add .&lt;br /&gt;
 git commit -m &amp;quot;my commit message&amp;quot;&lt;br /&gt;
 git push&lt;br /&gt;
&lt;br /&gt;
===Git Settings===&lt;br /&gt;
&lt;br /&gt;
==== !!! End of Line !!! ====&lt;br /&gt;
Force Git for Windows to use Linux end of line: LF (\n) instead of CRLF (\r\n)&lt;br /&gt;
 git config --global core.autocrlf false&lt;br /&gt;
 git config --global core.eol lf&lt;br /&gt;
&lt;br /&gt;
To use git to fix bad line endings, add a .gitattributes file to the repo with this contents:&lt;br /&gt;
 * text=auto eol=lf&lt;br /&gt;
&lt;br /&gt;
====colorful output====&lt;br /&gt;
 git config color.ui true&lt;br /&gt;
&lt;br /&gt;
====log: one line per commit====&lt;br /&gt;
 git config format.pretty oneline&lt;br /&gt;
&lt;br /&gt;
===Branching===&lt;br /&gt;
 # create new branch&lt;br /&gt;
 git checkout -b history-change&lt;br /&gt;
 # del branch&lt;br /&gt;
 git checkout main&lt;br /&gt;
 git branch -d history-change&lt;br /&gt;
&lt;br /&gt;
====Rename branch master to main====&lt;br /&gt;
1. at github.com in the settings of the repo, rename the default branch name, for example at https://github.com/entorb/tools/settings/branches&lt;br /&gt;
&lt;br /&gt;
2. update in local repo&lt;br /&gt;
 git branch -m master main&lt;br /&gt;
 git fetch origin&lt;br /&gt;
 git branch -u origin/main main&lt;br /&gt;
 git remote set-head origin -a&lt;br /&gt;
&lt;br /&gt;
3. set default branch name in local git&lt;br /&gt;
 git config --global init.defaultBranch main&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==History==&lt;br /&gt;
===Reports===&lt;br /&gt;
====Git Log====&lt;br /&gt;
 git log&lt;br /&gt;
 git log --pretty=oneline --abbrev-commit&lt;br /&gt;
 &lt;br /&gt;
 # list commits including the change summary, e.g. 1 file changed, 16 insertions(+), 2 deletions(-)&lt;br /&gt;
 git log --date=short --pretty=format:&amp;quot;%cd %h %s&amp;quot; --shortstat&lt;br /&gt;
&lt;br /&gt;
====Git Rev-list====&lt;br /&gt;
 # Get the total commits by week&lt;br /&gt;
 git rev-list --count HEAD --since=4.week&lt;br /&gt;
 git rev-list --count main --since=&amp;quot;Dec 1 2021&amp;quot; --before=&amp;quot;Jan 3 2022&amp;quot;&lt;br /&gt;
====Report of merges====&lt;br /&gt;
 var1&lt;br /&gt;
 git --no-pager log --merges --pretty=format:&#039;MyRepoName	%cs    %ae    %s&#039; | grep -v &#039;dependabot&#039; &amp;gt;&amp;gt; ../git-merge-report.csv&lt;br /&gt;
 var2&lt;br /&gt;
 git --no-pager log --grep=&#039;#&#039; --pretty=format:&#039;MyRepoName	%cs	%ae	%s&#039; --after 2022-01-01 &amp;gt;&amp;gt; ../git-merge-report.csv&lt;br /&gt;
 # note: in one repo the &#039;--merges&#039; flag did not find all merge PRs, so using --grep=&#039;#&#039; instead&lt;br /&gt;
&lt;br /&gt;
====Checking History====&lt;br /&gt;
Display all commits that modified a certain file&lt;br /&gt;
 git log --no-decorate --pretty=format:&amp;quot;%h%x09%ad%x09%an%x09%s&amp;quot; --date=iso -- data/de-districts/de-district_timeseries-02000.tsv&lt;br /&gt;
 # %x09 : tab&lt;br /&gt;
&lt;br /&gt;
===Modifiying History===&lt;br /&gt;
&lt;br /&gt;
====Merge 2 repos====&lt;br /&gt;
see https://github.com/entorb/tools/blob/main/git-tools/git-merge-2-repos.cmd&lt;br /&gt;
&lt;br /&gt;
====delete a dir from historiy using git-filter-repo====&lt;br /&gt;
preferred as of 2022 history cleanup using [https://github.com/newren/git-filter-repo git-filter-repo]&lt;br /&gt;
 pip install git-filter-repo&lt;br /&gt;
 git clone xxx&lt;br /&gt;
 cd xxx&lt;br /&gt;
 # backup .git/config&lt;br /&gt;
 cp .git/config ../config&lt;br /&gt;
 git-filter-repo --prune-empty always --invert-paths --path draft --path old --path maps/out&lt;br /&gt;
 # restore .git/config&lt;br /&gt;
 cp ../config .git/config&lt;br /&gt;
 &lt;br /&gt;
 git reflog expire --expire=now --all&lt;br /&gt;
 git gc --prune=now --aggressive&lt;br /&gt;
 &lt;br /&gt;
 git push -f&lt;br /&gt;
&lt;br /&gt;
to remove certain files matching a glob from history&lt;br /&gt;
 git-filter-repo --prune-empty always --invert-paths --path-glob &amp;quot;*.creds&amp;quot; --path-glob &amp;quot;data/*/*.json&amp;quot;&lt;br /&gt;
&lt;br /&gt;
afterwards revert your local repo to the remote one&lt;br /&gt;
 git fetch origin&lt;br /&gt;
 git reset --hard origin/main&lt;br /&gt;
 git pull&lt;br /&gt;
&lt;br /&gt;
==== combine changes to README.md etc into single commit ====&lt;br /&gt;
see https://github.com/entorb/tools/blob/main/git-tools/git-hist-combine.sh&lt;br /&gt;
&lt;br /&gt;
====delete a dir from historiy using bfg====&lt;br /&gt;
note: better use git-filter-rep (see above)&lt;br /&gt;
&lt;br /&gt;
using [https://rtyley.github.io/bfg-repo-cleaner/ bfg]&lt;br /&gt;
 https://github.com/entorb/tools/blob/main/git-tools/git-bfg-cleanup-delete-history-of-dir.cmd&lt;br /&gt;
 git reflog expire --expire=now --all&lt;br /&gt;
 git gc --prune=now --aggressive&lt;br /&gt;
&lt;br /&gt;
====squash/merge old commits====&lt;br /&gt;
 git rebase -i --root&lt;br /&gt;
 # this opens a list of all commits in you editor&lt;br /&gt;
 # replace pick by squash for the ones to&lt;br /&gt;
&lt;br /&gt;
====delete complete commits history====&lt;br /&gt;
First Method from [https://gist.github.com/heiswayi/350e2afda8cece810c0f6116dadbe651]&lt;br /&gt;
 # Check out to a temporary branch:&lt;br /&gt;
 git checkout --orphan TEMP_BRANCH&lt;br /&gt;
 # Add all the files:&lt;br /&gt;
 git add -A&lt;br /&gt;
 # Commit the changes:&lt;br /&gt;
 git commit -am &amp;quot;Initial commit&amp;quot;&lt;br /&gt;
 # Delete the old branch:&lt;br /&gt;
 git branch -D main&lt;br /&gt;
 # Rename the temporary branch to main:&lt;br /&gt;
 git branch -m main&lt;br /&gt;
 # Finally, force update to our repository:&lt;br /&gt;
 git push -f origin main&lt;br /&gt;
&lt;br /&gt;
Second Method via &amp;quot;deleting .git folder&amp;quot; also from [https://gist.github.com/heiswayi/350e2afda8cece810c0f6116dadbe651]&lt;br /&gt;
 # Clone the project, e.g. `myproject` is my project repository:&lt;br /&gt;
 git clone https://github/heiswayi/myproject.git&lt;br /&gt;
 # Since all of the commits history are in the `.git` folder, we have to remove it:&lt;br /&gt;
 cd myproject&lt;br /&gt;
 # And delete the `.git` folder:&lt;br /&gt;
 rm -rf .git&lt;br /&gt;
 # Now, re-initialize the repository:&lt;br /&gt;
 git init&lt;br /&gt;
 git remote add origin https://github.com/heiswayi/myproject.git&lt;br /&gt;
 git remote -v&lt;br /&gt;
 # Add all the files and commit the changes:&lt;br /&gt;
 git add --all&lt;br /&gt;
 git commit -am &amp;quot;Initial commit&amp;quot;&lt;br /&gt;
 # Force push update to the main branch of our project repository:&lt;br /&gt;
 git push -f origin main&lt;br /&gt;
&lt;br /&gt;
Alternative method:&amp;lt;br&amp;gt;&lt;br /&gt;
https://www.willandskill.se/en/deleting-your-git-commit-history-without-removing-repo-on-github-bitbucket/&lt;br /&gt;
&lt;br /&gt;
==Hacks==&lt;br /&gt;
=== Merge GitHub Dependabot PRs ===&lt;br /&gt;
 # switch to main branche and pull updates&lt;br /&gt;
 current_branch=$(git branch --show-current)&lt;br /&gt;
 if [ &amp;quot;$current_branch&amp;quot; != &amp;quot;main&amp;quot; ]; then&lt;br /&gt;
 	echo &amp;quot;branch was: $current_branch&amp;quot;&lt;br /&gt;
 	git checkout main&lt;br /&gt;
 fi&lt;br /&gt;
 git pull&lt;br /&gt;
 &lt;br /&gt;
 # update origin&lt;br /&gt;
 git fetch origin&lt;br /&gt;
 &lt;br /&gt;
 # list all branches that start with &amp;quot;dependabot/&amp;quot; but not &amp;quot;springframework&amp;quot; and save to c:/tmp/dependabot-branches.txt&lt;br /&gt;
 git branch -r | grep /dependabot/ | grep -v springframework &amp;gt;c:/tmp/dependabot-branches.txt&lt;br /&gt;
 # create empty file&lt;br /&gt;
 echo &amp;quot;&amp;quot; &amp;gt;c:/tmp/dependabot-changes.txt&lt;br /&gt;
 &lt;br /&gt;
 while read -r branch; do&lt;br /&gt;
 	# remove &amp;quot;origin/&amp;quot; from the branch name&lt;br /&gt;
 	branch_name=&amp;quot;${branch#origin/}&amp;quot;&lt;br /&gt;
 	echo &amp;quot;# $branch_name&amp;quot;&lt;br /&gt;
 	echo &amp;quot;# $branch_name&amp;quot; &amp;gt;&amp;gt;c:/tmp/dependabot-changes.txt&lt;br /&gt;
 	# switch to branch and pull latest version&lt;br /&gt;
 	git checkout &amp;quot;$branch_name&amp;quot;&lt;br /&gt;
 	git pull&lt;br /&gt;
 	# show changes to file pom.xml only&lt;br /&gt;
 	git diff main --unified=0 pom.xml &amp;gt;&amp;gt;c:/tmp/dependabot-changes.txt&lt;br /&gt;
 	echo &amp;quot;&amp;quot;&lt;br /&gt;
 	echo &amp;quot;&amp;quot; &amp;gt;&amp;gt;c:/tmp/dependabot-changes.txt&lt;br /&gt;
 done &amp;lt;c:/tmp/dependabot-branches.txt&lt;br /&gt;
 &lt;br /&gt;
 # back to main branch&lt;br /&gt;
 git checkout main&lt;br /&gt;
 &lt;br /&gt;
 # filter on changes only&lt;br /&gt;
 grep -e &#039;^[#\+\-] &#039; c:/tmp/dependabot-changes.txt &amp;gt;c:/tmp/dependabot-changes-filtered.txt&lt;br /&gt;
&lt;br /&gt;
===Cleanup===&lt;br /&gt;
====Remove branch not (any more) on main====&lt;br /&gt;
prunes tracking branches not on the remote.&lt;br /&gt;
from https://stackoverflow.com/posts/28464339/timeline&lt;br /&gt;
 git remote prune origin &lt;br /&gt;
list branches that have been merged into the current branch.&lt;br /&gt;
 git branch --merged&lt;br /&gt;
&lt;br /&gt;
===Reverting===&lt;br /&gt;
&lt;br /&gt;
====reset to a commit====&lt;br /&gt;
 get hash of commit&lt;br /&gt;
 git log --oneline&lt;br /&gt;
 git reset --hard HEAD~1&lt;br /&gt;
 # git push -f&lt;br /&gt;
&lt;br /&gt;
====revert a commit====&lt;br /&gt;
 get hash of commit&lt;br /&gt;
 git log --oneline&lt;br /&gt;
 git revert &amp;lt;commit hash&amp;gt;&lt;br /&gt;
 # git push -f&lt;br /&gt;
&lt;br /&gt;
====Reset branch to remote branch====&lt;br /&gt;
from [https://stackoverflow.com/posts/1628334/timeline] Setting your branch to exactly match the remote branch can be done in two steps:&lt;br /&gt;
 git fetch origin&lt;br /&gt;
 git reset --hard origin/main&lt;br /&gt;
 # git push -f&lt;br /&gt;
&lt;br /&gt;
====Fixing/Overwriting authors/emails====&lt;br /&gt;
list authors&lt;br /&gt;
 git shortlog -sn --all&lt;br /&gt;
 git log --pretty --author=&amp;quot;my@mail.com&amp;quot;&lt;br /&gt;
perform the history rewrite using global git config &lt;br /&gt;
(from https://stackoverflow.com/questions/750172/how-do-i-change-the-author-and-committer-name-email-for-multiple-commits/1320317#1320317) &lt;br /&gt;
 git rebase -r --root --exec &amp;quot;git commit --amend --no-edit --reset-author&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===Squashing===&lt;br /&gt;
Squash last 3 commits&lt;br /&gt;
 git reset --soft HEAD~3 &amp;amp;&amp;amp;&lt;br /&gt;
 git commit&lt;br /&gt;
&lt;br /&gt;
===revert local git to remote repository===&lt;br /&gt;
from [https://stackoverflow.com/posts/5361169/timeline]&lt;br /&gt;
 git reset --hard HEAD&lt;br /&gt;
 git clean -f -d&lt;br /&gt;
 git pull&lt;br /&gt;
&lt;br /&gt;
===GIT Commit to an existing Tag===&lt;br /&gt;
from https://gist.github.com/danielestevez/2044589&lt;br /&gt;
 1) Create a branch with the tag&lt;br /&gt;
 	git branch {tagname}-branch {tagname}&lt;br /&gt;
 	git checkout {tagname}-branch&lt;br /&gt;
 &lt;br /&gt;
 2) Include the fix manually if it&#039;s just a change .... &lt;br /&gt;
 	git add .&lt;br /&gt;
 	git ci -m &amp;quot;Fix included&amp;quot;&lt;br /&gt;
     or cherry-pick the commit, whatever is easier&lt;br /&gt;
 	git cherry-pick  {num_commit}&lt;br /&gt;
 	&lt;br /&gt;
 3) Delete and recreate the tag locally&lt;br /&gt;
 	git tag -d {tagname}&lt;br /&gt;
 	git tag {tagname}&lt;br /&gt;
 &lt;br /&gt;
 4) Delete and recreate the tag remotely&lt;br /&gt;
 	git push origin :{tagname} // deletes original remote tag&lt;br /&gt;
 	git push origin {tagname} // creates new remote tag&lt;br /&gt;
 		&lt;br /&gt;
 5)  Update local repository with the updated tag (suggestion by @wyattis)&lt;br /&gt;
 	git fetch --tags&lt;br /&gt;
 &lt;br /&gt;
 This is based on https://gist.github.com/739288 thanks to nickfloyd for it&lt;br /&gt;
&lt;br /&gt;
====Chmod====&lt;br /&gt;
set executable flag. see [https://stackoverflow.com/questions/21691202/how-to-create-file-execute-mode-permissions-in-git-on-windows]&lt;br /&gt;
 git update-index --chmod=+x file&lt;br /&gt;
&lt;br /&gt;
===Error handling===&lt;br /&gt;
Error:&lt;br /&gt;
 git pull --tags origin main&lt;br /&gt;
 [rejected] ... (would clobber existing tag)&lt;br /&gt;
Fixed by updating local tags with remote tags&lt;br /&gt;
 git fetch --tags -f&lt;br /&gt;
&lt;br /&gt;
===Tag handling===&lt;br /&gt;
Delete a tag locally&lt;br /&gt;
 git tag -d myTag&lt;br /&gt;
Delete a tag remotely&lt;br /&gt;
 git push --delete origin myTag&lt;br /&gt;
&lt;br /&gt;
===Clean up via Garbage Collector===&lt;br /&gt;
 git reflog expire --expire=now --all&lt;br /&gt;
 git gc --prune=now --aggressive&lt;br /&gt;
&lt;br /&gt;
==Scripts==&lt;br /&gt;
===Re-apply .gitignore===&lt;br /&gt;
see https://github.com/entorb/tools/blob/main/git-tools/git-gitignore-reapply.cmd&lt;br /&gt;
 REM Remove everything from the git index in order to refresh your git repository:&lt;br /&gt;
 git rm -r --cached .&lt;br /&gt;
 REM Add everything back into the repo:&lt;br /&gt;
 git add .&lt;br /&gt;
 git commit -m &amp;quot;.gitignore re-applied&amp;quot;&lt;br /&gt;
 git status&lt;br /&gt;
 pause&lt;br /&gt;
 git push&lt;br /&gt;
&lt;br /&gt;
===pull on all repos===&lt;br /&gt;
 for D in $(ls -d */); do&lt;br /&gt;
 	echo === $D ===&lt;br /&gt;
 	cd $D&lt;br /&gt;
 	# git status&lt;br /&gt;
 	current_branch=$(git branch --show-current)&lt;br /&gt;
 	if [ &amp;quot;$current_branch&amp;quot; != &amp;quot;master&amp;quot; ] &amp;amp;&amp;amp; [ &amp;quot;$current_branch&amp;quot; != &amp;quot;main&amp;quot; ]; then&lt;br /&gt;
 		echo &amp;quot;branch was: $current_branch&amp;quot;&lt;br /&gt;
 		git checkout main&lt;br /&gt;
 		if [ $(git branch --show-current) != &amp;quot;main&amp;quot; ]; then&lt;br /&gt;
 			git checkout master&lt;br /&gt;
 		fi&lt;br /&gt;
 	fi&lt;br /&gt;
 	git pull --force&lt;br /&gt;
 	cd ..&lt;br /&gt;
 done &lt;br /&gt;
 read -p &amp;quot;Enter to close&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===revert on all repos===&lt;br /&gt;
 for D in $(ls -d */); do&lt;br /&gt;
 	echo === $D ===&lt;br /&gt;
 	cd $D&lt;br /&gt;
 	current_branch=$(git branch --show-current)&lt;br /&gt;
 	if [ &amp;quot;$current_branch&amp;quot; != &amp;quot;main&amp;quot; ]; then&lt;br /&gt;
 		echo &amp;quot;branch was: $current_branch&amp;quot;&lt;br /&gt;
 		git checkout main&lt;br /&gt;
 	fi&lt;br /&gt;
 	git fetch origin&lt;br /&gt;
 	git reset --hard origin/main&lt;br /&gt;
 	git pull&lt;br /&gt;
 	cd ..&lt;br /&gt;
 done &lt;br /&gt;
 read -p &amp;quot;Enter to close&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==GitHub.com Tipps==&lt;br /&gt;
===create PR for another fork===&lt;br /&gt;
* on github.com propose an edit for 1 file&lt;br /&gt;
* this creates a branch in our repo based on the fork, called patch-1 or similar&lt;br /&gt;
* rename the branch at `github.com/&amp;lt;your name&amp;gt;/&amp;lt;your repo&amp;gt;/branches/yours`&lt;br /&gt;
* now you can locally pull from your main to fetch the new fork and locally add more modifications&lt;br /&gt;
* I suggest keeping &amp;quot;unmodified&amp;quot; base-branches of other forks, so easily create new PRs and branches based on them&lt;br /&gt;
* to update the fork from he fork&#039;s main, use github.com&lt;br /&gt;
&lt;br /&gt;
===Copy commit from parent/upstream repo to forked repo===&lt;br /&gt;
ensure that the parent repo is listed as upstream:&lt;br /&gt;
 git remote -v&lt;br /&gt;
 git fetch upstream&lt;br /&gt;
 # Create new branch&lt;br /&gt;
 git checkout -b copy-commit-branch&lt;br /&gt;
 git cherry-pick &amp;lt;commit-hash&amp;gt;&lt;br /&gt;
 # Resolve conflicts (if any):&lt;br /&gt;
 git cherry-pick --continue&lt;br /&gt;
 git push origin copy-commit-branch&lt;br /&gt;
 # Finally create a pull request&lt;br /&gt;
&lt;br /&gt;
Short version&lt;br /&gt;
 git fetch upstream&lt;br /&gt;
 git cherry-pick &amp;lt;commit-hash&amp;gt;&lt;br /&gt;
 git push&lt;br /&gt;
&lt;br /&gt;
==Notes of 2020==&lt;br /&gt;
very nice tutorial: [https://rogerdudler.github.io/git-guide/index.de.html git - Der einfache Einstieg]&lt;br /&gt;
&lt;br /&gt;
==Old notes==&lt;br /&gt;
[http://www.ralfebert.de/blog/tools/visual_git_tutorial_1/]&lt;br /&gt;
[http://www.kernel.org/pub/software/scm/git/docs/gittutorial.html]&lt;br /&gt;
&lt;br /&gt;
 git init # create an empty git repository in the current folder&lt;br /&gt;
 # set some settings&lt;br /&gt;
 git config --global user.name &amp;quot;Torben Menke&amp;quot;&lt;br /&gt;
 git config --global user.email &amp;quot;torben.menke@XXX.de&amp;quot;&lt;br /&gt;
 git config --global color.ui auto&lt;br /&gt;
 git config -l # shows configuration&lt;br /&gt;
 &lt;br /&gt;
 git add somefile.txt # one file&lt;br /&gt;
 git add somefolder # one folder&lt;br /&gt;
 git add . # all&lt;br /&gt;
 &lt;br /&gt;
 git commit &lt;br /&gt;
 git commit -a -m &amp;quot;message&amp;quot; # commit all, message=&amp;quot;message&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 git pull ssh://tmenke@[IP]/[PATH] main&lt;br /&gt;
 # edit&lt;br /&gt;
 # commit&lt;br /&gt;
 git push ssh://tmenke@[IP]/[PATH] main&lt;br /&gt;
 &lt;br /&gt;
 git log&lt;br /&gt;
 git log --pretty=oneline --abbrev-commit&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Privacy&amp;diff=5275</id>
		<title>Privacy</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Privacy&amp;diff=5275"/>
		<updated>2025-07-06T05:48:08Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Privacy]]&lt;br /&gt;
===Mobilfunkanbieter: Widerspruch gegen Erhebung von Bewegungsdaten===&lt;br /&gt;
https://www.kuketz-blog.de/empfehlungsecke/#widerspruch-bewegungsdaten&lt;br /&gt;
&lt;br /&gt;
===Advertisement on Smartphone===&lt;br /&gt;
In order to get rid of personalized advertisement on your phone, *periodically* reset the phones ad id via&amp;lt;br&amp;gt;&lt;br /&gt;
Android: Settings -&amp;gt; Accounts &amp;amp; Sync -&amp;gt; Google -&amp;gt; Ads -&amp;gt; Reset advertising ID&amp;lt;br&amp;gt;&lt;br /&gt;
iPhone/iPad: Settings -&amp;gt; Privacy -&amp;gt; Advertising -&amp;gt; Reset advertising Identifier&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Aktualisierung 2018: Post verkaufte vor Bundestagswahl Daten von 34 Mio. Haushalten an CDU und FDP===&lt;br /&gt;
Nachdem die Post in bester Facebook-Manier vor der letzten Bundestagswahl sehr lukrativ aufbereitete aber anonymisierte Daten von 34 Mio. Haushalten an CDU und FDP verkloppt hat, habe ich mich mal schlau gemacht, wie man dem widersprechen kann. Ich versuchte es zunächst per [[Privacy#Widerspruchs_E-Mail_Vorlage|E-Mail]] und habe auch prompt eine automatische Antwort bekommen, die so klingt, als wenn ich nicht der erste bin der an die Adresse schreibt. &lt;br /&gt;
&lt;br /&gt;
====Hintergrundinfos====&lt;br /&gt;
* [http://www.spiegel.de/politik/deutschland/deutsche-post-verkauft-daten-an-parteien-illegal-ist-das-nicht-a-1201057.html Spiegel Online]&lt;br /&gt;
* [https://www.zdf.de/comedy/heute-show/what-the-fakt-facebook-daten-skandal-deutsche-post-polizeigesetz-100.html Die Heute Show fasst es so zusammen:]&lt;br /&gt;
&amp;lt;blockquote&amp;gt;&lt;br /&gt;
Auch die [https://www.bild.de/bild-plus/geld/wirtschaft/deutsche-post/so-verhoekert-die-post-kundendaten-an-cdu-und-fdp-55260090.bild.html Deutsche Post verhökert Kundendaten], fand diese Woche die Bild am Sonntag heraus. Im Bundestagswahlkampf 2017 zahlten CDU und FDP für straßengenaue Analysen einer Post-Tochter: Die CDU baute darauf ihren Haustür-Wahlkampf auf, die FDP verschickte so Wahlwerbung an bestimmte Zielgruppen. Die Post-Tochter verfügt dabei über riesige Datenmengen. Die BAMS zitiert aus einem vertraulichen Papier der Post: „Für ca. 20 Mio. Häuser mit rund 34 Mio. Haushalten in Deutschland stehen mehr als 1 Milliarde Einzelinformationen zur Verfügung.“&lt;br /&gt;
&lt;br /&gt;
Wie kommt das alles? Jeder, der in Deutschland eine Adresse hat, ist automatisch in den Post-Datenbanken. Um die Nutzung dieser Daten zu verhindern, müssen Verbraucher schriftlich widersprechen. Die Verbraucher können von der Deutschen Post Direkt GmbH auch Auskunft darüber fordern, welche Daten das Unternehmen über sie gesammelt hat und woher diese stammen. Dafür stellt die Verbraucherzentrale Schleswig-Holstein ein [https://www.verbraucherzentrale.sh/sites/default/files/2018-04/Musterschreiben%20VZSH%20Auskunft%20Datenspeicherung.pdf Musterschreiben] zur Verfügung. Auch die Datenschutzbehörde in NRW empfiehlt allen Verbrauchern, sich bei Post Direkt in Troisdorf (z.B. an datenschutz@postdirekt.de) zu erkundigen, was über sie gespeichert wurde.&lt;br /&gt;
&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Widerspruchs E-Mail Vorlage====&lt;br /&gt;
Ich habe diese [https://www.datenschutz-berlin.de/pdf/adressenhandel/BlnBDI_Deutsche_Post_Direkt_GmbH_Widerspruch.pdf Vorlage] verwendet, angepasst und per [mailto:Datenschutz@postdirekt.de E-Mail] verschickt. Alternativ kann man auch diese [https://www.verbraucherzentrale.sh/sites/default/files/2018-04/Musterschreiben%20VZSH%20Auskunft%20Datenspeicherung.pdf Vorlage aus SH] verwenden oder irgendeine andere.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Sehr geehrte Damen und Herren,&lt;br /&gt;
&lt;br /&gt;
hiermit widerspreche ich der Verwendung meiner personenbezogenen Daten durch die Deutsche Post Direkt GmbH für Zwecke der Werbung sowie der Markt- und Meinungsforschung gem. § 28 Abs. 4 Bundesdatenschutzgesetz (BDSG) bzw. Art. 21 Abs. 2 EU-Datenschutz-Grundverordnung (DSGVO). Zudem bitte ich Sie, meine Anschrift in Ihre hausinterne Sperrliste aufzunehmen, um eine zukünftige Nutzung oder Übermittlung für Zwecke der Werbung und der Markt- und Meinungsforschung zu vermeiden.&lt;br /&gt;
&lt;br /&gt;
Darüber hinaus fordere ich Sie nach § 34 BDSG bzw. Art. 15 DSGVO auf, mir unentgeltlich Auskunft zu erteilen über die bei Ihnen über mich gespeicherten Daten, den Zweck der Speicherung, die Herkunft der Daten, sowie alle Personen und Stellen, gegenüber denen meine Daten offengelegt wurden.&lt;br /&gt;
&lt;br /&gt;
Ihre Bestätigung meines Widerspruchs sowie die oben erbetene Auskunft erwarte ich bis spätestens &#039;&#039;Datum (habe 30 Tage Zeit gegeben)&#039;&#039; an unten genannte Adresse.&lt;br /&gt;
&lt;br /&gt;
Mit freundlichen Grüßen&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;Name&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
wohnhaft in &#039;&#039;Adresse1&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
ehemals in &#039;&#039;Adresse2&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
ehemals in &#039;&#039;Adresse3&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
===Aktualisierung 2014===&lt;br /&gt;
Updated Software is here: [[Links#Privacy]] and [[Links#Firefox_Add-ons]]&lt;br /&gt;
&lt;br /&gt;
===Aktualisierung 2014: Unerwünschte Werbung, Post Direkt, Robinson Liste===&lt;br /&gt;
Nachdem ich Anfang 2013 ungefragt Werbung von [http://www.kabeldeutschland.de Kabel Deutschland] bekam, wunderte ich mich woher diese Firma meine Adresse hatte. Im Kleingedruckten stand als Quelle [http://www.schober.de Schober Information Group]. Schober seinerseits hat einen [http://www.presseportal.de/pm/58743/2305574/deal-fuer-die-zukunft-deutsche-post-direkt-und-schober-information-group-kooperieren-bei-consumer Deal] mit der [http://portal.postdirekt.de/ Post Direkt] (einer Tochter der DE Post) geschlossen, die wiederum damit wirbt, dass sie Daten von 37 Mio Haushalten anbietet. (siehe auch diesen [http://www.golem.de/1201/89380.html Golem Artikel])&lt;br /&gt;
&lt;br /&gt;
Nachdem ich allen 3 Firmen über die jeweiligen Kontaktformulare auf den Internetseiten mitteilte, dass ich einer Nutzung und Weitergabe widerspreche und erfahren möchte was mit meinen Daten bisher passiert ist, bekam ich überraschender Weise schnell 3 nette Briefe mit Versprechen mich auf die jeweiligen in Zukunft nicht mehr anzuschreiben. Am interessantesten war der von Schober, denn man teilte mir mit, dass man meine Daten nicht von der Post, sondern vom [http://www.otto.de Otto Versand] erworben wurden und dass meine Daten an die &#039;&#039;&#039;[http://www.rundfunkbeitrag.de/ GEZ]&#039;&#039;&#039; verkauft worden sind.&lt;br /&gt;
&lt;br /&gt;
Es wurde mir ein Eintrag in die [http://de.wikipedia.org/wiki/Robinsonliste Robinsonliste] nahegelegt, welchen ich hiermit weiterempfehlen möchte: http://www.ichhabediewahl.de&lt;br /&gt;
&lt;br /&gt;
===Aktualisierung 2013===&lt;br /&gt;
Lektüre von [https://www.awxcnx.de/download/privacy-handbuch.pdf] und diese Seite [https://www.anonym-surfen.de] sind empfehlenswert.&lt;br /&gt;
&lt;br /&gt;
===Aktualisierung 2012===&lt;br /&gt;
Sehr schöne Übersicht + Lösungen aus [https://www.datenschutzzentrum.de/tracking/schutz-vor-tracking.html Schleswig Holstein]. Auch nett die aus [http://www.datenschutz-hamburg.de Hamburg].&lt;br /&gt;
&lt;br /&gt;
====Tracking Sites====&lt;br /&gt;
Tracker sind kleine Schnippsel, die den Webseitenbetreibern Zugriffsstatistiken bringen, aber den Schnippselanbietern (z.B. Google Analytics, Facebook via Like Buttons außerhalb von Facebook) verraten auf welchen verschieden Seiten ich so surfe. Aussage von [http://www.spiegel.de/extra/0,1518,748826,00.html Spiegel Online]: selbst wenn du grad nicht bei Facebook eingeloggt bist, bekommen die doch deine IP Adresse und wissen damit wo du so rumgesurft bist. Ziemlich doof finde ich... &lt;br /&gt;
&lt;br /&gt;
Hier mehr dazu [https://www.datenschutzzentrum.de/tracking/schutz-vor-tracking.html], dort wird das Browser Addon &amp;quot;Ghostery&amp;quot; empfohlen, das mir sehr gut gefällt. &lt;br /&gt;
&lt;br /&gt;
[http://www.ghostery.com/download Ghostery] ist ein großartiges Browser-Addon das Tracking Skripte blockiert.&lt;br /&gt;
&lt;br /&gt;
Einstellungen (zu erreichen über das Geist-Symbol) die man meiner Meinung nach verwenden sollte:&lt;br /&gt;
Im Firefox heißen die Optionen so: (in anderen Browsern anders)&lt;br /&gt;
* &amp;quot;GhostRank aktivieren&amp;quot; EIN (Geschmackssache)&lt;br /&gt;
* &amp;quot;Automatische Aktualisierung der Zählpixelliste aktivieren&amp;quot;&lt;br /&gt;
* Blockeroptionen -&amp;gt; Zählpixel -&amp;gt; Select all !!!&lt;br /&gt;
* Blockeroptionen -&amp;gt; Cookies hab ich nicht aktiviert&lt;br /&gt;
* Advanced (oben) -&amp;gt; Allgemeine Optionen -&amp;gt; &amp;quot;Warnmeldung einblenden&amp;quot; AUS (ist Geschmackssache)&lt;br /&gt;
* Advanced -&amp;gt; Auto Update -&amp;gt; &amp;quot;Block new elements by default&amp;quot; EIN&lt;br /&gt;
* Advanced -&amp;gt; Leistungsoptionen -&amp;gt; &amp;quot;Flash- und Silverlight-Cookies beim Verlassen löschen&amp;quot; EIN&lt;br /&gt;
&lt;br /&gt;
====Facebook Like Buttons====&lt;br /&gt;
Lösung:&lt;br /&gt;
Ich verwende 2 Browser, z.B. &lt;br /&gt;
- InternetExplorer oder Opera nur für FB&lt;br /&gt;
- Firefox für alles außer FB &lt;br /&gt;
Dazu dass Firefox Addon: &amp;lt;del&amp;gt;&#039;&#039;&#039;BlockSite&#039;&#039;&#039;&amp;lt;/del&amp;gt; [https://addons.mozilla.org/de/firefox/addon/ublock-origin/ uBlock Origin]&lt;br /&gt;
mit den Filter Einträgen:&lt;br /&gt;
facebook.*&lt;br /&gt;
fbcdn.*&lt;br /&gt;
gern auch noch was gegen die TrackingSeite&lt;br /&gt;
ivwbox.*&lt;br /&gt;
&lt;br /&gt;
====Manually====&lt;br /&gt;
Google Analytics&lt;br /&gt;
Wenn man auch gleich noch die andere Datenkrake Google etwas beschneiden will, sollte man Google Analytics (Webserverstatistic) blockieren. Dazu gibt es ein Browser Plugin von [http://tools.google.com/dlpage/gaoptout Google] ;-):&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Zu Cookies====&lt;br /&gt;
Ich halte es für eine gute Idee im Firefox einzustellen, das Cookies beim Schließen des Browsers gelöscht werden. Dazu Einstellungen -&amp;gt; Datenschutz&lt;br /&gt;
&lt;br /&gt;
===Bit Outdated Version of this Text===&lt;br /&gt;
&lt;br /&gt;
I am blocking the following pages using my browsers blocking tool (Opera: Block Content, or Firefox: Plugin BlockSite) If I want to access Facebook I now use a different browser, that is used only for this kind of pages and which has no blocking set up.&lt;br /&gt;
&lt;br /&gt;
 *.facebook.* (because of like button) &lt;br /&gt;
 *.fbcdn.* (belongs to Facebook)&lt;br /&gt;
 *.studivz.*&lt;br /&gt;
 *.ivwbox.* (statistics)&lt;br /&gt;
 *.twitter.* (retweet, etc)&lt;br /&gt;
 *.google-analytics.*&lt;br /&gt;
 *.googlesyndication.*&lt;br /&gt;
 *.googleadservices.*&lt;br /&gt;
 *.urchin.*&lt;br /&gt;
 *.adservices.google.com*&lt;br /&gt;
 *.video-stats.video.google.com*&lt;br /&gt;
 *.apps5.oingo.com* (Microsoft.Typo-Patrol)&lt;br /&gt;
 *.appliedsemantics.*&lt;br /&gt;
&lt;br /&gt;
See also [[How_to_get_rid_of_Google_Analytics]] for blocking Google Analytics completely and system wide via hosts file.&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Local_private_LLM&amp;diff=5254</id>
		<title>Local private LLM</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Local_private_LLM&amp;diff=5254"/>
		<updated>2025-06-01T14:40:41Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Installation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Motivation ==&lt;br /&gt;
* Large language models (LLMs) are very good at analyzing (large) texts&lt;br /&gt;
* There are multiple free-to-test providers in the internet&lt;br /&gt;
* Sensitive private or confidential data (like diary/journal, medical records, etc.) should &#039;&#039;&#039;NOT&#039;&#039;&#039; be shared with any external service&lt;br /&gt;
&lt;br /&gt;
== Solution ==&lt;br /&gt;
Run your own LLM&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
I use the nice open-source tool [https://ollama.com Ollama] to manage and host the LLM.&lt;br /&gt;
&lt;br /&gt;
== Running ==&lt;br /&gt;
Start in terminal/command window by passing a model (the llama3.2 was not working well for me, see below)&lt;br /&gt;
 ollama run llama3.2:latest&lt;br /&gt;
&lt;br /&gt;
A list of supported LLM models can be found [https://ollama.com/search here].&lt;br /&gt;
&lt;br /&gt;
For me the [https://ollama.com/library/qwen3 qwen3] model worked best, so far.&lt;br /&gt;
&lt;br /&gt;
Besides the model, you need to select a model size, that fits to your hardware.&lt;br /&gt;
&lt;br /&gt;
For me:&lt;br /&gt;
 MacBook Pro M1 16GB&lt;br /&gt;
 -&amp;gt; models up to 8b work, but with patience &lt;br /&gt;
 Windows PC with gaming graphics card (GPU) GeForce RTX 3060 12GB&lt;br /&gt;
 -&amp;gt; models up to 14b run very smooth&lt;br /&gt;
&lt;br /&gt;
To check if the model fits into you GPU memory, first run&lt;br /&gt;
 ollama run &amp;lt;model&amp;gt;&lt;br /&gt;
than open a second terminal window and run&lt;br /&gt;
 ollama ps&lt;br /&gt;
to see where the model is stored.&lt;br /&gt;
&lt;br /&gt;
== Usage ==&lt;br /&gt;
=== chat commands === &lt;br /&gt;
 /clear : clear current session&lt;br /&gt;
 /bye   : exit&lt;br /&gt;
&lt;br /&gt;
=== command line commands === &lt;br /&gt;
show currently running model(s) and where it is stored (RAM or GPU RAM)&lt;br /&gt;
 ollama ps&lt;br /&gt;
&lt;br /&gt;
stop model (automatically done after 5min inactivity)&lt;br /&gt;
 ollama stop llama3.2:latest&lt;br /&gt;
&lt;br /&gt;
delete model&lt;br /&gt;
 ollama rm llama3.2:latest&lt;br /&gt;
&lt;br /&gt;
===Remote access===&lt;br /&gt;
Set env variable&lt;br /&gt;
 OLLAMA_HOST=192.168.0.123:11434&lt;br /&gt;
&lt;br /&gt;
===Local storage of the models ===&lt;br /&gt;
[https://github.com/ollama/ollama/blob/main/docs/faq.md#where-are-models-stored where-are-models-stored]&lt;br /&gt;
 macOS: ~/.ollama/models &lt;br /&gt;
 Linux: /usr/share/ollama/.ollama/models&lt;br /&gt;
 Windows: C:\Users\%username%\.ollama\models&lt;br /&gt;
change via env var OLLAMA_MODELS (ollama service restart needed)&lt;br /&gt;
&lt;br /&gt;
=== Links ===&lt;br /&gt;
* [https://ollama.com/library/deepseek-r1 Deepseek Models]&lt;br /&gt;
* [https://github.com/ollama/ollama/blob/main/docs/faq.md FAQ]&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Links&amp;diff=5233</id>
		<title>Links</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Links&amp;diff=5233"/>
		<updated>2025-05-24T05:07:00Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Security */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Software]]&lt;br /&gt;
==Online==&lt;br /&gt;
&lt;br /&gt;
===Websites I built===&lt;br /&gt;
* [https://www.mtv-pattensen.de MTV Pattensen]&lt;br /&gt;
* [http://www.fliesen-wochnik.de Fliesen-Wochnik]&lt;br /&gt;
* [https://www.huenert-kramp.de/joomla/ Hünert &amp;amp; Kramp]&lt;br /&gt;
&lt;br /&gt;
===Blogs===&lt;br /&gt;
* [http://blog.fefe.de Fefe ]&lt;br /&gt;
* [http://www.bildblog.de BildBlog ]&lt;br /&gt;
&lt;br /&gt;
===Translators===&lt;br /&gt;
* [https://www.deepl.com Translator: Deepl]&lt;br /&gt;
* [https://dict.leo.org Translator: German &amp;lt;-&amp;gt; English (Leo)]&lt;br /&gt;
* [https://en.pons.com/translate Translator: German &amp;lt;-&amp;gt; English (Pons)]&lt;br /&gt;
* [https://www.dict.cc Translator: German &amp;lt;-&amp;gt; English (DictCC)]&lt;br /&gt;
* [http://deutsch-schwedisches-woerterbuch.elch.nu/lexikon.php Translator: German &amp;lt;-&amp;gt; Swedish]&lt;br /&gt;
* [http://lexin2.nada.kth.se/swe-eng.html Translator: Swedish &amp;lt;-&amp;gt; English]&lt;br /&gt;
* [https://www.deutsch-plattdeutsch.de Translator: German &amp;lt;-&amp;gt; Low German (Sprüche)]&lt;br /&gt;
&lt;br /&gt;
===Weather Forecast===&lt;br /&gt;
* [https://www.lightningmaps.org/realtime LightningMaps]&lt;br /&gt;
* [https://wetter.rtl.de Wetter.de]&lt;br /&gt;
* [https://wetterstationen.meteomedia.de/messnetz/forecast/102470.html Meteomedia ]&lt;br /&gt;
* [https://www.windguru.cz Windguru Surf Weather Forecast]&lt;br /&gt;
* [https://muchoviento.net Muchoviento Wind Forecast]&lt;br /&gt;
* [https://www.windfinder.com Windfinder]&lt;br /&gt;
&lt;br /&gt;
===Nice Webpages===&lt;br /&gt;
* [https://ihatemoney.org/ I hate Money: Split Bills / Expenses]&lt;br /&gt;
* [https://www.xe.com/ucc/ Currency Converter ]&lt;br /&gt;
* [https://filext.com File-Extension-Glossar]&lt;br /&gt;
* [https://www.tomshardware.com/charts/ Tom&#039;s Hardwareguide - Charts ]&lt;br /&gt;
* [https://www.librarything.com Library Thing ]&lt;br /&gt;
* [https://www.vorleser.net Vorleser - Free Audiobook ]&lt;br /&gt;
* [https://www.gutenberg.org Project Gutenberg - Free eBooks ]&lt;br /&gt;
* [https://www.dafont.com DaFont - Free Font Archiv]&lt;br /&gt;
* [https://www.gapminder.org Gapminder - very pretty custom statistics on the world]&lt;br /&gt;
&lt;br /&gt;
===Cool Online-Tools===&lt;br /&gt;
* [https://www.random.org/passwords/ Random Password Generator] &lt;br /&gt;
* [https://tinyurl.com TinyURL]&lt;br /&gt;
* [https://www.webyield.net/link_checker.html Link-Checker for websites]&lt;br /&gt;
* [https://www.fliptext.net FlipText ]&lt;br /&gt;
* [https://dudle.inf.tu-dresden.de dudle: alternative to Doodle]&lt;br /&gt;
* [https://www.sibiller.de/anagramme Anagramm Generator]&lt;br /&gt;
* [https://www.techpowerup.com/review/ TechPowerUp - PC Hardware Performance Reviews]&lt;br /&gt;
* [https://downr.org Download YouTube: Video or Audio]&lt;br /&gt;
&lt;br /&gt;
===Sport===&lt;br /&gt;
* [https://gotoes.org/strava/Combine_GPX_TCX_FIT_Files.php Merge GPX/FIT files for upload to Strava etc] &lt;br /&gt;
* [https://trackprofiler.com Trackprofiler edit GPX(GPS) files] (alternative to gpsies.com&lt;br /&gt;
* [https://cultureplot.com/strava-heatmap/ Heartmap for GPX files (and Strava API)] and [https://github.com/OGladfelter/strava_heatmap GitHub Repo]&lt;br /&gt;
&lt;br /&gt;
===Privacy===&lt;br /&gt;
* [https://haveibeenpwned.com have i been pwned - Check if your email address is in a data breach]&lt;br /&gt;
* [https://www.torproject.org Tor Privacy Browser Bundle, based on Firefox]&lt;br /&gt;
* [https://www.eff.org/https-everywhere Firefox Plugin https-everywhere]&lt;br /&gt;
* [https://disconnect.me Firefox Plugin disconnect.me blocks trackers]&lt;br /&gt;
* [https://www.startpage.com StartPage WebSearch] or [https://ixquick.com Ixquick] as alternative to Google&lt;br /&gt;
* [https://palava.tv secure browser based video chat]&lt;br /&gt;
* [https://anonbox.net CCC Anonbox 1 day eMail] or [http://www.mailinator.com] or [http://www.temporaryinbox.com] or [http://www.10minutemail.com]&lt;br /&gt;
* [https://frank-geht-ran.de Wegwerftelefonnummer 01631737743]&lt;br /&gt;
* [https://bugmenot.com bugmenot: Share Logins]&lt;br /&gt;
* [https://prism-break.org Prism Break]&lt;br /&gt;
&lt;br /&gt;
===Security===&lt;br /&gt;
* [https://www.grc.com/x/ne.dll?bh0bkyd2 Shields Up: Router Test]&lt;br /&gt;
* [https://www.ssllabs.com/ssltest/analyze.html SSL Labs HTTPS Check]&lt;br /&gt;
&lt;br /&gt;
==Software I like==&lt;br /&gt;
===Software I like (Windows, Linux and Mac)===&lt;br /&gt;
* [https://dbeaver.io DBeaver Multi-DB-Admin tool]&lt;br /&gt;
&lt;br /&gt;
===Software I like (Linux only)===&lt;br /&gt;
* [https://www.krusader.org Krusader]&lt;br /&gt;
* [https://kdirstat.sourceforge.net/ KDirStat - visualise disk usage]&lt;br /&gt;
* [https://www.vysor.io Vysor - Mirror Android Screen to PC (Chrome)]&lt;br /&gt;
* [https://goaccess.io GoAccess - real-time web log analyzer]&lt;br /&gt;
&lt;br /&gt;
===Software I like (Windows only)===&lt;br /&gt;
====Making Windows usable/productive====&lt;br /&gt;
* [https://www.ghisler.com Total Commander]&lt;br /&gt;
* [https://notepad-plus.sourceforge.net Notepad ++] here some [[Notepad++|tipps]]&lt;br /&gt;
* [https://www.outertech.com/en/clipboard-manager Clipboard Manager with History]&lt;br /&gt;
* [https://stefansundin.github.io/altdrag/ AltDrag - Move Windows like in Linux]&lt;br /&gt;
* [https://www.chiark.greenend.org.uk/~sgtatham/putty/download.html Putty SSH client]&lt;br /&gt;
* [https://winscp.net WinSCP sftp for windows]&lt;br /&gt;
* [https://getgreenshot.org/ Greenshot Screenshot Tool]&lt;br /&gt;
* [https://sourceforge.net/projects/unxutils/ UnixUtils] - Port of Unix/Linux tools like wget, grep,... for windows, see [[Windows_Batch_Scripting#UnixUtils|tipps]]&lt;br /&gt;
&lt;br /&gt;
====Directory Size Analysis====&lt;br /&gt;
* [https://diskanalyzer.com/download WizTree] &lt;br /&gt;
* [https://windirstat.info WinDirStat - visualise disk usage]&lt;br /&gt;
* [https://www.jgoodies.com/freeware/jdiskreport/ JDiskReport - visualise disk usage and files per folder]&lt;br /&gt;
&lt;br /&gt;
====Repairing / fixing / tweaking Windows====&lt;br /&gt;
* [https://gallery.technet.microsoft.com/scriptcenter/Reset-Windows-Update-Agent-d824badc Microsoft Repair Tool for Windows Updater]&lt;br /&gt;
* [https://www.ccleaner.com CCleaner - cleaning temp, cache, etc]&lt;br /&gt;
* [https://xp-antispy.org XP-Antispy]&lt;br /&gt;
* [https://www.chip.de/downloads/O-O-ShutUp10_82318496.html O&amp;amp;O ShutUp10] privacy tuning for Windows 10&lt;br /&gt;
&lt;br /&gt;
====Photo Tools====&lt;br /&gt;
* [https://gimp-win.sourceforge.net/ Gimp (Windows-Installer)]&lt;br /&gt;
* [https://inkscape.org Inkscape] Here some [[Inkscape| tipps]]&lt;br /&gt;
* [https://www.foto-freeware.de/resize.php Picture-Batch-Resize]&lt;br /&gt;
* [https://adionsoft.net/fastimageresize/ adionSoft Fast Image Resizer ]&lt;br /&gt;
* [https://www.mediachance.com/hdri/index.html Dynamic Photo HDR]&lt;br /&gt;
* [https://www.darkleo.com DarkShot 2: Simple Screenshot Tool ]&lt;br /&gt;
* [https://www.faststone.org FastStone Image Viewer ]&lt;br /&gt;
* [https://www.andreaplanet.com/andreamosaic/ Mosaic Generator ]&lt;br /&gt;
* [https://www.sixdots.de/mosaik/de/ Mosaic Generator #2]&lt;br /&gt;
&lt;br /&gt;
====Audio====&lt;br /&gt;
* [https://www.wma-mp3.org Free WMA to MP3 Converter]&lt;br /&gt;
* [https://www.mp3mymp3.com MP3 my MP3 records any sound from your PC, splits on silence]&lt;br /&gt;
* [https://xrecode.com xrecode II] CD Ripper and Sound Cross Encoder&lt;br /&gt;
&lt;br /&gt;
====Video====&lt;br /&gt;
* [https://www.formatoz.com FormatFactory - Video Converter / Encoder] see [[Video shrinking]]&lt;br /&gt;
* [https://www.kinovea.org Kinovea Sport Video Analyse Tool]&lt;br /&gt;
&lt;br /&gt;
====Other====&lt;br /&gt;
* [https://www.libreoffice.org LibreOffice.org] here some [[LibreOffice / OpenOffice|tipps]]&lt;br /&gt;
* [https://www.accesspdf.com/pdftk/ pdf Toolkit]&lt;br /&gt;
* [https://www.gimp.org Gimp]&lt;br /&gt;
* [https://www.vim.org gVIM]&lt;br /&gt;
* [https://hamachi.en.softonic.com/ Hamachi - Virtual Private Network (VPN)]&lt;br /&gt;
* [https://www.der-hammer.info/terminal/ HTerm]&lt;br /&gt;
* [https://www.workrave.org Workrave: forces you to take breaks]&lt;br /&gt;
* [https://sourceforge.net/projects/synergy2/ Synergy: 2 PCs, one Mouse and Keyboard]&lt;br /&gt;
* [https://free.grisoft.com/ AVG Anti-Virus (free)]&lt;br /&gt;
* [https://www.free-av.de/ AntiVir Anti-Virus (free)]&lt;br /&gt;
* [https://www.rejetto.com/hfs/ HFS - Http File Server (easy sharing of files)]&lt;br /&gt;
* [https://www.castelware.de/downloads_freeware.html CWSysinfo: Displays a lot of system-info (even your Windows serial no.)]&lt;br /&gt;
* [https://www.magicaljellybean.com/keyfinder/ Magical Jelly Bean Keyfinder: Displays Serial Numbers]&lt;br /&gt;
* [https://freepdfxp.de/ FreePDF XP for creating PDF Documents via a printer-driver]&lt;br /&gt;
* [https://www.deepburner.com DeepBurner (free CD/DVD Burning Tool)]&lt;br /&gt;
* [https://www.2brightsparks.com/freeware/freeware-hub.html SyncBack Backup-Tool]&lt;br /&gt;
* [https://www.winsplit-revolution.com WinSplit - nice tool for arranging windows ]&lt;br /&gt;
* Fill empty Space with zeors for VM compression: [https://docs.microsoft.com/de-de/sysinternals/downloads/sdelete MS sdelete] or nullfile [http://www.feyrer.de/g4u/nullfile-1.02.exe 32bit] [http://www.feyrer.de/g4u/nullfile-1.01_64bit.exe 64bit]&lt;br /&gt;
* [https://www.keyfocus.net/kfws/ KeyFocus WebServer]&lt;br /&gt;
* [https://byshynet.com/software.php?id=3 MouseMover]&lt;br /&gt;
* [https://www.chip.de/downloads/ArtMoney-Special-Edition_32716630.html ArtMoney: Game Cheating Tool]&lt;br /&gt;
&lt;br /&gt;
===Firefox Add-ons===&lt;br /&gt;
see [[Firefox#Firefox Add-Ons|Firefox -&amp;gt; Firefox Add-Ons]]&lt;br /&gt;
&lt;br /&gt;
==Games==&lt;br /&gt;
===Free Games===&lt;br /&gt;
see https://www.golem.de/news/gratisspiele-wirklich-kostenlose-games-im-all-und-in-der-hoelle-2401-181559.html&lt;br /&gt;
&lt;br /&gt;
====Adventures====&lt;br /&gt;
* [https://zak2.org Zak McKracken 2: Adventure]&lt;br /&gt;
* [https://www.chip.de/downloads/Maniac-Mansion-Deluxe_13013383.html Maniac Mansion Deluxe]&lt;br /&gt;
&lt;br /&gt;
====Racing====&lt;br /&gt;
* [https://supertuxkart.net Super Tux Kart Racing (Super Mario Clone)]&lt;br /&gt;
* [https://www.chip.de/downloads/TrackMania-Nations-Forever_31482232.html TrackMania Nations Forever]&lt;br /&gt;
* [https://phoboslab.org/wipegame/ Wipeout Browser Version]&lt;br /&gt;
&lt;br /&gt;
====Shooter====&lt;br /&gt;
* [https://www.soldat.pl Soldat: 2D Shooter]&lt;br /&gt;
* [https://tremulous.net Tremulous: 3D Shooter using Quake 3 Engine]&lt;br /&gt;
* [https://heroesinthesky.gamigo.de Heroes in the Sky]&lt;br /&gt;
====Other====&lt;br /&gt;
* [https://www.openra.net Open RA (C&amp;amp;C Red Alert Clone)]&lt;br /&gt;
* [https://www.runesofmagic.com Runes of Magic (WoW-Clone)]&lt;br /&gt;
====Browser Games====&lt;br /&gt;
* [https://www.decisionproblem.com/paperclips/index2.html Universal Paperclips]&lt;br /&gt;
* [https://adarkroom.doublespeakgames.com/ A Dark Room]&lt;br /&gt;
* [https://orteil.dashnet.org/cookieclicker/ Cockie Clicker]&lt;br /&gt;
* [https://candybox2.github.io/ Candy Box 2]&lt;br /&gt;
* [https://neal.fun/password-game/ The Password Game]&lt;br /&gt;
* [https://gandalf.lakera.ai make Gandalf reveal the secret password]&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=PostgreSQL&amp;diff=5230</id>
		<title>PostgreSQL</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=PostgreSQL&amp;diff=5230"/>
		<updated>2025-05-21T06:56:25Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Find and Delete Locks / Processes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Coding]]&lt;br /&gt;
==Connect via single line connection string==&lt;br /&gt;
 postgresql:user:password@host:5432/database&lt;br /&gt;
&lt;br /&gt;
==SELECT==&lt;br /&gt;
===Date handling===&lt;br /&gt;
====Filter on date====&lt;br /&gt;
 SELECT * FROM table WHERE created &amp;gt; &#039;2020-06-22 00:00:00+02&#039;;&lt;br /&gt;
 SELECT * FROM table WHERE created &amp;gt; current_timestamp - &#039;3 hours&#039;::interval;&lt;br /&gt;
 SELECT * FROM table WHERE created &amp;gt; current_date - 91;  -- days&lt;br /&gt;
 SELECT * FROM table WHERE created &amp;gt; now() at time zone &#039;utc&#039; - interval &#039;10 minute&#039;;&lt;br /&gt;
 # day of week filter&lt;br /&gt;
 SELECT * FROM table WHERE EXTRACT(DOW FROM created) = 0;&lt;br /&gt;
&lt;br /&gt;
====date_trunc: convert date====&lt;br /&gt;
 SELECT date_trunc(&#039;hour&#039;, t.created) as hour FROM table t;&lt;br /&gt;
 SELECT date_trunc(&#039;day&#039;, t.created)::date AS created_date FROM  table t;&lt;br /&gt;
 SELECT date_trunc(&#039;week&#039;, t.created)::date AS week FROM  table t;&lt;br /&gt;
 SELECT date_trunc(&#039;month&#039;, t.created)::date AS month FROM  table t;&lt;br /&gt;
&lt;br /&gt;
====extract====&lt;br /&gt;
 SELECT EXTRACT (hour   from datecol) FROM myTable&lt;br /&gt;
 SELECT EXTRACT (isodow from datecol) FROM myTable -- weekno&lt;br /&gt;
 SELECT EXTRACT (dow    from datecol) FROM myTable -- weekday&lt;br /&gt;
 SELECT EXTRACT(epoch FROM closed - created) / 3600 AS hours FROM myTable -- difference in hours&lt;br /&gt;
&lt;br /&gt;
====other====&lt;br /&gt;
 # gen list of dates&lt;br /&gt;
 SELECT i::date AS day FROM generate_series(&#039;2020-01-01&#039;, CURRENT_DATE, &#039;1 day&#039;::interval) i&lt;br /&gt;
&lt;br /&gt;
====Group by date and add missing days with zero count====&lt;br /&gt;
 WITH all_days AS (&lt;br /&gt;
     SELECT generate_series(&#039;01.01.2023&#039;, current_date, &#039;1 day&#039;) :: date AS date&lt;br /&gt;
 ), count_per_day AS (&lt;br /&gt;
     SELECT&lt;br /&gt;
         created :: date AS date,&lt;br /&gt;
         COUNT(*) AS count&lt;br /&gt;
     FROM my_table&lt;br /&gt;
     GROUP BY 1&lt;br /&gt;
 )&lt;br /&gt;
 SELECT&lt;br /&gt;
     all_days.date,&lt;br /&gt;
     COALESCE (count, 0) AS count&lt;br /&gt;
 FROM all_days&lt;br /&gt;
     LEFT OUTER JOIN count_per_day ON all_days.date = count_per_day.date&lt;br /&gt;
 ORDER BY&lt;br /&gt;
     1 DESC;&lt;br /&gt;
&lt;br /&gt;
===cast types===&lt;br /&gt;
 select t.id, &#039;matched filter1&#039;::character varying(16) from table t&lt;br /&gt;
 &lt;br /&gt;
 -- 1. convert timerange to seconds&lt;br /&gt;
 -- 2. perform average&lt;br /&gt;
 -- 3. cast to nummeric to allow for rounding&lt;br /&gt;
 SELECT round(CAST(EXTRACT(EPOCH FROM avg (s.closure_date - s.date_of_start)) /3600 as numeric),1) AS avg_hours_total_avg from myTable s group by cloumn1&lt;br /&gt;
 &lt;br /&gt;
 -- convert date range to days&lt;br /&gt;
 SELECT EXTRACT (DAY FROM (current_date - date_due)) AS days FROM myTable&lt;br /&gt;
&lt;br /&gt;
====leading zeros for integer values====&lt;br /&gt;
 select TO_CHAR(plant_id, &#039;fm0000&#039;) from myTable&lt;br /&gt;
&lt;br /&gt;
===rounding of REALS===&lt;br /&gt;
 round(lower_specification_limit::numeric,6) as lower_specification_limit,&lt;br /&gt;
&lt;br /&gt;
===EXCEPT (=MINUS in Oracle)===&lt;br /&gt;
from [http://www.postgresqltutorial.com/postgresql-tutorial/postgresql-except/]&lt;br /&gt;
 SELECT column_list&lt;br /&gt;
 FROM A&lt;br /&gt;
 WHERE condition_a&lt;br /&gt;
 EXCEPT &lt;br /&gt;
 SELECT column_list&lt;br /&gt;
 FROM B&lt;br /&gt;
 WHERE condition_b;&lt;br /&gt;
&lt;br /&gt;
===RowSum / LAG===&lt;br /&gt;
 WITH daily_sum AS (&lt;br /&gt;
 SELECT created, sum(count_finished)&lt;br /&gt;
 FROM c_report_timeseries&lt;br /&gt;
 WHERE department_id &amp;gt; 0 &lt;br /&gt;
 GROUP BY 1&lt;br /&gt;
 ORDER BY 1&lt;br /&gt;
 )&lt;br /&gt;
 SELECT created, sum, sum - lag(sum) over (order by created) as increase&lt;br /&gt;
 FROM daily_sum;&lt;br /&gt;
&lt;br /&gt;
===Grouping and stats: min, max, avg, median===&lt;br /&gt;
 select d.name &amp;quot;Department&amp;quot;,&lt;br /&gt;
 count(*) ,&lt;br /&gt;
 min(i.modified-i.created),&lt;br /&gt;
 max(i.modified-i.created),&lt;br /&gt;
 avg(i.modified-i.created),&lt;br /&gt;
 PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY (i.modified-i.created) ) as median&lt;br /&gt;
 EXTRACT(EPOCH FROM PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY (i.modified-i.created)))::int AS median_seconds&lt;br /&gt;
 FROM table i&lt;br /&gt;
&lt;br /&gt;
===Substring===&lt;br /&gt;
 -- 087249731000011 -&amp;gt; 087249731-0000-11&lt;br /&gt;
 SELECT id, plant_id, material_number&lt;br /&gt;
 , substring (material_number FROM 1 for 9) || &#039;-&#039; || substring (material_number FROM 10 for 4) || &#039;-&#039; || substring (material_number FROM 14 for 2) AS &amp;quot;MatNoNew&amp;quot;&lt;br /&gt;
 FROM d_template t &lt;br /&gt;
 WHERE 1=1&lt;br /&gt;
 AND t.department_id = 50012487&lt;br /&gt;
 AND material_number NOT LIKE &#039;%-%&#039;&lt;br /&gt;
 AND length(material_number) = 15&lt;br /&gt;
 ;&lt;br /&gt;
&lt;br /&gt;
===sequence.nextval===&lt;br /&gt;
 SELECT nextval(&#039;mysequence&#039;);&lt;br /&gt;
===Pivot / longtable to shorttable===&lt;br /&gt;
 WITH data AS (&lt;br /&gt;
     SELECT&lt;br /&gt;
     date_trunc(&#039;week&#039;, created)::date AS &amp;quot;Date&amp;quot;,&lt;br /&gt;
     myType,&lt;br /&gt;
     count (*)&lt;br /&gt;
     FROM myTable&lt;br /&gt;
     GROUP BY 1,2&lt;br /&gt;
 )&lt;br /&gt;
 SELECT &amp;quot;Date&amp;quot;,&lt;br /&gt;
 max(&amp;quot;count&amp;quot;) FILTER (WHERE myType= &#039;D&#039;) AS &amp;quot;cntD&amp;quot;,&lt;br /&gt;
 max(&amp;quot;count&amp;quot;) FILTER (WHERE myType= &#039;S&#039;) AS &amp;quot;cntS&lt;br /&gt;
 max(&amp;quot;count&amp;quot;) FILTER (WHERE myType= &#039;M&#039;) AS &amp;quot;cntM&amp;quot;&lt;br /&gt;
 FROM data&lt;br /&gt;
 GROUP BY 1&lt;br /&gt;
 ORDER BY 1 ASC&lt;br /&gt;
&lt;br /&gt;
===Weighted Average===&lt;br /&gt;
 WITH my_table AS (&lt;br /&gt;
 SELECT id, property, val1, val2, val3&lt;br /&gt;
 CASE &lt;br /&gt;
   WHEN created&amp;gt;current_date - 365 THEN 3&lt;br /&gt;
   WHEN created&amp;gt;current_date - 730 THEN 2&lt;br /&gt;
   ELSE 1&lt;br /&gt;
 END AS weighting_factor&lt;br /&gt;
 )&lt;br /&gt;
 SELECT&lt;br /&gt;
 sum(val1*weighting_factor)/sum(weighting_factor) AS &amp;quot;val1w&amp;quot;,&lt;br /&gt;
 sum(val2*weighting_factor)/sum(weighting_factor) AS &amp;quot;val2w&amp;quot;,&lt;br /&gt;
 sum(val3*weighting_factor)/sum(weighting_factor) AS &amp;quot;val3w&amp;quot;&lt;br /&gt;
 FROM my_table&lt;br /&gt;
 GROUP BY property&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==INSERT==&lt;br /&gt;
===Insert or Update if conflict===&lt;br /&gt;
 INSERT INTO login_cnt (user_id)&lt;br /&gt;
 VALUES (%s)&lt;br /&gt;
 ON CONFLICT (user_id, &amp;quot;date&amp;quot;)&lt;br /&gt;
 DO UPDATE SET count = login_cnt.count + 1;&lt;br /&gt;
 &lt;br /&gt;
 -- login counter table&lt;br /&gt;
 CREATE TABLE&lt;br /&gt;
   login_cnt (&lt;br /&gt;
     user_id int2 NOT NULL,&lt;br /&gt;
     &amp;quot;date&amp;quot; date DEFAULT current_date NOT NULL,&lt;br /&gt;
     count int2 DEFAULT 1 NOT NULL,&lt;br /&gt;
     CONSTRAINT pkey_login_cnt PRIMARY KEY (user_id, date)&lt;br /&gt;
   );&lt;br /&gt;
&lt;br /&gt;
===Insert with conflicting IDs===&lt;br /&gt;
 insert into machine_group(id, area, number)&lt;br /&gt;
 values (6, 6, 6),&lt;br /&gt;
        (7, 6, 7),&lt;br /&gt;
        (8, 6, 8),&lt;br /&gt;
        (9, 6, 9),&lt;br /&gt;
        (10, 6, 10)&lt;br /&gt;
 on conflict (id) do nothing;&lt;br /&gt;
&lt;br /&gt;
==UPDATE==&lt;br /&gt;
===REGEXP and REGEXP_REPLACE===&lt;br /&gt;
 -- replace &lt;br /&gt;
 UPDATE mytable&lt;br /&gt;
 SET remark = REPLACE(remark, &#039;&amp;amp;amp;&#039;, &#039;&amp;amp;&#039;)&lt;br /&gt;
 WHERE remark LIKE &#039;%&amp;amp;amp;%&#039;&lt;br /&gt;
 &lt;br /&gt;
 -- remove _|_ -&amp;gt; linebreak \n=10&lt;br /&gt;
 UPDATE mytable&lt;br /&gt;
 SET remark = REPLACE(remark, &#039;_|_&#039;, chr(10) )&lt;br /&gt;
 WHERE remark LIKE &#039;%_|_%&#039;&lt;br /&gt;
&lt;br /&gt;
===UPDATE via SELECT===&lt;br /&gt;
update single column from another table&lt;br /&gt;
 UPDATE q_user&lt;br /&gt;
 SET    card_no = q_upload_user.card_no&lt;br /&gt;
 FROM   q_upload_user  &lt;br /&gt;
 WHERE  q_user.id = q_upload_user.id&lt;br /&gt;
&lt;br /&gt;
using WITH temp table&lt;br /&gt;
 WITH map_name_id AS &lt;br /&gt;
 (&lt;br /&gt;
 SELECT short_name, min(id) min_id&lt;br /&gt;
  FROM   q_upload_organization&lt;br /&gt;
  GROUP BY short_name &lt;br /&gt;
 )&lt;br /&gt;
 UPDATE q_upload_user2&lt;br /&gt;
 SET    department_id = map_name_id.min_id&lt;br /&gt;
 FROM   map_name_id  &lt;br /&gt;
 WHERE  q_upload_user2.dpt_short_name_sap = map_name_id.short_name&lt;br /&gt;
&lt;br /&gt;
sync table tab1 from tab2&lt;br /&gt;
 UPDATE tab1&lt;br /&gt;
 SET (group_id, error_de, error_en, error_hu) = (&lt;br /&gt;
  SELECT group_id, error_de, error_en, error_hu FROM tab2 WHERE tab1.code = tab2.code&lt;br /&gt;
 )&lt;br /&gt;
&lt;br /&gt;
===pseudonymization===&lt;br /&gt;
 UPDATE user&lt;br /&gt;
 SET&lt;br /&gt;
 username = replace( format(&#039;u%7s&#039;, id), &#039; &#039;, &#039;0&#039;) -- u0000001 for id=1&lt;br /&gt;
 ,first_name = (array[&#039;Max&#039;, &#039;Egon&#039;, &#039;Lisa&#039;]) [floor(random() * 3 + 1)]&lt;br /&gt;
 ,last_name = (array[&#039;Meier&#039;, &#039;Müller&#039;, &#039;Schmidt&#039;)[floor(random() * 3 + 1)]&lt;br /&gt;
 ,email = &#039;no-mail@schaeffler.com&#039;&lt;br /&gt;
 WHERE id = 1;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==DELETE==&lt;br /&gt;
===Performance for DELETE Statement===&lt;br /&gt;
from [https://dba.stackexchange.com/a/35007]&lt;br /&gt;
 Good: DELETE FROM foo WHERE id IN (select id from rows_to_delete);&lt;br /&gt;
 Bad:  DELETE FROM foo WHERE id NOT IN (select id from rows_to_keep);&lt;br /&gt;
 Good Alternative for the bad rows_to_keep path:&lt;br /&gt;
 DELETE FROM foo &lt;br /&gt;
  WHERE id IN (&lt;br /&gt;
   SELECT f.id FROM foo f &lt;br /&gt;
   LEFT JOIN rows_to_keep k ON f.id = k.id&lt;br /&gt;
              WHERE k.id IS NULL&lt;br /&gt;
  );&lt;br /&gt;
&lt;br /&gt;
delete based on multiple criteria also in another table&lt;br /&gt;
 DELETE FROM&lt;br /&gt;
   table1 t1&lt;br /&gt;
 WHERE&lt;br /&gt;
   EXISTS (&lt;br /&gt;
   SELECT&lt;br /&gt;
     1&lt;br /&gt;
   FROM&lt;br /&gt;
     table2 t2&lt;br /&gt;
   WHERE&lt;br /&gt;
     1 = 1&lt;br /&gt;
     AND t2.date &amp;lt; CURRENT_DATE - 30&lt;br /&gt;
     AND t1.col1 = t2.col1&lt;br /&gt;
     AND t1.col2 = t2.col2 )&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==CREATE TABLE==&lt;br /&gt;
===Create Table with auto-increment id===&lt;br /&gt;
 CREATE TABLE test_auto_id (&lt;br /&gt;
 id serial PRIMARY KEY,&lt;br /&gt;
 value TEXT NOT NULL&lt;br /&gt;
 );&lt;br /&gt;
 &lt;br /&gt;
 INSERT INTO test_auto_id (value) VALUES (&#039;asdf&#039;);&lt;br /&gt;
 &lt;br /&gt;
 SELECT * FROM test_auto_id ;&lt;br /&gt;
&lt;br /&gt;
===Create Table Like===&lt;br /&gt;
 -- without contents&lt;br /&gt;
 CREATE TABLE myNewTable AS SELECT * FROM myTable WHERE 1=2;&lt;br /&gt;
 -- without contents but including index etc (but no permissions)&lt;br /&gt;
 CREATE TABLE myNewTable (like myTable including all);&lt;br /&gt;
&lt;br /&gt;
===Create unlogged cache/caching table===&lt;br /&gt;
inspired from [https://martinheinz.dev/blog/105 You Don&#039;t Need a Dedicated Cache Service - PostgreSQL as a Cache]&lt;br /&gt;
 CREATE UNLOGGED TABLE my_cache (&lt;br /&gt;
 url text UNIQUE NOT NULL,&lt;br /&gt;
 value jsonb,&lt;br /&gt;
 delete_at timestamp WITHOUT TIME ZONE DEFAULT now() + &#039;24 hours&#039;::interval );&lt;br /&gt;
 &lt;br /&gt;
 CREATE INDEX idx_cache_key ON my_cache (url);&lt;br /&gt;
 &lt;br /&gt;
 INSERT INTO my_cache  (url, value)&lt;br /&gt;
 VALUES (&#039;myURL2.com&#039;, &#039;{&amp;quot;key&amp;quot;:&amp;quot;value&amp;quot;}&#039;);&lt;br /&gt;
&lt;br /&gt;
==FUNCTION in plpgsql==&lt;br /&gt;
 CREATE OR REPLACE FUNCTION drm.tmp_change_plant_id(id_old int, id_new int)&lt;br /&gt;
  RETURNS void AS $$   &lt;br /&gt;
 BEGIN&lt;br /&gt;
 &lt;br /&gt;
 UPDATE myTable SET col = 123 WHERE col = 456;&lt;br /&gt;
 &lt;br /&gt;
 END;&lt;br /&gt;
 $$ LANGUAGE plpgsql;&lt;br /&gt;
 ;&lt;br /&gt;
&lt;br /&gt;
==Users==&lt;br /&gt;
===grant permission to user===&lt;br /&gt;
 -- as user myuser&lt;br /&gt;
 GRANT USAGE ON SCHEMA myschema TO myuser2;&lt;br /&gt;
 GRANT SELECT, INSERT ON TABLE mytable TO myuser2;&lt;br /&gt;
&lt;br /&gt;
===read only user===&lt;br /&gt;
 CREATE USER myUser PASSWORD &#039;myPassword&#039;;&lt;br /&gt;
 GRANT USAGE ON SCHEMA mySchema TO myUser&lt;br /&gt;
 GRANT SELECT ON ALL TABLES IN SCHEMA mySchema TO myUser&lt;br /&gt;
 &lt;br /&gt;
 -- needed for views that are added later&lt;br /&gt;
 ALTER DEFAULT PRIVILEGES IN SCHEMA mySchema GRANT SELECT ON TABLES TO myUser;&lt;br /&gt;
 &lt;br /&gt;
 -- default to search in another schema&lt;br /&gt;
 ALTER ROLE myUser SET search_path TO mySchema, public;&lt;br /&gt;
 &lt;br /&gt;
 -- set default for transactions to read-only&lt;br /&gt;
 ALTER USER myUser SET default_transaction_read_only = on;&lt;br /&gt;
 &lt;br /&gt;
 -- check via&lt;br /&gt;
 SELECT * FROM pg_db_role_setting&lt;br /&gt;
 -- -&amp;gt; unfold the results&lt;br /&gt;
&lt;br /&gt;
===Disable user===&lt;br /&gt;
 ALTER USER myuser WITH NOLOGIN&lt;br /&gt;
&lt;br /&gt;
===Change password===&lt;br /&gt;
 ALTER USER user_name WITH PASSWORD &#039;new_password&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Schema Ownership==&lt;br /&gt;
 GRANT myuser TO postgresadmin;&lt;br /&gt;
 ALTER SCHEMA myschema OWNER TO myuser;&lt;br /&gt;
 GRANT ALL ON SCHEMA myschema TO myuser;&lt;br /&gt;
 REVOKE myuser FROM postgresadmin;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Other==&lt;br /&gt;
&lt;br /&gt;
===sprintf-alternative===&lt;br /&gt;
goal&lt;br /&gt;
 sprintf &#039;u%07d&#039;, 1&lt;br /&gt;
achieved via&lt;br /&gt;
 SELECT replace( format(&#039;u%7s&#039;, 1), &#039; &#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
===random item of list===&lt;br /&gt;
 SELECT (array[&#039;Yes&#039;, &#039;No&#039;, &#039;Maybe&#039;])[floor(random() * 3 + 1)];&lt;br /&gt;
&lt;br /&gt;
===Count the occurrences of a substring in a string===&lt;br /&gt;
solution based on [https://dba.stackexchange.com/questions/166762/how-do-you-count-the-occurrences-of-an-anchored-string-using-postgresql/166763#166763] and [https://stackoverflow.com/a/42708237]&lt;br /&gt;
&lt;br /&gt;
 SELECT&lt;br /&gt;
  (length(str) - length(replace(str, replacestr, &#039;&#039;)) )::int&lt;br /&gt;
  / length(replacestr)&lt;br /&gt;
 FROM ( VALUES&lt;br /&gt;
  (&#039;foobarbaz&#039;, &#039;ba&#039;)&lt;br /&gt;
 ) AS t(str, replacestr);&lt;br /&gt;
becomes &lt;br /&gt;
 SELECT&lt;br /&gt;
   sub1.id, &lt;br /&gt;
   (length(sub1.str) - length(replace(sub1.str, sub1.replacestr, &#039;&#039;)) )::int  / length(sub1.replacestr) AS times&lt;br /&gt;
 FROM&lt;br /&gt;
 (&lt;br /&gt;
 SELECT id, checklist AS str, &#039;{&amp;quot;id&amp;quot;:&#039;::text AS replacestr FROM d_department&lt;br /&gt;
 ) sub1&lt;br /&gt;
which counts the number of (json) ids &#039;{&amp;quot;id&amp;quot;:&#039; in a textfield&lt;br /&gt;
&lt;br /&gt;
===Get table column type / describe alternative===&lt;br /&gt;
 SELECT&lt;br /&gt;
   table_name,&lt;br /&gt;
   column_name,&lt;br /&gt;
   data_type,&lt;br /&gt;
   character_maximum_length&lt;br /&gt;
 FROM&lt;br /&gt;
   INFORMATION_SCHEMA.COLUMNS&lt;br /&gt;
 WHERE&lt;br /&gt;
   1 = 1&lt;br /&gt;
   AND table_schema IN (&#039;schema1&#039;,&#039;schema2&#039;)&lt;br /&gt;
   AND (table_name LIKE &#039;c\_%&#039; OR table_name LIKE &#039;d\_%&#039;)&lt;br /&gt;
   AND column_name = &#039;order&#039;&lt;br /&gt;
 ORDER BY&lt;br /&gt;
   table_name,&lt;br /&gt;
   column_name&lt;br /&gt;
&lt;br /&gt;
===Concatenate / string_agg columns of multiple rows===&lt;br /&gt;
 SELECT id, STRING_AGG(c.comment, &#039;&lt;br /&gt;
 -----&lt;br /&gt;
 &#039; ORDER BY c.created ASC) AS comment&lt;br /&gt;
 FROM comment c&lt;br /&gt;
 GROUP BY id&lt;br /&gt;
&lt;br /&gt;
==Performance Tuning==&lt;br /&gt;
use this to check the SQL speed:&lt;br /&gt;
 EXPLAIN (analyze,buffers,timing) &lt;br /&gt;
 SELECT * FROM table WHERE ...&lt;br /&gt;
&lt;br /&gt;
===EXISTS===&lt;br /&gt;
 SELECT 1 WHERE EXISTS ( SELECT 1 FROM table WHERE field = :field )&lt;br /&gt;
 --Is much faster than performing a COUNT(*) and later check if greater than 0&lt;br /&gt;
&lt;br /&gt;
===UNION vs UNION ALL===&lt;br /&gt;
UNION does include a DISTINCT, while UNION ALL does not, so if you know the data is unique, use UNION ALL&lt;br /&gt;
&lt;br /&gt;
===SELECT WHERE IN () Statement===&lt;br /&gt;
when passing a long list of values into a statement like&lt;br /&gt;
 SELECT * FROM myTable where id in (1,2,3,4,5,6,7,8,...);&lt;br /&gt;
the statement becomes very slow, see [https://stackoverflow.com/questions/39246746/postgres-query-with-in-is-very-slow]&lt;br /&gt;
&lt;br /&gt;
Better use a WITH clause (=CTE = common table expressions):&lt;br /&gt;
 WITH value_list AS (&lt;br /&gt;
   VALUES (1),(2),...&lt;br /&gt;
 )&lt;br /&gt;
 SELECT * FROM myTable WHERE id IN (SELECT * FROM value_list);&lt;br /&gt;
&lt;br /&gt;
===Find Missing Index/Indices===&lt;br /&gt;
Read https://use-the-index-luke.com/&lt;br /&gt;
&lt;br /&gt;
from https://www.cybertec-postgresql.com/en/index-your-foreign-key/&lt;br /&gt;
 SELECT c.conrelid::regclass AS &amp;quot;table&amp;quot;,&lt;br /&gt;
        /* list of key column names in order */&lt;br /&gt;
        string_agg(a.attname, &#039;,&#039; ORDER BY x.n) AS columns,&lt;br /&gt;
        pg_catalog.pg_size_pretty(&lt;br /&gt;
           pg_catalog.pg_relation_size(c.conrelid)&lt;br /&gt;
        ) AS size,&lt;br /&gt;
        c.conname AS constraint,&lt;br /&gt;
        c.confrelid::regclass AS referenced_table&lt;br /&gt;
 FROM pg_catalog.pg_constraint c&lt;br /&gt;
    /* enumerated key column numbers per foreign key */&lt;br /&gt;
    CROSS JOIN LATERAL&lt;br /&gt;
       unnest(c.conkey) WITH ORDINALITY AS x(attnum, n)&lt;br /&gt;
    /* name for each key column */&lt;br /&gt;
    JOIN pg_catalog.pg_attribute a&lt;br /&gt;
       ON a.attnum = x.attnum&lt;br /&gt;
          AND a.attrelid = c.conrelid&lt;br /&gt;
 WHERE NOT EXISTS&lt;br /&gt;
         /* is there a matching index for the constraint? */&lt;br /&gt;
         (SELECT 1 FROM pg_catalog.pg_index i&lt;br /&gt;
          WHERE i.indrelid = c.conrelid&lt;br /&gt;
            /* it must not be a partial index */&lt;br /&gt;
            AND i.indpred IS NULL&lt;br /&gt;
            /* the first index columns must be the same as the&lt;br /&gt;
               key columns, but order doesn&#039;t matter */&lt;br /&gt;
            AND (i.indkey::smallint[])[0:cardinality(c.conkey)-1]&lt;br /&gt;
                OPERATOR(pg_catalog.@&amp;gt;) c.conkey)&lt;br /&gt;
   AND c.contype = &#039;f&#039;&lt;br /&gt;
 GROUP BY c.conrelid, c.conname, c.confrelid&lt;br /&gt;
 ORDER BY pg_catalog.pg_relation_size(c.conrelid) DESC;&lt;br /&gt;
&lt;br /&gt;
==Admin==&lt;br /&gt;
 SELECT version();&lt;br /&gt;
&lt;br /&gt;
===PostgreSQL Monitoring Cheatsheet===&lt;br /&gt;
see [https://russ.garrett.co.uk/2015/10/02/postgres-monitoring-cheatsheet/]&lt;br /&gt;
&lt;br /&gt;
===Disconnect all sessions===&lt;br /&gt;
 SELECT	pg_terminate_backend(pg_stat_activity.pid)&lt;br /&gt;
 FROM pg_stat_activity&lt;br /&gt;
 WHERE pg_stat_activity.datname = &#039;myDB&#039;&lt;br /&gt;
   AND pid &amp;lt;&amp;gt; pg_backend_pid();&lt;br /&gt;
&lt;br /&gt;
===Drop and recreate DB via psql ===&lt;br /&gt;
 SET PGPASSWORD=myPassword&lt;br /&gt;
 psql --port=5432 --username=myUser&lt;br /&gt;
 DROP database IF EXISTS myDB;&lt;br /&gt;
 CREATE database myDB;&lt;br /&gt;
 \c myDB;&lt;br /&gt;
 DROP SCHEMA IF EXISTS public;&lt;br /&gt;
 CREATE SCHEMA public;&lt;br /&gt;
 \q&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Table size including Indices===&lt;br /&gt;
 SELECT&lt;br /&gt;
     table_name,&lt;br /&gt;
     pg_size_pretty(table_size) AS table_size,&lt;br /&gt;
     pg_size_pretty(indexes_size) AS indexes_size,&lt;br /&gt;
     pg_size_pretty(total_size) AS total_size&lt;br /&gt;
 FROM (&lt;br /&gt;
     SELECT&lt;br /&gt;
         table_name,&lt;br /&gt;
         pg_table_size(table_name) AS table_size,&lt;br /&gt;
         pg_indexes_size(table_name) AS indexes_size,&lt;br /&gt;
         pg_total_relation_size(table_name) AS total_size&lt;br /&gt;
     FROM (&lt;br /&gt;
         SELECT (&#039;&amp;quot;&#039; || table_schema || &#039;&amp;quot;.&amp;quot;&#039; || table_name || &#039;&amp;quot;&#039;) AS table_name&lt;br /&gt;
         FROM information_schema.tables&lt;br /&gt;
     ) AS all_tables&lt;br /&gt;
     ORDER BY total_size DESC&lt;br /&gt;
 ) AS pretty_sizes;&lt;br /&gt;
&lt;br /&gt;
===Find table column candidates for type enum (less than 10 different values)===&lt;br /&gt;
 DO $$&lt;br /&gt;
 DECLARE&lt;br /&gt;
 r RECORD;&lt;br /&gt;
 query TEXT;&lt;br /&gt;
 all_queries TEXT := &#039;&#039;;&lt;br /&gt;
 my_table_name TEXT := &#039;d_template&#039;;&lt;br /&gt;
 BEGIN&lt;br /&gt;
   FOR r IN &lt;br /&gt;
   SELECT column_name, data_type&lt;br /&gt;
 FROM information_schema.COLUMNS&lt;br /&gt;
 WHERE table_name = my_table_name&lt;br /&gt;
 AND data_type NOT IN (&#039;bigint&#039;, &#039;integer&#039;, &#039;timestamp without time zone&#039;, &#039;boolean&#039;, &#039;USER-DEFINED&#039;)&lt;br /&gt;
 ORDER BY column_name&lt;br /&gt;
 LOOP&lt;br /&gt;
   query := &#039;SELECT &#039;&#039;&#039; || r.column_name || &#039;&#039;&#039; AS column_name, count(*) FROM (SELECT DISTINCT &#039; || r.column_name || &#039; FROM &#039; || my_table_name || &#039; LIMIT 10) s1 HAVING count(*)&amp;lt;10&#039;;&lt;br /&gt;
 --RAISE NOTICE &#039;%&#039;, query;&lt;br /&gt;
 IF all_queries &amp;lt;&amp;gt; &#039;&#039; THEN&lt;br /&gt;
 all_queries := all_queries || &#039; UNION ALL &#039;;&lt;br /&gt;
 END IF;&lt;br /&gt;
 all_queries := all_queries || query;&lt;br /&gt;
 END LOOP;&lt;br /&gt;
 RAISE NOTICE &#039;%&#039;, all_queries;&lt;br /&gt;
 --EXECUTE all_queries;&lt;br /&gt;
 END $$;&lt;br /&gt;
&lt;br /&gt;
===Vacuum / Autovacuum===&lt;br /&gt;
====Manual====&lt;br /&gt;
 VACUUM VERBOSE ANALYZE myTable;&lt;br /&gt;
or with exclusive lock:&lt;br /&gt;
 VACUUM FULL VERBOSE ANALYZE myTable;&lt;br /&gt;
====AutoVacuum====&lt;br /&gt;
 SELECT name, setting FROM pg_settings WHERE name=&#039;autovacuum&#039;;&lt;br /&gt;
 -- should be on&lt;br /&gt;
 SELECT * from pg_settings where category like &#039;Autovacuum&#039;;&lt;br /&gt;
 --overview by &lt;br /&gt;
 SELECT schemaname, relname, last_autoanalyze, last_analyze, last_autovacuum, last_vacuum, n_live_tup, n_dead_tup,*&lt;br /&gt;
 FROM &amp;quot;pg_catalog&amp;quot;.&amp;quot;pg_stat_all_tables&amp;quot;&lt;br /&gt;
 WHERE schemaname = &#039;mySchema&#039; &lt;br /&gt;
 ORDER BY last_autoanalyze DESC&lt;br /&gt;
&lt;br /&gt;
==Performance Analysis==&lt;br /&gt;
&lt;br /&gt;
====List of currently running Queries====&lt;br /&gt;
 SELECT pid, datname, usename, state, query, query_start, now()-query_start AS runtime, client_addr, application_name, *&lt;br /&gt;
 FROM pg_stat_activity&lt;br /&gt;
 WHERE 1=1&lt;br /&gt;
 ORDER BY query_start ASC ;&lt;br /&gt;
&lt;br /&gt;
====pg_stat_statements module====&lt;br /&gt;
pg_stat_statements module is very nice, as it displays which queries run how often and how long, see for example [https://dba.stackexchange.com/questions/44084/troubleshooting-high-cpu-usage-from-postgres-and-postmaster-services] &lt;br /&gt;
&amp;lt;br/&amp;gt;Installation &lt;br /&gt;
* postgresql.conf&lt;br /&gt;
 in : section # - Kernel Resource Usage -&lt;br /&gt;
 add: shared_preload_libraries = &#039;pg_stat_statements&#039;&lt;br /&gt;
* server parameter &#039;pg_stat_statements.track&#039; must be set to &#039;top&#039; or &#039;all&#039;&lt;br /&gt;
* restart PostgreSQL&lt;br /&gt;
 CREATE EXTENSION pg_stat_statements&lt;br /&gt;
 (in each DB)&lt;br /&gt;
* check in available extensions via&lt;br /&gt;
 SELECT * FROM pg_available_extensions WHERE name = &#039;pg_stat_statements&#039;&lt;br /&gt;
finally run (recent postgres version)&lt;br /&gt;
 SELECT &lt;br /&gt;
   round((total_exec_time / 1000 / 3600)::numeric,2) as total_hours,&lt;br /&gt;
   round((total_exec_time / 1000 / calls)::numeric,3) as avg_sec,&lt;br /&gt;
   calls num_calls,&lt;br /&gt;
   query&lt;br /&gt;
 FROM pg_stat_statements &lt;br /&gt;
 WHERE 1=1&lt;br /&gt;
 AND query != &#039;&amp;lt;insufficient privilege&amp;gt;&#039; -- only by current user&lt;br /&gt;
 -- AND lower(query) LIKE &#039;%select *%join%&#039;&lt;br /&gt;
 ORDER BY 2 DESC LIMIT 100;&lt;br /&gt;
finally run (old postgres version)&lt;br /&gt;
 SELECT &lt;br /&gt;
   round((total_time / 1000 / 3600)::numeric,2) as total_hours,&lt;br /&gt;
   round((total_time / 1000 / calls)::numeric,3) as avg_sec,&lt;br /&gt;
   calls num_calls,&lt;br /&gt;
   query&lt;br /&gt;
 FROM pg_stat_statements &lt;br /&gt;
 WHERE 1=1&lt;br /&gt;
 AND query != &#039;&amp;lt;insufficient privilege&amp;gt;&#039; -- only by current user&lt;br /&gt;
 -- AND lower(query) LIKE &#039;%select *%join%&#039;&lt;br /&gt;
 ORDER BY 2 DESC LIMIT 100;&lt;br /&gt;
* Grant access to non-admin users:&lt;br /&gt;
 GRANT USAGE ON SCHEMA public TO myUser;&lt;br /&gt;
 GRANT SELECT ON pg_stat_statements TO myUser;&lt;br /&gt;
* Reset stats (as admin user)&lt;br /&gt;
 SELECT pg_stat_statements_reset()&lt;br /&gt;
&lt;br /&gt;
====Check/analyze why a certain statement runs slow====&lt;br /&gt;
from [https://dba.stackexchange.com/a/226481]&lt;br /&gt;
 EXPLAIN (analyze,buffers,timing) &lt;br /&gt;
  DELETE FROM myTable WHERE id IN (&lt;br /&gt;
   SELECT id FROM myTableToDelete&lt;br /&gt;
 ) AND id &amp;gt; 2150 &lt;br /&gt;
   AND id &amp;lt; 2200 ;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 EXPLAIN ANALYZE&lt;br /&gt;
 SELECT id FROM myTableToDelete&lt;br /&gt;
&lt;br /&gt;
analyze via &lt;br /&gt;
 psql -qAt -f explain.sql &amp;gt; analyze.json&lt;br /&gt;
 http://tatiyants.com/pev/#/plans/new&lt;br /&gt;
&lt;br /&gt;
====Find and Delete Locks / Processes====&lt;br /&gt;
 GRANT pg_signal_backend TO postgresadmin WITH ADMIN OPTION;&lt;br /&gt;
 SELECT pg_terminate_backend(pid)&lt;br /&gt;
     FROM pg_stat_activity&lt;br /&gt;
     WHERE pid &amp;lt;&amp;gt; pg_backend_pid();&lt;br /&gt;
 &lt;br /&gt;
 -- Display PIDs and Queries running on a table, from [https://jaketrent.com/post/find-kill-locks-postgres/]&lt;br /&gt;
 SELECT pid, state, usename, query, query_start &lt;br /&gt;
   FROM pg_stat_activity &lt;br /&gt;
  WHERE pid IN (&lt;br /&gt;
   SELECT pid FROM pg_locks l &lt;br /&gt;
     JOIN pg_class t ON l.relation = t.oid AND t.relkind = &#039;r&#039; &lt;br /&gt;
    WHERE t.relname = &#039;myTable&#039;&lt;br /&gt;
  );&lt;br /&gt;
 &lt;br /&gt;
 -- show all processes on current DB&lt;br /&gt;
 SELECT datname, pid, state, query, age(clock_timestamp(), query_start) AS age &lt;br /&gt;
 FROM pg_stat_activity&lt;br /&gt;
 WHERE 1=1 &lt;br /&gt;
     AND state &amp;lt;&amp;gt; &#039;idle&#039; &lt;br /&gt;
     AND query NOT LIKE &#039;% FROM pg_stat_activity %&#039; &lt;br /&gt;
     AND datname = current_database() &lt;br /&gt;
 ORDER BY age;&lt;br /&gt;
 &lt;br /&gt;
 -- show locks&lt;br /&gt;
 SELECT t.relname, *&lt;br /&gt;
   FROM pg_locks l&lt;br /&gt;
   JOIN pg_class t ON l.relation = t.oid AND t.relkind = &#039;r&#039;&lt;br /&gt;
  WHERE t.relname != &#039;pg_class&#039;;&lt;br /&gt;
 &lt;br /&gt;
 -- cancel process&lt;br /&gt;
 SELECT pg_cancel_backend(&amp;lt;my_pid&amp;gt;);&lt;br /&gt;
 COMMIT;&lt;br /&gt;
 &lt;br /&gt;
 -- force cancel process&lt;br /&gt;
 SELECT pg_terminate_backend(&amp;lt;my_pid&amp;gt;);&lt;br /&gt;
 COMMIT;&lt;br /&gt;
 &lt;br /&gt;
 -- force cancel all processes&lt;br /&gt;
 SELECT pg_terminate_backend(pid)&lt;br /&gt;
 FROM pg_stat_activity&lt;br /&gt;
 WHERE pid &amp;lt;&amp;gt; pg_backend_pid() AND datname = current_database();&lt;br /&gt;
 COMMIT;&lt;br /&gt;
&lt;br /&gt;
===Backup and Restore===&lt;br /&gt;
Backup&lt;br /&gt;
 SET PGPASSWORD=myPassword&lt;br /&gt;
 pg_dump.exe --host=localhost --port=5432 --username=myUser --dbname=myDB --schema=mySchema --Format c --blobs --verbose --file myDB.dmp &lt;br /&gt;
Restore (as admin user postgres)&lt;br /&gt;
 ALTER SCHEMA &amp;quot;myDB&amp;quot; RENAME TO &amp;quot;myDB.old&amp;quot;;&lt;br /&gt;
 SET PGPASSWORD=myPassword&lt;br /&gt;
 pg_restore --host=localhost --port=5432 --username=postgres --create --dbname myDB --verbose --exit-on-error myDB.dmp&lt;br /&gt;
 DROP SCHEMA &amp;quot;myDB.old&amp;quot; CASCADE;&lt;br /&gt;
&lt;br /&gt;
====Export and import single table====&lt;br /&gt;
 SET PGPASSWORD=myPassword&lt;br /&gt;
 &lt;br /&gt;
 # export single report table&lt;br /&gt;
 # use of pg_dump / restore is problematic, since it requires to have the same schema at the target DB, hence using COPY instead&lt;br /&gt;
 psql.exe --host=myHost1 --port=5432 --username=myUser --dbname=myDB --schema=mySchema ^&lt;br /&gt;
 --command=&amp;quot;COPY myTable TO STDOUT DELIMITER &#039;|&#039; CSV HEADER;&amp;quot; &amp;gt; myTable.csv&lt;br /&gt;
 # or&lt;br /&gt;
 psql.exe --host=myHost1 --port=5432 --username=myUser --dbname=myDB --schema=mySchema ^&lt;br /&gt;
 --command=&amp;quot;COPY (SELECT id,name FROM myTable) TO STDOUT DELIMITER &#039;|&#039; CSV HEADER;&amp;quot; &amp;gt; myTable.csv&lt;br /&gt;
 &lt;br /&gt;
 # import using COPY&lt;br /&gt;
 psql.exe &lt;br /&gt;
 --host=myHost1 --port=5432 --username=myUser --dbname=myDB ^&lt;br /&gt;
 -v ON_ERROR_STOP=1&lt;br /&gt;
 --command=&amp;quot;TRUNCATE TABLE myTable; COPY myTable FROM STDIN DELIMITER &#039;|&#039; CSV HEADER;&amp;quot; &amp;lt; myTable.csv&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Visual_Studio_Code&amp;diff=5227</id>
		<title>Visual Studio Code</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Visual_Studio_Code&amp;diff=5227"/>
		<updated>2025-05-18T15:35:36Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Draw.io */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Coding]]&lt;br /&gt;
===settings.json===&lt;br /&gt;
    // VS Code Core&lt;br /&gt;
    &amp;quot;diffEditor.ignoreTrimWhitespace&amp;quot;: false,&lt;br /&gt;
    // &amp;quot;editor.fontSize&amp;quot;: 18,&lt;br /&gt;
    &amp;quot;editor.accessibilitySupport&amp;quot;: &amp;quot;off&amp;quot;,&lt;br /&gt;
    &amp;quot;editor.formatOnSave&amp;quot;: true,&lt;br /&gt;
    &amp;quot;editor.minimap.enabled&amp;quot;: false,&lt;br /&gt;
    &amp;quot;editor.suggestSelection&amp;quot;: &amp;quot;first&amp;quot;,&lt;br /&gt;
    &amp;quot;editor.wordWrap&amp;quot;: &amp;quot;on&amp;quot;,&lt;br /&gt;
    &amp;quot;files.eol&amp;quot;: &amp;quot;\n&amp;quot;,&lt;br /&gt;
    &amp;quot;files.insertFinalNewline&amp;quot;: true,&lt;br /&gt;
    &amp;quot;files.trimTrailingWhitespace&amp;quot;: true,&lt;br /&gt;
    &amp;quot;files.exclude&amp;quot;: {&lt;br /&gt;
        &amp;quot;**/node_modules&amp;quot;: true,&lt;br /&gt;
    },    &amp;quot;outline.showVariables&amp;quot;: false,&lt;br /&gt;
    &amp;quot;security.workspace.trust.untrustedFiles&amp;quot;: &amp;quot;open&amp;quot;,&lt;br /&gt;
    &amp;quot;telemetry.telemetryLevel&amp;quot;: &amp;quot;off&amp;quot;,&lt;br /&gt;
    &amp;quot;update.showReleaseNotes&amp;quot;: false,&lt;br /&gt;
    &amp;quot;vsintellicode.modify.editor.suggestSelection&amp;quot;: &amp;quot;automaticallyOverrodeDefaultValue&amp;quot;,&lt;br /&gt;
    &amp;quot;window.zoomLevel&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;workbench.startupEditor&amp;quot;: &amp;quot;none&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
===Shortcuts===&lt;br /&gt;
====Log File Cleanup====&lt;br /&gt;
* mark text to remove from the log&lt;br /&gt;
* to set cursor to all matching lines:  CTRL+F2 / CMD+F2&lt;br /&gt;
* to delete these lines: CTRL+SHIFT+K / CMD+SHIFT+K&lt;br /&gt;
&lt;br /&gt;
===Extensions===&lt;br /&gt;
====Arduiono====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=vsciot-vscode.vscode-arduino Arduino]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools C/C++]&lt;br /&gt;
&lt;br /&gt;
settings.json&lt;br /&gt;
    // Arduino / C&lt;br /&gt;
    &amp;quot;arduino.ignoreBoards&amp;quot;: [&lt;br /&gt;
        &amp;quot;Adafruit HUZZAH ESP8266&amp;quot;&lt;br /&gt;
    ],&lt;br /&gt;
    //    &amp;quot;C_Cpp.updateChannel&amp;quot;: &amp;quot;Insiders&amp;quot;,&lt;br /&gt;
    &amp;quot;[c]&amp;quot;: {&lt;br /&gt;
        &amp;quot;editor.wordBasedSuggestions&amp;quot;: false,&lt;br /&gt;
        &amp;quot;editor.suggest.insertMode&amp;quot;: &amp;quot;replace&amp;quot;,&lt;br /&gt;
        &amp;quot;editor.semanticHighlighting.enabled&amp;quot;: true,&lt;br /&gt;
        &amp;quot;editor.quickSuggestions&amp;quot;: {&lt;br /&gt;
            &amp;quot;comments&amp;quot;: &amp;quot;on&amp;quot;,&lt;br /&gt;
            &amp;quot;strings&amp;quot;: &amp;quot;on&amp;quot;,&lt;br /&gt;
            &amp;quot;other&amp;quot;: &amp;quot;on&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
====Better Comments====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items/?itemName=aaron-bond.better-comments Better Comments&lt;br /&gt;
&lt;br /&gt;
====Clipboard History====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=Anjali.clipboard-history Clipboard History]&lt;br /&gt;
&lt;br /&gt;
====CSpell - Code Spell Checker====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker CSpell]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker-german CSpell German]&lt;br /&gt;
&lt;br /&gt;
settings.json&lt;br /&gt;
    // cSpell&lt;br /&gt;
    &amp;quot;cSpell.language&amp;quot;: &amp;quot;en-US,de-DE&amp;quot;,&lt;br /&gt;
    &amp;quot;cSpell.userWords&amp;quot;: [&lt;br /&gt;
        &amp;quot;Menke&amp;quot;,&lt;br /&gt;
        &amp;quot;Torben&amp;quot;&lt;br /&gt;
    ],&lt;br /&gt;
and &amp;lt;br&amp;gt;&lt;br /&gt;
cspell.json (in project dir)&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;useGitignore&amp;quot;: true,&lt;br /&gt;
   &amp;quot;dictionaries&amp;quot;: [&lt;br /&gt;
     &amp;quot;cspell-words&amp;quot;&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;dictionaryDefinitions&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;name&amp;quot;: &amp;quot;cspell-words&amp;quot;,&lt;br /&gt;
       &amp;quot;path&amp;quot;: &amp;quot;cspell-words.txt&amp;quot;,&lt;br /&gt;
       &amp;quot;addWords&amp;quot;: true&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;language&amp;quot;: &amp;quot;en-US, de-DE&amp;quot;,&lt;br /&gt;
   &amp;quot;ignorePaths&amp;quot;: [&lt;br /&gt;
     &amp;quot;node_modules/**&amp;quot;,&lt;br /&gt;
     &amp;quot;src/assets/i18n/**&amp;quot;,&lt;br /&gt;
     &amp;quot;*.svg&amp;quot;&lt;br /&gt;
   ]&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
cspell-words.txt (configured above)&lt;br /&gt;
 Torben&lt;br /&gt;
 Menke&lt;br /&gt;
 !forbiddenword&lt;br /&gt;
&lt;br /&gt;
comments:&lt;br /&gt;
 ignoreWords -&amp;gt; forbitten words to allow&lt;br /&gt;
 dictionaryDefinitions -&amp;gt; custom wordlist/dictionary&lt;br /&gt;
 ignoreRegExpList -&amp;gt; code blocks to ignore&lt;br /&gt;
&lt;br /&gt;
ignore specific lines. Inside source code files use&lt;br /&gt;
 // cspell:disable-line -- disables checking for the current line.&lt;br /&gt;
 // cspell:disable-next-line -- disables checking till the end of the next line.&lt;br /&gt;
&lt;br /&gt;
====Check for unknown words in all files====&lt;br /&gt;
 cspell --words-only --unique &amp;quot;**/*.{txt,md,html,js,ts,json,yml,py}&amp;quot; &amp;gt; cspell-unknown-words.txt&lt;br /&gt;
&lt;br /&gt;
====CSV/TSV====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items/?itemName=mechatroner.rainbow-csv Rainbow CSV]&lt;br /&gt;
&lt;br /&gt;
====Draw.io====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio Draw.io Integration]&lt;br /&gt;
&lt;br /&gt;
====Excel Viewer====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=GrapeCity.gc-excelviewer Excel Viewer]&lt;br /&gt;
&lt;br /&gt;
====Git====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=donjayamanne.githistory Git History]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=waderyan.gitblame Git Blame]&lt;br /&gt;
&lt;br /&gt;
settings.json&lt;br /&gt;
    // Git &lt;br /&gt;
    &amp;quot;git.autofetch&amp;quot;: true,&lt;br /&gt;
    &amp;quot;git.confirmSync&amp;quot;: false,&lt;br /&gt;
    &amp;quot;git.enableSmartCommit&amp;quot;: true,&lt;br /&gt;
&lt;br /&gt;
====Gnuplot====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=mammothb.gnuplot Gnuplot]&lt;br /&gt;
&lt;br /&gt;
====HTML====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=HTMLHint.vscode-htmlhint HTMLHint]&lt;br /&gt;
&lt;br /&gt;
settings.json&lt;br /&gt;
    // HTML&lt;br /&gt;
    &amp;quot;html.format.wrapLineLength&amp;quot;: 0,&lt;br /&gt;
    &amp;quot;html.format.maxPreserveNewLines&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;html.format.preserveNewLines&amp;quot;: false,&lt;br /&gt;
    &amp;quot;html.format.extraLiners&amp;quot;: &amp;quot;head, body, /html, p, h1, h2, h3, h4, h5, h6&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====JavaScript and Vue====&lt;br /&gt;
see https://vueschool.io/articles/vuejs-tutorials/eslint-and-prettier-with-vite-and-vue-js-3/&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint ESLint]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode Prettier]&lt;br /&gt;
=====Vue.js=====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=Vue.volar Vue]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin Vue TS]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=vuetifyjs.vuetify-vscode Vuetify]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=ZixuanChen.vitest-explorer Vitest]&lt;br /&gt;
&lt;br /&gt;
settings.json&lt;br /&gt;
  //&lt;br /&gt;
  // JSON &amp;amp; YAML&lt;br /&gt;
  //&lt;br /&gt;
  &amp;quot;[json]&amp;quot;: {&lt;br /&gt;
    &amp;quot;editor.defaultFormatter&amp;quot;: &amp;quot;esbenp.prettier-vscode&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;[jsonc]&amp;quot;: {&lt;br /&gt;
    &amp;quot;editor.defaultFormatter&amp;quot;: &amp;quot;esbenp.prettier-vscode&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;[yaml]&amp;quot;: {&lt;br /&gt;
    &amp;quot;editor.defaultFormatter&amp;quot;: &amp;quot;esbenp.prettier-vscode&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  //&lt;br /&gt;
  // JavaScript &amp;amp; Vue&lt;br /&gt;
  //&lt;br /&gt;
  &amp;quot;[javascript]&amp;quot;: {&lt;br /&gt;
    &amp;quot;editor.codeActionsOnSave&amp;quot;: [&lt;br /&gt;
      &amp;quot;source.formatDocument&amp;quot;,&lt;br /&gt;
      &amp;quot;source.fixAll.eslint&amp;quot;&lt;br /&gt;
    ],&lt;br /&gt;
    &amp;quot;editor.defaultFormatter&amp;quot;: &amp;quot;esbenp.prettier-vscode&amp;quot;,&lt;br /&gt;
    // runs with &amp;quot;source.formatDocument&amp;quot;&lt;br /&gt;
    &amp;quot;editor.formatOnSave&amp;quot;: true&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;[vue]&amp;quot;: {&lt;br /&gt;
    &amp;quot;editor.codeActionsOnSave&amp;quot;: [&lt;br /&gt;
      &amp;quot;source.formatDocument&amp;quot;,&lt;br /&gt;
      &amp;quot;source.fixAll.eslint&amp;quot;&lt;br /&gt;
    ],&lt;br /&gt;
    &amp;quot;editor.defaultFormatter&amp;quot;: &amp;quot;esbenp.prettier-vscode&amp;quot;,&lt;br /&gt;
    // runs with &amp;quot;source.formatDocument&amp;quot;&lt;br /&gt;
    &amp;quot;editor.formatOnSave&amp;quot;: true&lt;br /&gt;
  },&lt;br /&gt;
  // Prettier settings - runs with &amp;quot;source.formatDocument&amp;quot;&lt;br /&gt;
  &amp;quot;prettier.enable&amp;quot;: true,&lt;br /&gt;
  &amp;quot;prettier.enableDebugLogs&amp;quot;: true,&lt;br /&gt;
  &amp;quot;prettier.trailingComma&amp;quot;: &amp;quot;all&amp;quot;,&lt;br /&gt;
  // ESLint settings - runs with &amp;quot;source.fixAll.eslint&amp;quot;&lt;br /&gt;
  &amp;quot;eslint.debug&amp;quot;: false,&lt;br /&gt;
  &amp;quot;eslint.format.enable&amp;quot;: true,&lt;br /&gt;
  &amp;quot;eslint.probe&amp;quot;: [&amp;quot;javascript&amp;quot;, &amp;quot;typescript&amp;quot;, &amp;quot;vue&amp;quot;, &amp;quot;html&amp;quot;],&lt;br /&gt;
  &amp;quot;eslint.validate&amp;quot;: [&amp;quot;javascript&amp;quot;, &amp;quot;typescript&amp;quot;, &amp;quot;vue&amp;quot;, &amp;quot;html&amp;quot;],&lt;br /&gt;
&lt;br /&gt;
create package.json&lt;br /&gt;
 npm init&lt;br /&gt;
install packages&lt;br /&gt;
 npm install --save-dev eslint&lt;br /&gt;
 npm install --save-dev eslint-config-google&lt;br /&gt;
 npm install --save-dev prettier&lt;br /&gt;
 npm install --save-dev eslint-config-prettier&lt;br /&gt;
create .eslintrc.json&lt;br /&gt;
 npm init @eslint/config&lt;br /&gt;
&lt;br /&gt;
.eslint.js&lt;br /&gt;
 module.exports = {&lt;br /&gt;
   env: {&lt;br /&gt;
     browser: true,&lt;br /&gt;
     es2021: true,&lt;br /&gt;
   },&lt;br /&gt;
   extends: [&lt;br /&gt;
     &amp;quot;google&amp;quot;,&lt;br /&gt;
     &amp;quot;plugin:vue/vue3-recommended&amp;quot;,&lt;br /&gt;
     &amp;quot;plugin:vitest/recommended&amp;quot;,&lt;br /&gt;
     &amp;quot;prettier&amp;quot;, // as last one&lt;br /&gt;
   ],&lt;br /&gt;
   plugins: [&amp;quot;vitest&amp;quot;],&lt;br /&gt;
   rules: {&lt;br /&gt;
     // quotes: [&amp;quot;error&amp;quot;, &amp;quot;double&amp;quot;],&lt;br /&gt;
     &amp;quot;vue/valid-attribute-name&amp;quot;: &amp;quot;error&amp;quot;,&lt;br /&gt;
     &amp;quot;vitest/max-nested-describe&amp;quot;: [&lt;br /&gt;
       &amp;quot;error&amp;quot;,&lt;br /&gt;
       {&lt;br /&gt;
         max: 3,&lt;br /&gt;
       },&lt;br /&gt;
     ],&lt;br /&gt;
   },&lt;br /&gt;
 };&lt;br /&gt;
&lt;br /&gt;
.prettierrc&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;arrowParens&amp;quot;: &amp;quot;avoid&amp;quot;,&lt;br /&gt;
   &amp;quot;endOfLine&amp;quot;: &amp;quot;lf&amp;quot;,&lt;br /&gt;
   &amp;quot;htmlWhitespaceSensitivity&amp;quot;: &amp;quot;css&amp;quot;,&lt;br /&gt;
   &amp;quot;printWidth&amp;quot;: 80,&lt;br /&gt;
   &amp;quot;semi&amp;quot;: true,&lt;br /&gt;
   &amp;quot;singleQuote&amp;quot;: false,&lt;br /&gt;
   &amp;quot;tabWidth&amp;quot;: 2,&lt;br /&gt;
   &amp;quot;trailingComma&amp;quot;: &amp;quot;all&amp;quot;,&lt;br /&gt;
   &amp;quot;vueIndentScriptAndStyle&amp;quot;: false&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====LaTeX====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=James-Yu.latex-workshop LaTeX Workshop]&lt;br /&gt;
&lt;br /&gt;
settings.json&lt;br /&gt;
    // LaTeX&lt;br /&gt;
    &amp;quot;latex-workshop.latex.autoBuild.run&amp;quot;: &amp;quot;never&amp;quot;,&lt;br /&gt;
    &amp;quot;settingsSync.ignoredSettings&amp;quot;: [],&lt;br /&gt;
    &amp;quot;[latex]&amp;quot;: {&lt;br /&gt;
        &amp;quot;editor.autoIndent&amp;quot;: &amp;quot;none&amp;quot;,&lt;br /&gt;
        &amp;quot;editor.formatOnSave&amp;quot;: false&lt;br /&gt;
        // &amp;quot;auto-format&amp;quot;: false&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
====Markdown====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one Markdown All in One]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced Markdown Preview Enhanced]&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint Markdown Lint]&lt;br /&gt;
settings.json&lt;br /&gt;
    &amp;quot;[markdown]&amp;quot;: {&lt;br /&gt;
        &amp;quot;editor.formatOnSave&amp;quot;: true,&lt;br /&gt;
        &amp;quot;editor.formatOnPaste&amp;quot;: true,&lt;br /&gt;
        &amp;quot;editor.defaultFormatter&amp;quot;: &amp;quot;DavidAnson.vscode-markdownlint&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
====Markdown Preview Mermaid Support====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items/?itemName=bierner.markdown-mermaid Markdown Preview Mermaid Support]&lt;br /&gt;
&lt;br /&gt;
====Perl====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=sfodje.perltidy perltidy]&lt;br /&gt;
Old:&lt;br /&gt;
* Perl Toolbox (for coding style check) (requires perlcritic]) &lt;br /&gt;
* Perl (requires CPAN [https://metacpan.org/pod/distribution/Perl-Tidy/lib/Perl/Tidy.pod Perl::Tidy] and [https://sourceforge.net/projects/ctags/ ctags]&lt;br /&gt;
* Perl Debug (requires CPAN [https://metacpan.org/pod/PadWalker PadWalker])&lt;br /&gt;
&lt;br /&gt;
Requirements&lt;br /&gt;
* cpan install Perl::Tidy&lt;br /&gt;
* cpan install Perl::Critic&lt;br /&gt;
* [https://sourceforge.net/projects/ctags/ ctags]&lt;br /&gt;
&lt;br /&gt;
settings.json&lt;br /&gt;
    // Perl&lt;br /&gt;
    &amp;quot;perltidy.additionalArguments&amp;quot;: [ // see http://perltidy.sourceforge.net/perltidy.html&lt;br /&gt;
        &amp;quot;--perl-best-practices&amp;quot;, // -pbp abbreviation for the parameters in the book Perl Best Practices by Damian Conway&lt;br /&gt;
        &amp;quot;--blank-lines-before-subs=2&amp;quot;, // -blbs&lt;br /&gt;
        &amp;quot;--closing-side-comments&amp;quot;, // adds &amp;quot;## end sub subname etc&amp;quot;&lt;br /&gt;
        &amp;quot;--indent-columns=2&amp;quot;, // -ic does not work in vis.stutio code , but editor.tabSize does&lt;br /&gt;
        &amp;quot;--noblanks-before-comments&amp;quot;, // -nbbc , opposite: --blanks-before-comments=2 / -bbc&lt;br /&gt;
        // &amp;quot;--maximum-line-length=0&amp;quot;, // -l , 0 = infinite&lt;br /&gt;
        // &amp;quot;--standard-output&amp;quot;, // -so&lt;br /&gt;
        // &amp;quot;--quiet&amp;quot;, // -q Deactivate error messages and syntax checking (for running under an editor)&lt;br /&gt;
        // //&lt;br /&gt;
        // &amp;quot;--add-whitespace&amp;quot;, // -aws add white spaces, use -naws if you do not want any whitespace added, but are willing to have some whitespace deleted&lt;br /&gt;
        // &amp;quot;--brace-tightness=0&amp;quot;, // -bt braces thickness&lt;br /&gt;
        // &amp;quot;--cuddled-else&amp;quot;, // -ce&lt;br /&gt;
        // &amp;quot;--delete-old-whitespace&amp;quot;, // -dws default&lt;br /&gt;
        // &amp;quot;--ignore-side-comment-lengths&amp;quot;, // comments are allowed to be longer than the lines of code&lt;br /&gt;
        // &amp;quot;--keep-old-blank-lines=1&amp;quot;, //-kbl, 0=ignore, 1=stable, 2=keep&lt;br /&gt;
        // &amp;quot;--maximum-consecutive-blank-lines=1&amp;quot;, // -mbl&lt;br /&gt;
        // &amp;quot;--nohanging-side-comments&amp;quot;,&lt;br /&gt;
        // &amp;quot;--opening-brace-always-on-right&amp;quot;, // -bar&lt;br /&gt;
        // &amp;quot;--paren-tightness=0&amp;quot;, // -pt parentheses thickness&lt;br /&gt;
        // &amp;quot;--square-bracket-tightness=0&amp;quot;, // -sbt square brackets thickness&lt;br /&gt;
        // // &amp;quot;--nohanging-side-comments&amp;quot;,&lt;br /&gt;
    ],&lt;br /&gt;
    &amp;quot;perl-toolbox.lint.excludedPolicies&amp;quot;: [&lt;br /&gt;
        &amp;quot;ErrorHandling::RequireCarping&amp;quot;, // die used instead of croak&lt;br /&gt;
        &amp;quot;InputOutput::RequireCheckedSyscalls&amp;quot;, // Lint: BRUTAL: Return value of flagged function ignored - print&lt;br /&gt;
        &amp;quot;NamingConventions::Capitalization&amp;quot;, // var. names all capital or all lower case &lt;br /&gt;
        &amp;quot;ProhibitEmptyQuotes&amp;quot;, // don&#039;t complain if using $s = &lt;br /&gt;
        &amp;quot;ProhibitEnumeratedClasses&amp;quot;, // don&#039;t complain about [a-z]&lt;br /&gt;
        &amp;quot;ProhibitEscapedMetacharacters&amp;quot;, // don&#039;t complain about \d in reg exp / Use character classes for literal metachars instead of escapes&lt;br /&gt;
        &amp;quot;ProhibitMagicNumbers&amp;quot;, // don&#039;t complain $n = 14&lt;br /&gt;
        &amp;quot;ProhibitManyArgs&amp;quot;, // don&#039;t care how many arguments a function has&lt;br /&gt;
        &amp;quot;ProhibitNoisyQuotes&amp;quot;, // don&#039;t complain if using 1 char strings: $s = &#039;a&#039;&lt;br /&gt;
        &amp;quot;ProhibitParensWithBuiltins&amp;quot;, // don&#039;t complain if using () for buildin functions like open&lt;br /&gt;
        &amp;quot;ProhibitPostfixControls&amp;quot;, // don&#039;t complain for $n ++ if &amp;lt;something&amp;gt;&lt;br /&gt;
        &amp;quot;ProhibitPunctuationVars&amp;quot;, // don&#039;t complain if using $!&lt;br /&gt;
        &amp;quot;ProhibitUnusualDelimiters&amp;quot;, // don&#039;t complain about m!^(https?)://!&lt;br /&gt;
        &amp;quot;ProhibitUselessTopic&amp;quot;, // don&#039;t complain about use of $_&lt;br /&gt;
        &amp;quot;RequireDotMatchAnything&amp;quot;, // don&#039;t complain Regular expression without &amp;quot;/s&amp;quot; flag&lt;br /&gt;
        &amp;quot;RequireExplicitPackage&amp;quot;, // don&#039;t require package as first line &lt;br /&gt;
        &amp;quot;RequireExtendedFormatting&amp;quot;, // don&#039;t complain Regular expression without &amp;quot;/x&amp;quot; flag&lt;br /&gt;
        &amp;quot;RequireLineBoundaryMatching&amp;quot;, // don&#039;t complain Regular expression without &amp;quot;/s&amp;quot; flag&lt;br /&gt;
        &amp;quot;RequireNumberSeparators&amp;quot;, // don&#039;t require 141_234_397.0145 instead of 141234397.0145 .&lt;br /&gt;
        &amp;quot;RequireTidyCode&amp;quot;, // don&#039;t complain on first line if not all of Perl::Tidy is met&lt;br /&gt;
        &amp;quot;RequireVersionVar&amp;quot;, // don&#039;t complain on No package-scoped &amp;quot;$VERSION&amp;quot; variable found &lt;br /&gt;
        &amp;quot;ValuesAndExpressions::ProhibitInterpolationOfLiterals&amp;quot;, // check use of &#039; vs &amp;quot;&lt;br /&gt;
    ],&lt;br /&gt;
    // &amp;quot;perl.ctagsPath&amp;quot;: &amp;quot;c:\\Users\\t\\Progs\\ctags\\ctags.exe&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====PHP====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephense-client PHP Intelephense]&lt;br /&gt;
&lt;br /&gt;
settings.json&lt;br /&gt;
    // PHP&lt;br /&gt;
    &amp;quot;intelephense.environment.phpVersion&amp;quot;: &amp;quot;7.4.30&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====Python====&lt;br /&gt;
see [[Python#Editor:_Visual_Studio_Code]]&lt;br /&gt;
&lt;br /&gt;
==== Remote - SSH ====&lt;br /&gt;
* Connecte to remote machine via [https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh Remote - SSH]&lt;br /&gt;
* F1&lt;br /&gt;
* &amp;quot;Remote SSH: Connect to Host&amp;quot;&lt;br /&gt;
* user@server.com&lt;br /&gt;
&lt;br /&gt;
====SQL====&lt;br /&gt;
=====Format SQL=====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=inferrinizzard.prettier-sql-vscode Prettier-SQL]&lt;br /&gt;
   &amp;quot;Prettier-SQL.SQLFlavourOverride&amp;quot;: &amp;quot;postgresql&amp;quot;,&lt;br /&gt;
   &amp;quot;Prettier-SQL.ignoreTabSettings&amp;quot;: true&lt;br /&gt;
&lt;br /&gt;
=====SQLite=====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items/?itemName=alexcvzz.vscode-sqlite SQLite]&lt;br /&gt;
&lt;br /&gt;
====Shell Format====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=foxundermoon.shell-format Shell Format]&lt;br /&gt;
&lt;br /&gt;
====Text Linting====&lt;br /&gt;
* [https://marketplace.visualstudio.com/items?itemName=PatrykPeszko.vscode-proselint Proselint]&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=IOS_iPhone_iPad_Apps&amp;diff=5209</id>
		<title>IOS iPhone iPad Apps</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=IOS_iPhone_iPad_Apps&amp;diff=5209"/>
		<updated>2025-05-02T05:12:08Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Reisen */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Software]][[Category:Apple]]&lt;br /&gt;
===Zocken===&lt;br /&gt;
* [https://apps.apple.com/app/id1465731199 2 Player Games: the Challenge] (2€ to remove ads)&lt;br /&gt;
* [https://apps.apple.com/app/id1635978552 1 2 3 4 Player Games]&lt;br /&gt;
* [https://apps.apple.com/app/id1596736236 Angry Birds Classic]&lt;br /&gt;
* [https://apps.apple.com/app/id1145275343 Super Mario Run]&lt;br /&gt;
* [https://apps.apple.com/app/id1235863443 Civi 6]&lt;br /&gt;
* [https://apps.apple.com/app/id479516143 Minecraft]&lt;br /&gt;
* [https://apps.apple.com/app/id1492005122 Diablo Immortal]&lt;br /&gt;
* [https://apps.apple.com/app/id1289067019 Krabat]&lt;br /&gt;
* [https://apps.apple.com/app/id1291427111 Evoland 2]&lt;br /&gt;
* [https://apps.apple.com/app/id1309032833 Zombie Defence]&lt;br /&gt;
* [https://apps.apple.com/app/id1476593876 Table Top Racing]&lt;br /&gt;
* [https://apps.apple.com/app/id1406710800 Stardew Valley]&lt;br /&gt;
* [https://apps.apple.com/app/id1496786244 BER Bausimulator (Postillion)]&lt;br /&gt;
* [https://apps.apple.com/app/id808296431 Crashlands]&lt;br /&gt;
* [https://apps.apple.com/app/id979917622 Desktop Dungeons]&lt;br /&gt;
* [https://apps.apple.com/app/id552039496 The Room 1]&lt;br /&gt;
* [https://apps.apple.com/app/id1496354836 Woodoku]&lt;br /&gt;
&lt;br /&gt;
===Apps===&lt;br /&gt;
* [https://apps.apple.com/app/id986420993 WarnWetter]&lt;br /&gt;
* [https://apps.apple.com/app/id714464092 Windows Remote Desktop Client]&lt;br /&gt;
* [https://apps.apple.com/app/id1435127111 KeePassium]&lt;br /&gt;
===Camera===&lt;br /&gt;
* [https://apps.apple.com/app/id975925059 MS Lens]&lt;br /&gt;
* [https://apps.apple.com/app/id489473238 power Barcode Scanner]&lt;br /&gt;
===Orga===&lt;br /&gt;
* [https://apps.apple.com/app/id1029207872 Google Keep / Notizen]&lt;br /&gt;
* [https://apps.apple.com/app/id293561396 Remember the milk]&lt;br /&gt;
* [https://apps.apple.com/app/id1477376905 GitHub]&lt;br /&gt;
* [https://apps.apple.com/app/id963034692 Streaks]&lt;br /&gt;
===Natur===&lt;br /&gt;
* [https://apps.apple.com/app/id1297860122 Flora Incognita]&lt;br /&gt;
* [https://apps.apple.com/app/id773457673 Merlin Bird ID by Cornell Lab]&lt;br /&gt;
&lt;br /&gt;
===Reisen===&lt;br /&gt;
* [https://apps.apple.com/app/id585027354 Google Maps]&lt;br /&gt;
* [https://apps.apple.com/app/id343555245 DB Navigator]&lt;br /&gt;
* [https://apps.apple.com/app/id1458716890 Stellarium]&lt;br /&gt;
* [https://apps.apple.com/app/id430946556 Park4Night]&lt;br /&gt;
* OpenStreetmap (OSM) Contributing&lt;br /&gt;
** [https://apps.apple.com/app/id1621945342 Every-Door]&lt;br /&gt;
** [https://apps.apple.com/app/id592990211 Go Map!!]&lt;br /&gt;
&lt;br /&gt;
===Sport===&lt;br /&gt;
* [https://apps.apple.com/app/id503519713 Zombies Run]&lt;br /&gt;
* [https://apps.apple.com/app/id447374873 Komoot]&lt;br /&gt;
* [https://apps.apple.com/app/id1255964203 Tabata Timer]&lt;br /&gt;
* [https://apps.apple.com/app/id1328791384 Useful knots]&lt;br /&gt;
===Musik===&lt;br /&gt;
* [https://apps.apple.com/app/id343555245 Deezer]&lt;br /&gt;
* [https://apps.apple.com/app/id1482898319 Deezer Audiobooks]&lt;br /&gt;
* [https://apps.apple.com/app/id324684580 Spotify]&lt;br /&gt;
===Kinder===&lt;br /&gt;
* [https://apps.apple.com/app/id1180554775 Anton]&lt;br /&gt;
* [https://apps.apple.com/app/id1209287132 Mathematische Tricks]&lt;br /&gt;
* [https://apps.apple.com/app/id1486159728 Lego Anleitungen]&lt;br /&gt;
===Lesen===&lt;br /&gt;
* [https://apps.apple.com/app/id324715238 Wikipedia]&lt;br /&gt;
* [https://apps.apple.com/app/id997079563 Kiwix Wilipedia Offline]&lt;br /&gt;
* [https://apps.apple.com/app/id944944881 Tolino]&lt;br /&gt;
* [https://apps.apple.com/app/id422554835 Onleihe]&lt;br /&gt;
===Schauen===&lt;br /&gt;
* [https://apps.apple.com/app/id544007664 YouTube]&lt;br /&gt;
* [https://apps.apple.com/app/id650377962 VLC]&lt;br /&gt;
* [https://apps.apple.com/app/id981496660 ARD Mediathek]&lt;br /&gt;
* [https://apps.apple.com/app/id437025413 ZDF Mediathek]&lt;br /&gt;
* [https://apps.apple.com/app/id932310976 die Maus]&lt;br /&gt;
* [https://apps.apple.com/app/id1023688500 der Elefant]&lt;br /&gt;
* [https://apps.apple.com/app/id342792525 IMDB]&lt;br /&gt;
* [https://apps.apple.com/app/id545519333 Amazon Prime Video]&lt;br /&gt;
===Office===&lt;br /&gt;
* [https://apps.apple.com/app/id541164041 MS Office]&lt;br /&gt;
* [https://apps.apple.com/app/id842842640 Google Docs]&lt;br /&gt;
* [https://apps.apple.com/app/id842849113 Goodle Sheets/Tabellen]&lt;br /&gt;
* [https://apps.apple.com/app/id414706506 Google Translator]&lt;br /&gt;
===Shopping===&lt;br /&gt;
* [https://apps.apple.com/app/id382596778 Kleinanzeigen]&lt;br /&gt;
===Chat===&lt;br /&gt;
* [https://apps.apple.com/app/id874139669 Signal]&lt;br /&gt;
* [https://apps.apple.com/app/id686449807 Telegram]&lt;br /&gt;
* [https://apps.apple.com/app/id578665578 Threema]&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Wiki_Maintainance&amp;diff=5208</id>
		<title>Wiki Maintainance</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Wiki_Maintainance&amp;diff=5208"/>
		<updated>2025-05-01T04:57:13Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* How to upgrade this wiki to latest version */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Webserver]]&lt;br /&gt;
===How to upgrade this wiki to latest version===&lt;br /&gt;
* cleanup DB (see below)&lt;br /&gt;
* backup DB&lt;br /&gt;
* download latest LTS [https://www.mediawiki.org/wiki/Download version]&lt;br /&gt;
* extract next to old version (new dir, do not overwrite)&lt;br /&gt;
* copy LocalSettings.php&lt;br /&gt;
* copy pix/ dir for logo&lt;br /&gt;
* download the custom extensions used in LocalSettings.php&lt;br /&gt;
** [https://www.mediawiki.org/wiki/Extension:Matomo Matomo]&lt;br /&gt;
** [https://www.mediawiki.org/wiki/Special:ExtensionDistributor/HitCounters HitCounters]&lt;br /&gt;
** [https://www.mediawiki.org/wiki/Special:ExtensionDistributor/NewestPages NewestPages]&lt;br /&gt;
* as long as there are no file attachments in here, that part can be skipped&lt;br /&gt;
* adjust wickie symlink&lt;br /&gt;
* restart of php might be required via: uberspace tools restart php&lt;br /&gt;
&lt;br /&gt;
===Delete old page revisions/versions/history===&lt;br /&gt;
See [https://www.mediawiki.org/wiki/Manual:DeleteOldRevisions.php]&lt;br /&gt;
 cd maintenance&lt;br /&gt;
 php deleteOldRevisions.php --delete&lt;br /&gt;
 php deleteArchivedRevisions.php --delete&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=JavaScript&amp;diff=5191</id>
		<title>JavaScript</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=JavaScript&amp;diff=5191"/>
		<updated>2025-03-24T06:00:56Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* ignore Test Coverage for missing else etc */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Coding]]&lt;br /&gt;
===Basics===&lt;br /&gt;
Load after html loading via defer&lt;br /&gt;
 &amp;lt;script src=&amp;quot;main.js&amp;quot; defer&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====HTML Form Elements====&lt;br /&gt;
Function of button click&lt;br /&gt;
 &amp;lt;button id=&amp;quot;submit&amp;quot; onclick=&amp;quot;myFunction()&amp;quot;&amp;gt;Anmelden&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Function on enter&lt;br /&gt;
 &amp;lt;input type=&amp;quot;number&amp;quot; id=&amp;quot;threshold&amp;quot; name=&amp;quot;threshold&amp;quot; min=&amp;quot;50&amp;quot; max=&amp;quot;500&amp;quot; step=&amp;quot;1&amp;quot; value=&amp;quot;300&amp;quot;&amp;gt;&lt;br /&gt;
 var input_threshold = document.getElementById(&amp;quot;threshold&amp;quot;);&lt;br /&gt;
     input_threshold.addEventListener(&amp;quot;keydown&amp;quot;, function (e) {&lt;br /&gt;
       // Enter is pressed&lt;br /&gt;
       if (e.keyCode === 13) {&lt;br /&gt;
         event.preventDefault();&lt;br /&gt;
         myFunction();&lt;br /&gt;
       }&lt;br /&gt;
     }, false); &lt;br /&gt;
&lt;br /&gt;
===Variables===&lt;br /&gt;
Declaration&lt;br /&gt;
 const var = 123; // constant: no change possible&lt;br /&gt;
 let var = 123;   // block-bound, perferred to var&lt;br /&gt;
 var var = 123;   // old style, better use let instead&lt;br /&gt;
&lt;br /&gt;
Clone/Copy Object&lt;br /&gt;
 // from https://www.samanthaming.com/tidbits/70-3-ways-to-clone-objects/&lt;br /&gt;
 const myObject2 = Object.assign({}, myObject);&lt;br /&gt;
&lt;br /&gt;
====Arrays====&lt;br /&gt;
Loop over all elements&lt;br /&gt;
 var rows = table.getRows();&lt;br /&gt;
 for (var i = 0; i &amp;lt; rows.length; i++) {&lt;br /&gt;
     var row = rows[i];&lt;br /&gt;
     ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Loop over array of objects&lt;br /&gt;
 for (let i = 0; i &amp;lt; Object.keys(array).length; i++) {&lt;br /&gt;
   const key = Object.keys(array)[i];&lt;br /&gt;
   const value = array[key];&lt;br /&gt;
 }&lt;br /&gt;
 NOTE: do not use&lt;br /&gt;
 for (const key in array) { ... }&lt;br /&gt;
 as this results in this problem&lt;br /&gt;
 &amp;quot;The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Element in Array&lt;br /&gt;
 if(myArray.indexOf(myItem) &amp;gt; -1) {...}&lt;br /&gt;
&lt;br /&gt;
Last element of array&lt;br /&gt;
 my_data.slice(-1)[0]&lt;br /&gt;
or&lt;br /&gt;
 my_data.slice(-1)[0][&amp;quot;myKey&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
=====Array of Objects=====&lt;br /&gt;
delete some object properties&lt;br /&gt;
 // delete not needed object properties&lt;br /&gt;
 const allowedKeys = new Set([&amp;quot;type&amp;quot;, &amp;quot;name&amp;quot;, &amp;quot;x_date&amp;quot;, &amp;quot;x_url&amp;quot;, ...Object.keys(measures)]);&lt;br /&gt;
 data.forEach((obj) =&amp;gt; {&lt;br /&gt;
   Object.entries(obj).forEach(([key]) =&amp;gt; {&lt;br /&gt;
     if (!allowedKeys.has(key)) {&lt;br /&gt;
       delete obj[key];&lt;br /&gt;
     }&lt;br /&gt;
   });&lt;br /&gt;
   // extract year from x_date and add as property&lt;br /&gt;
   obj.year = parseInt(obj.x_date.substr(0, 4));&lt;br /&gt;
 });&lt;br /&gt;
&lt;br /&gt;
Extract distinct list/set list for property &#039;type&#039; values&lt;br /&gt;
 const act_types = [...new Set(data.map((obj) =&amp;gt; obj.type))].sort();&lt;br /&gt;
&lt;br /&gt;
Sort by object property &amp;quot;measure&amp;quot; DESC&lt;br /&gt;
 data = data.sort((a, b) =&amp;gt; b[measure] - a[measure]);&lt;br /&gt;
&lt;br /&gt;
===Async fetching via JQuery===&lt;br /&gt;
in HTML&lt;br /&gt;
 &amp;lt;script src=&amp;quot;lib/jquery-3.5.0.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
in JS&lt;br /&gt;
 // array of promises for async fetching&lt;br /&gt;
 const promises = [];&lt;br /&gt;
 &lt;br /&gt;
 // ref dictionary to be fetched: Country Code -&amp;gt; Country Name&lt;br /&gt;
 var mapCountryNames = {};&lt;br /&gt;
 &lt;br /&gt;
 // fetch countries-latest-all.json containing country reference data like code and continent&lt;br /&gt;
 function fetch_mapRefCountryData(mapCountryNames) {&lt;br /&gt;
     const url =&lt;br /&gt;
         &amp;quot;https://entorb.net/COVID-19-coronavirus/data/int/countries-latest-all.json&amp;quot;;&lt;br /&gt;
     return $.getJSON(url, function (data) {&lt;br /&gt;
         console.log(&amp;quot;success: mapCountryNames&amp;quot;);&lt;br /&gt;
     })&lt;br /&gt;
         .done(function (data) {&lt;br /&gt;
             console.log(&amp;quot;done: mapCountryNames&amp;quot;);&lt;br /&gt;
             $.each(data, function (key, val) {&lt;br /&gt;
                 mapCountryNames[data[key].Code] = data[key].Country;&lt;br /&gt;
             });&lt;br /&gt;
         })&lt;br /&gt;
         .fail(function () {&lt;br /&gt;
             console.log(&amp;quot;fail: mapCountryNames&amp;quot;);&lt;br /&gt;
         });&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 // Start the async fetching &lt;br /&gt;
 promises.push(fetch_mapRefCountryData(mapCountryNames, mapContinentCountries));&lt;br /&gt;
 &lt;br /&gt;
 // Wait for all async promises to be done (all data is fetched), then print message&lt;br /&gt;
 Promise.all(promises).then(function () {&lt;br /&gt;
     console.log(&amp;quot;All data fetched&amp;quot;);&lt;br /&gt;
 });&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Date handling==&lt;br /&gt;
 // calculate date via offset&lt;br /&gt;
 const daysOffset = 7;&lt;br /&gt;
 const s_data_last_date = &amp;quot;2020-04-01&amp;quot;&lt;br /&gt;
 const ts_last_date = Date.parse(s_data_last_date)&lt;br /&gt;
 var minDate = new Date(ts_last_date);&lt;br /&gt;
 minDate.setDate(minDate.getDate() + daysOffset);&lt;br /&gt;
&lt;br /&gt;
==Helper Functions==&lt;br /&gt;
from [https://love2dev.com/blog/javascript-remove-from-array/&lt;br /&gt;
 function arrayRemove(arr, value) {&lt;br /&gt;
   return arr.filter(function (ele) { return ele != value; });&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
from [https://stackoverflow.com/posts/3364546/timeline]&lt;br /&gt;
 function removeAllOptionsFromSelect(select) {&lt;br /&gt;
   var i, L = select.options.length - 1;&lt;br /&gt;
   for (i = L; i &amp;gt;= 0; i--) {&lt;br /&gt;
     select.remove(i);&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 // Formats value &amp;quot;Something_Is_HERE&amp;quot; to &amp;quot;Something is here&amp;quot; like sentence&lt;br /&gt;
 // value: The value to format&lt;br /&gt;
 // separator: the separator string between words&lt;br /&gt;
 function formatValueToSentenceLike(value, separator) {&lt;br /&gt;
   const allLowerCaseValue = value.split(separator).join(&amp;quot; &amp;quot;).toLowerCase();&lt;br /&gt;
   return allLowerCaseValue[0].toUpperCase() + allLowerCaseValue.substr(1);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 // modifies array of objects by removing if value == keys&lt;br /&gt;
 function arrayRemoveValueTextPairByValue(arr, key) {&lt;br /&gt;
   for (let i = arr.length - 1; i &amp;gt;= 0; i--) {&lt;br /&gt;
     if (arr[i].value == key) { arr.splice(i, 1); }&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Local Storage==&lt;br /&gt;
 // read browser&#039;s local storage for last session data&lt;br /&gt;
 localStorageData = window.localStorage.getItem(&amp;quot;my_data&amp;quot;);&lt;br /&gt;
 if (localStorageData) {&lt;br /&gt;
     var my_data = JSON.parse(localStorageData);&lt;br /&gt;
 } else {&lt;br /&gt;
     var my_data = [];&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Export and Import / Download and Upload data to Variable==&lt;br /&gt;
HTML&lt;br /&gt;
 &amp;lt;a id=&amp;quot;downloadAnchor&amp;quot; style=&amp;quot;display:none&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;
 &amp;lt;button id=&amp;quot;download_data&amp;quot; onclick=&amp;quot;download_data()&amp;quot;&amp;gt;Export&amp;lt;/button&amp;gt; and Import: &amp;lt;input id=&amp;quot;upload_data&amp;quot; type=&amp;quot;file&amp;quot; onchange=&amp;quot;upload_data(this)&amp;quot;&amp;gt;&lt;br /&gt;
JS&lt;br /&gt;
 // download data&lt;br /&gt;
 function download_data() {&lt;br /&gt;
     const dataStr = &amp;quot;data:text/json;charset=utf-8,&amp;quot; + encodeURIComponent(JSON.stringify([settings, data]));&lt;br /&gt;
     let html_dl_anchor = document.getElementById(&amp;quot;downloadAnchor&amp;quot;);&lt;br /&gt;
     html_dl_anchor.setAttribute(&amp;quot;href&amp;quot;, dataStr);&lt;br /&gt;
     html_dl_anchor.setAttribute(&amp;quot;download&amp;quot;, &amp;quot;eta.json&amp;quot;);&lt;br /&gt;
     html_dl_anchor.click();&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 // upload data&lt;br /&gt;
 function upload_data(input) {&lt;br /&gt;
     // from https://javascript.info/file&lt;br /&gt;
     let file = input.files[0];&lt;br /&gt;
     let reader = new FileReader();&lt;br /&gt;
     reader.readAsText(file);&lt;br /&gt;
     reader.onload = function () {&lt;br /&gt;
         const uploaded_data = JSON.parse(reader.result);&lt;br /&gt;
         settings = uploaded_data[0];&lt;br /&gt;
         data = uploaded_data[1];&lt;br /&gt;
     };&lt;br /&gt;
     reader.onerror = function () {&lt;br /&gt;
         console.log(reader.error);&lt;br /&gt;
     };&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Hide/remove HTML elements==&lt;br /&gt;
HTML&lt;br /&gt;
 &amp;lt; div id=&amp;quot;text_intro&amp;quot; &amp;gt;...&amp;lt; /div &amp;gt;&lt;br /&gt;
JS&lt;br /&gt;
 function hide_intro() {&lt;br /&gt;
     // from https://stackoverflow.com/questions/1070760/javascript-href-vs-onclick-for-callback-function-on-hyperlink&lt;br /&gt;
     const html_text_intro = document.getElementById(&#039;text_intro&#039;);&lt;br /&gt;
     html_text_intro.remove();&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Internet Explorer Backward Compatibility==&lt;br /&gt;
====Error: Object doesn&#039;t support property or method &#039;includes&#039;====&lt;br /&gt;
variant 1:&lt;br /&gt;
&lt;br /&gt;
replace:&lt;br /&gt;
 if(myarray.includes(key)) {&lt;br /&gt;
by:&lt;br /&gt;
 if(myarray.indexOf(key) &amp;gt; -1) {&lt;br /&gt;
&lt;br /&gt;
variant 2: polyfile&lt;br /&gt;
 if (!String.prototype.includes) {&lt;br /&gt;
   String.prototype.includes = function(search, start) {&lt;br /&gt;
     &#039;use strict&#039;;&lt;br /&gt;
 &lt;br /&gt;
     if (search instanceof RegExp) {&lt;br /&gt;
       throw TypeError(&#039;first argument must not be a RegExp&#039;);&lt;br /&gt;
     } &lt;br /&gt;
     if (start === undefined) { start = 0; }&lt;br /&gt;
     return this.indexOf(search, start) !== -1;&lt;br /&gt;
   };&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Tabulator==&lt;br /&gt;
 // wait for tableBuilt event&lt;br /&gt;
 table.on(&amp;quot;tableBuilt&amp;quot;, function () {&lt;br /&gt;
     myUpdateTableFnc();&lt;br /&gt;
 });&lt;br /&gt;
&lt;br /&gt;
==eCharts==&lt;br /&gt;
&lt;br /&gt;
==Jest Unittests==&lt;br /&gt;
install jest and jsdom for testing browser features like window.localStorage&lt;br /&gt;
 npm install --save-dev jest jest-environment-jsdom&lt;br /&gt;
in package.json set&lt;br /&gt;
 &amp;quot;scripts&amp;quot;: {&amp;quot;test&amp;quot;: &amp;quot;jest --coverage&amp;quot;,&amp;quot;testc&amp;quot;: &amp;quot;jest --coverage&amp;quot;},&lt;br /&gt;
 &amp;quot;jest&amp;quot;: { &amp;quot;testEnvironment&amp;quot;: &amp;quot;jsdom&amp;quot; }&lt;br /&gt;
run jest&lt;br /&gt;
 npm test&lt;br /&gt;
 npm run testc&lt;br /&gt;
&lt;br /&gt;
In the file I want to test here `helper.js`&lt;br /&gt;
 var module = module || {};&lt;br /&gt;
 module.exports = {&lt;br /&gt;
   zeroPad,&lt;br /&gt;
 ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The testcasefile `helper.test.js`&lt;br /&gt;
 describe(&amp;quot;Testing zeroPad&amp;quot;, () =&amp;gt; {&lt;br /&gt;
  const { zeroPad } = require(&amp;quot;./helper&amp;quot;);&lt;br /&gt;
   test(&amp;quot;1-&amp;gt;01&amp;quot;, () =&amp;gt; {&lt;br /&gt;
     expect(zeroPad(1, 2)).toEqual(&amp;quot;01&amp;quot;);&lt;br /&gt;
   });&lt;br /&gt;
 });&lt;br /&gt;
&lt;br /&gt;
===test.each===&lt;br /&gt;
To test many cases use&lt;br /&gt;
 describe(&amp;quot;zeroPad()&amp;quot;, () =&amp;gt; {&lt;br /&gt;
   const { zeroPad } = require(&amp;quot;./helper&amp;quot;);&lt;br /&gt;
   const cases = [&lt;br /&gt;
     // arg1, arg2, expectedResult&lt;br /&gt;
     [1, 2, &amp;quot;01&amp;quot;],&lt;br /&gt;
     [6, 3, &amp;quot;006&amp;quot;],&lt;br /&gt;
   ];&lt;br /&gt;
   test.each(cases)(&lt;br /&gt;
     &amp;quot;given &#039;%p&#039;, &#039;%p&#039; it shall return %p&amp;quot;,&lt;br /&gt;
     (arg1, arg2, expectedResult) =&amp;gt; {&lt;br /&gt;
       expect(zeroPad(arg1, arg2)).toEqual(expectedResult);&lt;br /&gt;
     }&lt;br /&gt;
   );&lt;br /&gt;
 });&lt;br /&gt;
&lt;br /&gt;
===ignore Test Coverage for missing else etc===&lt;br /&gt;
place such a comment before the non-relevant line&lt;br /&gt;
 /* istanbul ignore else */&lt;br /&gt;
 /* istanbul ignore next */&lt;br /&gt;
&lt;br /&gt;
==Translations / i18n==&lt;br /&gt;
 const supportedLanguages = [&#039;de&#039;, &#039;en&#039;, &#039;fr&#039;]&lt;br /&gt;
 const langBrowser = navigator.language.slice(0, 2)&lt;br /&gt;
 const lang = supportedLanguages.includes(langBrowser) ? langBrowser : &#039;en&#039;&lt;br /&gt;
 const messages = await import(`./locales/${lang}.json`).catch(() =&amp;gt; import(&#039;./locales/en.&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Video_Encoding&amp;diff=5181</id>
		<title>Video Encoding</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Video_Encoding&amp;diff=5181"/>
		<updated>2025-03-23T06:10:45Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Software]]&lt;br /&gt;
===2024===&lt;br /&gt;
see [[Video shrinking]] for Handbrake&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Nice Linux tools===&lt;br /&gt;
* Avidemux (simple cutting+converting) &lt;br /&gt;
* Kdenlive (joining multiple video/audio tracks)&lt;br /&gt;
* WinFF frontend for the encoder FFmpeg&lt;br /&gt;
* gkt-recordmydesktop (videoformat=ogv ; problems recording an applications sound output)&lt;br /&gt;
&lt;br /&gt;
====ffmpeg====&lt;br /&gt;
=====convert gif to mp3=====&lt;br /&gt;
from [https://unix.stackexchange.com/questions/40638/how-to-do-i-convert-an-animated-gif-to-an-mp4-or-mv4-on-the-command-line]&lt;br /&gt;
 ffmpeg -y -i myanimation.gif -movflags faststart -pix_fmt yuv420p -vf &amp;quot;scale=trunc(iw/2)*2:trunc(ih/2)*2&amp;quot; myvideo.mp4&lt;br /&gt;
&lt;br /&gt;
===H.264 codec===&lt;br /&gt;
you need this packages&lt;br /&gt;
 libavcodec-unstripped-52&lt;br /&gt;
 libavdevice-unstripped-52&lt;br /&gt;
 libavfilter-unstripped-0&lt;br /&gt;
 libavformat-unstripped-52&lt;br /&gt;
 libavutil-unstripped-49&lt;br /&gt;
 libpostproc-unstripped-51&lt;br /&gt;
 libswscale-unstripped-0&lt;br /&gt;
(or newer versions)&lt;br /&gt;
&lt;br /&gt;
===ogv -&amp;gt; H.264===&lt;br /&gt;
1. Win FF is a frontend for FFmpeg&lt;br /&gt;
useful parameters:&lt;br /&gt;
 -f mp4 -vcodec libx264 -acodec libfaac -ac 2&lt;br /&gt;
&lt;br /&gt;
2. ffmpeg by hand (bitrate: -b)&lt;br /&gt;
 ffmpeg -i input.avi -f mp4 -vcodec libx264 -acodec libfaac -ac 2 -b 1000k output.mp4&lt;br /&gt;
&lt;br /&gt;
3. mencoder input.ogm -ovc xvid -oac mp3lame -xvidencopts pass=1 -o output.avi &lt;br /&gt;
(without -oac mp3lame for no sound)&lt;br /&gt;
&lt;br /&gt;
===Kdenlive===&lt;br /&gt;
&lt;br /&gt;
====no h.264 output====&lt;br /&gt;
[http://ubuntuforums.org/showthread.php?t=1280510]&lt;br /&gt;
to overcome this problem, one can add other rendering profiles:&lt;br /&gt;
 1) click on one of the predefined rendering profiles in the H.264 group&lt;br /&gt;
 2) click the &#039;Create new profile&#039; button&lt;br /&gt;
 3) Give it a name, and replace acodec=libfaac to acodec=libmp3lame&lt;br /&gt;
 This will then render with mp3+h264&lt;br /&gt;
&lt;br /&gt;
====Making a screengrab using Kdenlive====&lt;br /&gt;
[http://www.kdenlive.org/contribution-manual/howto-make-video-tutorial]&lt;br /&gt;
&lt;br /&gt;
gtk-recordmydesktop can be used to record a desktop video in ogv format.&lt;br /&gt;
&lt;br /&gt;
in order to edit this usind kdenlive create a custom profile: (Settings -&amp;gt; Manage Project Profiles)&lt;br /&gt;
set the parameters to the ones of the video&lt;br /&gt;
Use this new Profile for your Project.&lt;br /&gt;
&lt;br /&gt;
====Audio to Mono====&lt;br /&gt;
 ac=1 for mono&lt;br /&gt;
 ab=64k&lt;br /&gt;
 ar=44100&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Children_of_Morta&amp;diff=5177</id>
		<title>Children of Morta</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Children_of_Morta&amp;diff=5177"/>
		<updated>2025-03-16T06:14:23Z</updated>

		<summary type="html">&lt;p&gt;Torben: Created page with &amp;quot;Category:Zocken  [https://store.steampowered.com/app/330020/Children_of_Morta/ Children of Morta on Steam]   ==Mods== ===4 Player=== https://www.nexusmods.com/childrenofmorta/mods/2&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Zocken]]&lt;br /&gt;
&lt;br /&gt;
[https://store.steampowered.com/app/330020/Children_of_Morta/ Children of Morta on Steam] &lt;br /&gt;
&lt;br /&gt;
==Mods==&lt;br /&gt;
===4 Player===&lt;br /&gt;
https://www.nexusmods.com/childrenofmorta/mods/2&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=HPMOR&amp;diff=5174</id>
		<title>HPMOR</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=HPMOR&amp;diff=5174"/>
		<updated>2025-03-04T18:23:49Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Translations */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Harry Potter and the Methods of Rationality (HPMOR) =&lt;br /&gt;
Link collection to Eliezer Yudkowsky&#039;s great fanfiction&lt;br /&gt;
&lt;br /&gt;
== Original Story ==&lt;br /&gt;
* [https://www.hpmor.com/ Official website of &#039;Harry Potter and the Methods of Rationality&#039;]&lt;br /&gt;
* [https://github.com/rrthomas/hpmor/releases/latest/ Revised PDF and eBook (epub and mobi) versions of HPMOR]&lt;br /&gt;
* [https://hpmorpodcast.com/ Audiobook/Podcast by Eneasz Brodski] - [https://podcasts.apple.com/de/podcast/podcast-the-methods-of-rationality-podcast/id431784580?i=1000473946812 at Apple] - [https://podcasts.google.com/feed/aHR0cHM6Ly9mZWVkcy5mZWVkYnVybmVyLmNvbS9IYXJyeVBvdHRlckFuZFRoZU1ldGhvZHNPZlJhdGlvbmFsaXR5VGhlUG9kY2FzdA/episode/aHR0cHM6Ly9ocG1vcnBvZGNhc3QuY29tLz9wPTYw?sa=X&amp;amp;ved=0CAUQkfYCahcKEwjA44jfuO34AhUAAAAAHQAAAAAQAQ at Google] - [https://open.spotify.com/episode/3up7VkwoJUTNMwaM6j6qvs?si=Gx3LsHH9SY-v5TOYJHrUxw&amp;amp;dl_branch=1 at Spotify] (complete)&lt;br /&gt;
* [https://voraces.podbean.com/ Audiobook by Jack Voraces] - [https://podcasts.apple.com/gb/podcast/harry-potter-and-the-methods-of-rationality-audiobook/id1465181848 at Apple] - [https://podcasts.google.com/feed/aHR0cHM6Ly9mZWVkLnBvZGJlYW4uY29tL3ZvcmFjZXMvZmVlZC54bWw at Google] - [https://open.spotify.com/show/4MCs8UYpoBlNp4aRfzB3a5 at Spotify] - [https://www.amazon.com/Harry-Potter-Methods-Rationality/dp/B08JJSF4B3 at Audible] (complete)&lt;br /&gt;
&lt;br /&gt;
== Translations ==&lt;br /&gt;
* List of translations, see [http://www.hpmor.com/info/ hpmor.com] and [https://en.wikipedia.org/wiki/Harry_Potter_and_the_Methods_of_Rationality#Translations Wikipedia]&lt;br /&gt;
* [https://github.com/entorb/hpmor-de/releases/tag/WorkInProgress German DE translation (Deutsche Übersetzung) of HPMOR in PDF and eBook (epub and mobi)]&lt;br /&gt;
* [https://github.com/kamaradclimber/hpmor/releases/latest French translation (traduction française) of HPMOR as PDF and eBook (epub)]&lt;br /&gt;
* [https://hpmoraudio.de/ DE Audiobook/Podcast by Tilmann Glücks] - [https://podcasts.apple.com/us/podcast/harry-potter-und-die-methoden-des-rationalismus-der-podcast/id1630675041 at Apple] - [https://podcasts.google.com/feed/aHR0cHM6Ly9ocG1vcmF1ZGlvLmRlLz9mZWVkPXBvZGNhc3Q at Google] - [https://open.spotify.com/show/4XmiF2dq0DMVxLyVwQJOam at Spotify] (in progress)&lt;br /&gt;
* [https://www.youtube.com/watch?v=h32Ht-HUbL0&amp;amp;list=PLfgJSXz3-j3aYhWyR3Q5JzcI3h_eibPls DE Audiobook by Tralex HPMOR at YouTube] (in progress)&lt;br /&gt;
&lt;br /&gt;
== My Contributions ==&lt;br /&gt;
* Typesetting [https://github.com/entorb/hpmor-de German PDF and eBook of HPMOR] based on 4 German translations&lt;br /&gt;
* Improvements to the [https://github.com/rrthomas/hpmor English PDF] and created eBook&lt;br /&gt;
&lt;br /&gt;
== Follow-up stuff ==&lt;br /&gt;
Only proceed after reading/listening to the story, as these contain spoilers&lt;br /&gt;
&lt;br /&gt;
* [https://youtube.com/watch?v=aT_q646lJVo YouTube summary]&lt;br /&gt;
* [https://www.fanfiction.net/s/11293489/1/A-Crack-Slash-Epilogue Epiloque: A Crack Slash Epilogue by Alexander Wales]&lt;br /&gt;
* [http://www.anarchyishyperbole.com/p/significant-digits.html Sequel: Significant Digits by Alexander Davis]&lt;br /&gt;
* [https://askwhocastsai.substack.com/s/significant-digits-audiobook AI read audiobook of Significant Digits] [https://podcasts.apple.com/us/podcast/significant-digits-audiobook/id1777030523 at Apple] [https://open.spotify.com/show/1KRTpm0h4ADeoNlCEYpnZI Spotify] (in progress)&lt;br /&gt;
* [https://m.fanfiction.net/s/10636246/1/Following-the-Phoenix Alternative end: Following the Phoenix by hezzel, spinning off at chapter 81]&lt;br /&gt;
&lt;br /&gt;
== Supplementary ==&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Harry_Potter_and_the_Methods_of_Rationality Wikipedia]&lt;br /&gt;
* [https://de.wikipedia.org/wiki/Harry_Potter_and_the_Methods_of_Rationality Wikipedia DE]&lt;br /&gt;
* [https://www.reddit.com/r/HPMOR/ Subreddit r/HPMOR]&lt;br /&gt;
* [https://hpmor.fandom.com/wiki/Harry_Potter_and_the_Methods_of_Rationality_Wiki Wiki at Fandom]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Feel free to drop me a [https://entorb.net/contact.php?origin=HPMOR message], have fun, &#039;&#039;Torben&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Backup&amp;diff=5162</id>
		<title>Backup</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Backup&amp;diff=5162"/>
		<updated>2025-01-06T18:38:53Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* rsync */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Software]][[Category:Linux]][[Category:Windows]]&lt;br /&gt;
KEIN BACKUP - KEIN MITLEID&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
As harddisks are known to break from time to time and re-setting up an old measurement pc is quite some pain in the ass, doing a backup onto an external USB harddisk (40-100€) might be worth thinking about...&lt;br /&gt;
&lt;br /&gt;
Basically we have to distinguish between two types of backups:&lt;br /&gt;
# Frequently changed data (your personal data)&lt;br /&gt;
# Full image of the PC (important for measurement PCs), that includes all needed drivers, libraries, etc and allows restoring of the PC in no time&lt;br /&gt;
The first one should be done very frequently (daily/weekly?), the second one once a year or whenever some greater changes to the PC are done.&lt;br /&gt;
&lt;br /&gt;
Concerning the USB harddisk I prefere small 2.5&amp;quot; ones that work without a separate power supply.&lt;br /&gt;
&lt;br /&gt;
Here I wrote a little guide that might help you. &amp;lt;br&amp;gt;&lt;br /&gt;
Happy backing up Torben (18.02.2010)&lt;br /&gt;
&lt;br /&gt;
==Backup of a single folder==&lt;br /&gt;
&lt;br /&gt;
===Windows===&lt;br /&gt;
====Directory Compare====&lt;br /&gt;
I like the free tool [http://tp.lc.ehu.es/jma/win32/dirco.html Directory Compare] and will now explain you how I use it to backup a single folder like My Documents (De: Eigene Dateien)&lt;br /&gt;
&lt;br /&gt;
* First get the tool. I prefer the .zip file version, since I see no need in installing it.&lt;br /&gt;
* Unpack the zip-file or install the tool into some folder (remember this folder!)&lt;br /&gt;
* Run the tool by starting &amp;quot;DirCmp.exe&amp;quot;&lt;br /&gt;
* Select the folder you want to backup as &amp;quot;Source&amp;quot; and the one you want to copy it to as &amp;quot;Target&amp;quot; &amp;lt;br&amp;gt;(e.g. your external harddisk/networkdrive etc, but keep in mind that the drive has to be connected using the same driveletter before stating the backup...)&lt;br /&gt;
* Click on &amp;quot;Scan&amp;quot; as a test&lt;br /&gt;
* Under &amp;quot;Options-&amp;gt;Configure-&amp;gt;Unattended mode&amp;quot; check &amp;quot;use direct API calls...&amp;quot; to prevent the tool to produce message windows&lt;br /&gt;
* Click on File-&amp;gt;Save to save this settings into a file (e.g. myBackup) that is placed in the program folder. e.g.: &amp;lt;br&amp;gt;c:\program files\Directory Compare\myBackup&lt;br /&gt;
* Leave the program&lt;br /&gt;
* Use the windows explorer to navigate to the program folder&lt;br /&gt;
* Right click on &amp;quot;DirCmp.exe&amp;quot; -&amp;gt; Create new Shortcut&lt;br /&gt;
* Rename it to something nice&lt;br /&gt;
* Right click on it -&amp;gt; properties (De: Eigenschaften)&lt;br /&gt;
* Edit the link target (De: Ziel) and append &amp;quot;myBackup.dcp /m/q&amp;quot; without the &amp;quot;&amp;lt;br&amp;gt;(/m stands for mirror=make target exactly the same as source, overwriting all changes to files done on taget)&lt;br /&gt;
* Now just create a desktop shortcut to this file and you are done&lt;br /&gt;
Keep in mind, that all changes to files in the target folder are lost and overwritten with each backup, since /m is set, see above.&lt;br /&gt;
If you want to keep an older version of the backup, just rename the folder on your external harddisk, so a new copy of all your data is created at the next backup.&lt;br /&gt;
&lt;br /&gt;
In order to increase security, I strongly advice using two different USB harddisks alternating, e.g. one for even months/weeks, one for uneven ones. The reason is, that if your PC breaks while your USB disk is connected you might loose it...&lt;br /&gt;
(This also keeps a little more history of changes)&lt;br /&gt;
&lt;br /&gt;
====Zip Folder====&lt;br /&gt;
Using [https://sourceforge.net/projects/unxutils/ UnixUtils] for Windows zip util. Should be possible using windows build-in Powershell (Compress-Archive input.txt output.zip).&lt;br /&gt;
 @echo off&lt;br /&gt;
 &lt;br /&gt;
 REM set outputfolder&lt;br /&gt;
 set BACKUPFOLDER=&amp;quot;sicher&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 REM get CurrDirName&lt;br /&gt;
 for %%I in (.) do set CurrDirName=%%~nxI&lt;br /&gt;
 &lt;br /&gt;
 REM use date and time commands to fetch a datestr in format 180830_0647&lt;br /&gt;
 REM for English Windows this has to be modified. Currently written for &lt;br /&gt;
 REM date returning &#039;30.08.2018&#039; and time returning &#039; 6:47:36,10&#039;&lt;br /&gt;
 set DATESTR=%date:~-2,2%%date:~-7,2%%date:~-10,2%_%time:~0,2%%time:~3,2%&lt;br /&gt;
 REM replace &#039; &#039; in hours &amp;lt;10 with 0&lt;br /&gt;
 set DATESTR=%DATESTR: =0%&lt;br /&gt;
 &lt;br /&gt;
 REM mkdir&lt;br /&gt;
 if not exist &amp;quot;%BACKUPFOLDER%&amp;quot; mkdir &amp;quot;%BACKUPFOLDER%&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 REM zipping using UnixUtils&#039; zip, excluding backupfolder&lt;br /&gt;
 %UserProfile%\Documents\Progs\UnixUtils\zip.exe -9 -r &amp;quot;%BACKUPFOLDER%&amp;quot;\%CurrDirName%_%DATESTR%.zip *.* -x *%BACKUPFOLDER%\*&lt;br /&gt;
 REM -x *sicher\* =&amp;gt; exclude folder &amp;quot;sicher&amp;quot;&lt;br /&gt;
&lt;br /&gt;
====rsync for Windows====&lt;br /&gt;
There is a port of rsync for Windows using cygwin, [https://www.heise.de/download/product/rsync-1271 download here]. &lt;br /&gt;
&lt;br /&gt;
=====Update of 2021: better use robocopy=====&lt;br /&gt;
 robocopy SOURCE TARGET /MIR /FFT /Z /R:10 /W:5 /MT:8 /log:robocopy.log /NP /NS /NC&lt;br /&gt;
 /MIR = Mirror&lt;br /&gt;
 /MT  = MultiThreading (default: 8 threads)&lt;br /&gt;
 /FFT = Assumes FAT file times (two-second precision). &lt;br /&gt;
 /Z   = Copies files in restartable mode&lt;br /&gt;
 /R   = Retries&lt;br /&gt;
 /W   = Wait time between retries&lt;br /&gt;
 /NP  = log: no progress (%)&lt;br /&gt;
 /NS  = log: no file sizes&lt;br /&gt;
 /NC  = log: no file classes&lt;br /&gt;
 /NFL = log: no file names&lt;br /&gt;
 /XD  = exclude dirs, does not allow wildcards&lt;br /&gt;
 /XF  = exclude files, allows wildcards: *.tmp&lt;br /&gt;
&lt;br /&gt;
For local Backup on USB disk you should remove the retry mechanism and use only 1 thread&lt;br /&gt;
 robocopy SOURCE TARGET /MIR /Z /R:0 /W:0 /MT:1 /log:robocopy.log /NP /NS /NC&lt;br /&gt;
&lt;br /&gt;
To move instead of backup&lt;br /&gt;
 Robocopy &amp;quot;.\source&amp;quot; &amp;quot;.\target&amp;quot; /move /e /MT:8 /R:3 /W:5 /LOG:&amp;quot;.\robocopy_move.log&amp;quot; /is /it&lt;br /&gt;
 /e include all subdirectories&lt;br /&gt;
 /is Includes the same files. Same files are identical in name, size, times, and all attributes.&lt;br /&gt;
 /it Includes “tweaked” files. Tweaked files have the same name, size, and times, but different attributes.&lt;br /&gt;
&lt;br /&gt;
=====Example 1 - mini=====&lt;br /&gt;
 @echo off&lt;br /&gt;
 set rsync=&amp;quot;E:\progs\rsync\rsync.exe&amp;quot;&lt;br /&gt;
 set source=/cygdrive/&amp;quot;D/photos/2017/&amp;quot;&lt;br /&gt;
 set target=/cygdrive/&amp;quot;E/backup/photos 2017&amp;quot;&lt;br /&gt;
 %rsync% -ahv --modify-window=2 --no-perms --no-owner --no-group --delete --delete-excluded %source% %target%&lt;br /&gt;
 rem a -&amp;gt; rlptgoD&lt;br /&gt;
 rem --modify-window=2 -&amp;gt; 3602 might help sometimes&lt;br /&gt;
 rem modify-window=2 -&amp;gt; allow for time differences of 2 sec, as for MS FAT&lt;br /&gt;
&lt;br /&gt;
=====Example 2=====&lt;br /&gt;
 @echo off&lt;br /&gt;
 set rsync=&amp;quot;C:\Users\menketrb\Documents\Progs\rsync\rsync.exe&amp;quot;&lt;br /&gt;
 set source=/cygdrive/&amp;quot;C/Users/menketrb/Documents/&amp;quot;&lt;br /&gt;
 set target=/cygdrive/&amp;quot;U/sicher/doks-KOPIE&amp;quot;&lt;br /&gt;
 set targetReal=&amp;quot;U:\sicher\doks-KOPIE&amp;quot;&lt;br /&gt;
 set exclude=--exclude=&#039;My Music&#039; --exclude=&#039;My Pictures&#039; --exclude=&#039;My Videos&#039;&lt;br /&gt;
&lt;br /&gt;
 REM write current date to file inside source folder&lt;br /&gt;
 set DATESTR=%date:~-2,4%%date:~-7,2%%date:~-10,2%_%time:~0,2%%time:~3,2%&lt;br /&gt;
 REM replace &#039; &#039; in small hours with 0&lt;br /&gt;
 set DATESTR=%DATESTR: =0%&lt;br /&gt;
 set datefile=%targetReal%\0backup-date-U-%DATESTR%.txt&lt;br /&gt;
 date /T &amp;gt;&amp;gt; %datefile%&lt;br /&gt;
 time /T &amp;gt;&amp;gt; %datefile%&lt;br /&gt;
 &lt;br /&gt;
 REM set target readonly off and on&lt;br /&gt;
 attrib -r %targetReal%\*.* /s&lt;br /&gt;
 %rsync% -ahv --modify-window=2 --no-perms --no-owner --no-group --delete --delete-excluded %source% %target%&lt;br /&gt;
 attrib +r %targetReal%\*.* /s&lt;br /&gt;
&lt;br /&gt;
=====Example 3 - keep daily, weekly and monthly backups=====&lt;br /&gt;
 @echo off&lt;br /&gt;
 &lt;br /&gt;
 set rsync=&amp;quot;C:\Users\menketrb\Documents\Progs\rsync\rsync.exe&amp;quot;&lt;br /&gt;
 set source=/cygdrive/&amp;quot;C/Users/menketrb/Documents/&amp;quot;&lt;br /&gt;
 set sourceReal=&amp;quot;C:\Users\menketrb\Documents&amp;quot;&lt;br /&gt;
 set target=/cygdrive/&amp;quot;C/Users/menketrb/sicher/doksKOPIE&amp;quot;&lt;br /&gt;
 set targetReal=&amp;quot;C:\Users\menketrb\sicher\doksKOPIE&amp;quot;&lt;br /&gt;
 set exclude=--exclude=Progs --exclude=&#039;My Music&#039; --exclude=&#039;My Pictures&#039; --exclude=&#039;My Videos&#039;&lt;br /&gt;
 &lt;br /&gt;
 REM write current date to file inside source folder&lt;br /&gt;
 set DATESTR=%date:~-2,4%%date:~-7,2%%date:~-10,2%_%time:~0,2%%time:~3,2%&lt;br /&gt;
 :: replace &#039; &#039; in small hours with 0&lt;br /&gt;
 set DATESTR=%DATESTR: =0%&lt;br /&gt;
 set datefile=%sourceReal%\0backup-date-Lokal-%DATESTR%.txt&lt;br /&gt;
 date /T &amp;gt;&amp;gt; %datefile%&lt;br /&gt;
 time /T &amp;gt;&amp;gt; %datefile%&lt;br /&gt;
 &lt;br /&gt;
 :: get date in several formats for name of backups&lt;br /&gt;
 set monat=%date:~-7,2%&lt;br /&gt;
 &lt;br /&gt;
 :: Get DayOfWeek as number&lt;br /&gt;
 SETLOCAL enabledelayedexpansion&lt;br /&gt;
 SET /a count=0&lt;br /&gt;
 FOR /F &amp;quot;skip=1&amp;quot; %%D IN (&#039;wmic path win32_localtime get DayOfWeek&#039;) DO (&lt;br /&gt;
     if &amp;quot;!count!&amp;quot; GTR &amp;quot;0&amp;quot; GOTO next&lt;br /&gt;
     set dow=%%D&lt;br /&gt;
     SET /a count+=1&lt;br /&gt;
 )&lt;br /&gt;
 :: /a = arithmetisch&lt;br /&gt;
 :next&lt;br /&gt;
 &lt;br /&gt;
 :: Get WeekInMonth as number&lt;br /&gt;
 SETLOCAL enabledelayedexpansion&lt;br /&gt;
 SET /a count=0&lt;br /&gt;
 FOR /F &amp;quot;skip=1&amp;quot; %%D IN (&#039;wmic path win32_localtime get WeekInMonth&#039;) DO (&lt;br /&gt;
     if &amp;quot;!count!&amp;quot; GTR &amp;quot;0&amp;quot; GOTO next&lt;br /&gt;
     set wim=%%D&lt;br /&gt;
     SET /a count+=1&lt;br /&gt;
 )&lt;br /&gt;
 :: /a = arithmetisch&lt;br /&gt;
 :next&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 REM remove write protection&lt;br /&gt;
 attrib -r %targetReal%\*.* /s&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 echo =&lt;br /&gt;
 echo ======= Tagesbackup: rsync-d%dow% ========&lt;br /&gt;
 echo =&lt;br /&gt;
 %rsync% -ahv --modify-window=2 --no-perms --no-owner --no-group --delete --delete-excluded %source% %target%/rsync-d%dow%&lt;br /&gt;
 &lt;br /&gt;
 echo =&lt;br /&gt;
 echo ======= Wochenbackup: rsync-w%wim% ========&lt;br /&gt;
 echo =&lt;br /&gt;
 %rsync% -ahv --modify-window=2 --no-perms --no-owner --no-group --delete --delete-excluded %source% %target%/rsync-w%wim%&lt;br /&gt;
 &lt;br /&gt;
 echo =&lt;br /&gt;
 echo ======= Monatsbackup: rsync-m%monat% ========&lt;br /&gt;
 echo =&lt;br /&gt;
 %rsync% -ahv --modify-window=2 --no-perms --no-owner --no-group --delete --delete-excluded %source% %target%/rsync-m%monat%&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 REM set write protection&lt;br /&gt;
 attrib +r %targetReal%\*.* /s&lt;br /&gt;
 &lt;br /&gt;
 del %datefile%&lt;br /&gt;
&lt;br /&gt;
===Linux===&lt;br /&gt;
====rsync====&lt;br /&gt;
Use the command line tool rsync e.g.:&lt;br /&gt;
 rsync dir1/*.txt dir2/&lt;br /&gt;
 # delete from target what is not in source&lt;br /&gt;
 rsync -r --delete dir1/ dir2/&lt;br /&gt;
 &lt;br /&gt;
 rsync -rvhu --delete --delete-excluded dir1 dir2&lt;br /&gt;
rsync is able to speak ssh: (z = compress during transfer)&lt;br /&gt;
 rsync -rvhuz --no-links --delete --delete-excluded dir1 user@server:dir2&lt;br /&gt;
&lt;br /&gt;
An alternative is [http://rsnapshot.org rsnapshot] [http://wiki.ubuntuusers.de/rsnapshot Gute Deutsche Anleitung] (Thanks to Philipp + Malte)&lt;br /&gt;
&lt;br /&gt;
An alternative with GUI, similar to Apple&#039;s TimeMachine is [http://wiki.ubuntuusers.de/Back_In_Time Back In Time]&lt;br /&gt;
&lt;br /&gt;
====rsnapshop====&lt;br /&gt;
 sudo apt-get install rsnapshot&lt;br /&gt;
 sudo mkdir /sicher-rsnapshot&lt;br /&gt;
in /etc/rsnapshot.conf (use tab to separate key-values pairs!)&lt;br /&gt;
 # target folder&lt;br /&gt;
 snapshot_root	/sicher-rsnapshot&lt;br /&gt;
 &lt;br /&gt;
 # retain levels = how many backups to keep per level (alpha, beta, gamma, delta)&lt;br /&gt;
 # every day&lt;br /&gt;
 retain  alpha   7&lt;br /&gt;
 # every week&lt;br /&gt;
 retain  beta    4&lt;br /&gt;
 # every month&lt;br /&gt;
 retain  gamma   12&lt;br /&gt;
 # every year&lt;br /&gt;
 retain delta   5 &lt;br /&gt;
 &lt;br /&gt;
 # dirs to backup&lt;br /&gt;
 # LOCALHOST&lt;br /&gt;
 backup  /home/          localhost/&lt;br /&gt;
 backup  /etc/           localhost/&lt;br /&gt;
test configs&lt;br /&gt;
 rsnapshot configtest &lt;br /&gt;
run manually&lt;br /&gt;
 sudo rsnapshot alpha&lt;br /&gt;
run via crontab&lt;br /&gt;
 sudo crontab -e&lt;br /&gt;
 # m h  dom mon dow   command&lt;br /&gt;
 10 0 * * *      /usr/bin/rsnapshot alpha&lt;br /&gt;
 # every week on Monday&lt;br /&gt;
 20 0  * * 1     /usr/bin/rsnapshot beta&lt;br /&gt;
 # every month 1.X.&lt;br /&gt;
 30 0  1 * *     /usr/bin/rsnapshot gamma&lt;br /&gt;
 # every year 1.1.&lt;br /&gt;
 40 0  1 1 *     /usr/bin/rsnapshot delta&lt;br /&gt;
&lt;br /&gt;
==Full image of a harddisk drive==&lt;br /&gt;
I like the free tool [http://www.runtime.org/driveimage-xml.htm DriveImage XML] that be placed on a boot CD in order to do a full backup of a harddisk or partition. It can be found on some bootcds you can find in the net, like UBCD4Win or Hiren&#039;s BootCD, but I prefere creating a small personal bootcd without any other tools on it. In order to do so read the next passage or skip it if you already have a suitable boot cd with DriveImage XML on it.&lt;br /&gt;
&lt;br /&gt;
===Build a Boot CD===&lt;br /&gt;
For some very strange reasons I was not able to build a working bootcd when running the tools in a virtualbox&#039; WinXP. Using a &amp;quot;real&amp;quot; Windows (Vista 64 in my case) it all worked out fine...&lt;br /&gt;
* Fetch [http://www.nu2.nu/pebuilder/#download Bart&#039;s PE Builder] &lt;br /&gt;
* Ensure you have a WinXP Install CD ready&lt;br /&gt;
* Install Bart&#039;s PE Builder&lt;br /&gt;
* Get the [http://www.runtime.org/driveimage_xml.cab DriveImage XML plugin (.cab file)] for Bart&#039;s PE Builder&lt;br /&gt;
* Insert your WinXP CD&lt;br /&gt;
* Run Bart&#039;s PE Builder&lt;br /&gt;
* Tell the program where the WinXP install is located (CD Drive)&lt;br /&gt;
* Click on plugins and add the previously downloaded DriveImage XML plugin&lt;br /&gt;
* Close the plugin window&lt;br /&gt;
* Enable the creation of an iso file&lt;br /&gt;
* Start building&lt;br /&gt;
* Close the program and burn the newly created image onto a cd (not the file, but use the iso-image-mode of your burn program)&lt;br /&gt;
Instead of burning directly you might want to try you bootcd first using virtualbox...&lt;br /&gt;
&lt;br /&gt;
===Backing up===&lt;br /&gt;
* FIRST: Attach the external hard disk that you want to store the image of your pc on&lt;br /&gt;
* Boot from a bootcd that has DriveImage XML installed&lt;br /&gt;
* Start the tool DriveImage XML&lt;br /&gt;
* Do the backup and ensure that the image is stored on your external harddisk&lt;br /&gt;
* Settings: Compression = &amp;quot;Low&amp;quot; makes sense&lt;br /&gt;
&lt;br /&gt;
===Linux and dd, e.g. for SD cards (RasPi)===&lt;br /&gt;
from [https://raspberrypi.stackexchange.com/questions/311/how-do-i-backup-my-raspberry-pi]&lt;br /&gt;
 dd if=/dev/sdX of=/mnt/backup/sdX.dd bs=1M&lt;br /&gt;
 # (bs = blocksize)&lt;br /&gt;
 &lt;br /&gt;
 # with on the fly gzip compression&lt;br /&gt;
 dd if=/dev/sdX | gzip &amp;gt; /mnt/backup/sdX.dd.gz&lt;br /&gt;
 &lt;br /&gt;
 # with on the fly bzip2 compression (slower, but better compression)&lt;br /&gt;
 dd if=/dev/sdX | bzip2 &amp;gt; /mnt/backup/sdX.dd.bz2&lt;br /&gt;
 &lt;br /&gt;
 # with on the fly xz compression (slower, but better compression)&lt;br /&gt;
 dd if=/dev/sdX | xz &amp;gt; /mnt/backup/sdX.dd.xz&lt;br /&gt;
&lt;br /&gt;
Speed/size comparison for a 16GB SD card with only few GB in use (without filling empty space with zeros, see below)&lt;br /&gt;
  	time		bytes		MB/s	compression&lt;br /&gt;
 dd	00:43:07	15931539456	5.9	100.0%&lt;br /&gt;
 gzip	00:43:01	 5205027237	1.9	 32.7%&lt;br /&gt;
 bzip2	00:51:47	 5114374745	1.6	 32.1%&lt;br /&gt;
 xz	01:01:36	 4959001460	1.3	 31.1%&lt;br /&gt;
 after zero-filling of empty space:&lt;br /&gt;
 gzip	00:43:00	  711255877	2,6	  4.5%&lt;br /&gt;
&lt;br /&gt;
To improve compression you can fill empty blocks with zeros first on the device, but this takes time and causes write load on the device (maybe bad for ssds)&lt;br /&gt;
 dd if=/dev/zero of=asdf.txt ; rm asdf.txt&lt;br /&gt;
 # (assuming your current working dir is on the partition you want to fill the free space with zeros.&lt;br /&gt;
&lt;br /&gt;
To restore the backup, reverse the commands:&lt;br /&gt;
 dd if=/mnt/backup/sdX.dd of=/dev/sdX bs=1M&lt;br /&gt;
 &lt;br /&gt;
 # when compressed using gzip&lt;br /&gt;
 gzip -dc /mnt/backup/sdX.dd.gz | dd of=/dev/sdX bs=1M&lt;br /&gt;
 &lt;br /&gt;
 # when compressed using bzip2&lt;br /&gt;
 bunzip2 /mnt/backup/sdX.dd.gz | dd of=/dev/sdX bs=1M&lt;br /&gt;
 &lt;br /&gt;
 # when compressed using xz&lt;br /&gt;
 xunz /mnt/backup/sdX.dd.xz | dd of=/dev/sdX bs=1M&lt;br /&gt;
&lt;br /&gt;
==Disaster Recovery==&lt;br /&gt;
===Undelete single files===&lt;br /&gt;
To enable for recovery, make sure to write as little as possible onto the partition. In Linux unmounting is a good option.&lt;br /&gt;
I suggest using the Linux tool photorec, which scans an (unmounted!) partition (ext2/3, Fat32, NTFS, ...) for deleted files and recovers them to another partition.&lt;br /&gt;
photorec ships with testdisk, so to install in Ubuntu use&lt;br /&gt;
 sudo apt-get install testdisk&lt;br /&gt;
afterwards run&lt;br /&gt;
 sudo photorec&lt;br /&gt;
&lt;br /&gt;
===Fix partition table / recover deleted partition===&lt;br /&gt;
see [https://www.linuxquestions.org/questions/linux-general-1/how-to-recover-deleted-files-from-a-fat32-volume-in-linux-739511/ TestDisk ]&lt;br /&gt;
&lt;br /&gt;
===securely wipe / overwrite disk===&lt;br /&gt;
====Linux/MacOS====&lt;br /&gt;
prior to trashing or selling a harddisk you might want to wipe all data&lt;br /&gt;
 # fill with a zero-filled file&lt;br /&gt;
 cat /dev/zero &amp;gt; asdf.txt&lt;br /&gt;
 # or fill whole drive with zeros&lt;br /&gt;
 dd if=/dev/zero of=/dev/sdX&lt;br /&gt;
 # or more secure fill with random &lt;br /&gt;
 dd if=/dev/urandom of=asdf.txt asdf.txt&lt;br /&gt;
 # or&lt;br /&gt;
 cat /dev/urandom &amp;gt; asdf.txt&lt;br /&gt;
====Windows====&lt;br /&gt;
https://www.feyrer.de/g4u/nullfile-1.01_64bit.exe&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Mac&amp;diff=5160</id>
		<title>Mac</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Mac&amp;diff=5160"/>
		<updated>2025-01-06T06:10:20Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Set Hostname */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Software]][[Category:Apple]]&lt;br /&gt;
==Terminal==&lt;br /&gt;
===Set Hostname===&lt;br /&gt;
from [https://www.autodesk.com/support/technical/article/caas/sfdcarticles/sfdcarticles/Setting-the-Mac-hostname-or-computer-name-from-the-terminal.html]&lt;br /&gt;
 sudo scutil --set HostName T-MB-2.fritz.box&lt;br /&gt;
 sudo scutil --set LocalHostName T-MB-2&lt;br /&gt;
 sudo scutil --set ComputerName T-MB-2&lt;br /&gt;
 dscacheutil -flushcache&lt;br /&gt;
&lt;br /&gt;
==Shortcuts==&lt;br /&gt;
 Control + K : fill 2nd clipboad by cut text from cursor till end of line&lt;br /&gt;
 Control + Y : paste from 2nd clipboad&lt;br /&gt;
Finder / Open file Dialogs&lt;br /&gt;
 Command + Shift + . : show hidden files&lt;br /&gt;
Windows Management&lt;br /&gt;
(better use Rectangle App, see below)&lt;br /&gt;
 Control + Tab : Switch between applications&lt;br /&gt;
 Control + &amp;lt;   : Switch between windows of same application&lt;br /&gt;
 swipe 3-finger-upwards : Mission Control&lt;br /&gt;
Emoji&lt;br /&gt;
 Control+Command+Space&lt;br /&gt;
&lt;br /&gt;
==Disabling/speedup animations==&lt;br /&gt;
 # Window opening animations&lt;br /&gt;
 defaults write NSGlobalDomain NSAutomaticWindowAnimationsEnabled -bool true&lt;br /&gt;
 # revert&lt;br /&gt;
 # defaults write NSGlobalDomain&lt;br /&gt;
 &lt;br /&gt;
 # The resizing animation&lt;br /&gt;
 defaults write NSGlobalDomain NSWindowResizeTime -float 0.001&lt;br /&gt;
 # revert&lt;br /&gt;
 # defaults write NSGlobalDomain NSWindowResizeTime -float 0.2&lt;br /&gt;
 &lt;br /&gt;
 # The Quick Look window animation&lt;br /&gt;
 defaults write -g QLPanelAnimationDuration -float 0&lt;br /&gt;
 # revert&lt;br /&gt;
 # defaults delete -g QLPanelAnimationDuration&lt;br /&gt;
 &lt;br /&gt;
 # Launching an app from the Dock&lt;br /&gt;
 defaults write com.apple.dock launchanim -bool false&lt;br /&gt;
 # revert&lt;br /&gt;
 # defaults write com.apple.dock launchanim -bool true&lt;br /&gt;
&lt;br /&gt;
==Time Machine Backups==&lt;br /&gt;
===Prepare external Hard Disk===&lt;br /&gt;
Formating problem &lt;br /&gt;
 Erase process has failed, press done to continue. Mediakit reports not enough space on device for requested operation.&lt;br /&gt;
Quoting [https://www.reddit.com/r/applehelp/comments/40yvjh/disk_utility_fails_to_eraseformat_an_external_hdd/] : &lt;br /&gt;
Here&#039;s what I would try. First run&lt;br /&gt;
 diskutil list&lt;br /&gt;
to get the name to the disk you&#039;re trying to format. The below commands assume this is &amp;quot;disk1&amp;quot;, but replace &amp;quot;disk1&amp;quot; with the correct disk if it&#039;s something different.&lt;br /&gt;
Now unmount the disk:&lt;br /&gt;
 diskutil unmountDisk force disk1&lt;br /&gt;
and then write zeros to the boot sector:&lt;br /&gt;
 sudo dd if=/dev/zero of=/dev/disk1 bs=1024 count=1024&lt;br /&gt;
finally attempt to partition it again:&lt;br /&gt;
 diskutil partitionDisk disk1 GPT JHFS+ &amp;quot;My External HD&amp;quot; 0g&lt;br /&gt;
&lt;br /&gt;
===Encryption Takes For Ever===&lt;br /&gt;
Quoting [https://discussions.apple.com/thread/7813232]: To speed up the process of TM encryption, go to Disk Utility, select your external HDD and erase and use the option journaled/encrypted. Once your external HDD is erased, you can redo the backup and the encryption will be as fast as a flash.&lt;br /&gt;
&lt;br /&gt;
===Delete old backups===&lt;br /&gt;
mount backup drive in filder via connect to server&lt;br /&gt;
 afp://my_DS_Hostname&lt;br /&gt;
now it can be found under Locations and you can delete old backups by sending them to bin (and cleaning that one afterwards)&lt;br /&gt;
&lt;br /&gt;
==Install Google Keep Notes as Application via Chromium==&lt;br /&gt;
First download [https://download-chromium.appspot.com/?platform=Mac&amp;amp;type=snapshots Chromium] and move to applications than follow this [http://www.rawinfopages.com/mac/content/how-run-google-keep-window-desktop-mac-app guide] to create a shell script for automator.&lt;br /&gt;
 Type     = Application&lt;br /&gt;
 Action   = Run Shell Script&lt;br /&gt;
 Contents = /Applications/Chromium.app/Contents/MacOS/Chromium --app=https://keep.google.com&lt;br /&gt;
Save as .app in Application folder and drag to dock, done!&lt;br /&gt;
&lt;br /&gt;
==Settings==&lt;br /&gt;
Hide top panel from secondary screen from [http://osxdaily.com/2013/10/27/hide-menu-bar-external-display-mac-os-x/]&lt;br /&gt;
 Mission Control -&amp;gt; Uncheck the box next to 2Displays have separate Spaces&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==Tools==&lt;br /&gt;
===Git===&lt;br /&gt;
Git requires apple xcode tools &lt;br /&gt;
 xcode-select --install&lt;br /&gt;
&lt;br /&gt;
===HomeBrew Package Manager===&lt;br /&gt;
from [https://docs.brew.sh/FAQ]&lt;br /&gt;
First update the formulae and Homebrew itself:&lt;br /&gt;
 brew update&lt;br /&gt;
install&lt;br /&gt;
 brew install mypackage&lt;br /&gt;
list installed pakages&lt;br /&gt;
 brew list&lt;br /&gt;
uninstall&lt;br /&gt;
 brew remove mypackage&lt;br /&gt;
You can now find out what is outdated with:&lt;br /&gt;
 brew outdated&lt;br /&gt;
Upgrade everything with:&lt;br /&gt;
 brew upgrade&lt;br /&gt;
By default, Homebrew does not uninstall old versions of a formula, so over time you will accumulate old versions. To remove them, simply use:&lt;br /&gt;
 brew cleanup mypackage&lt;br /&gt;
or clean up everything at once:&lt;br /&gt;
 brew cleanup&lt;br /&gt;
or to see what would be cleaned up:&lt;br /&gt;
 brew cleanup -n&lt;br /&gt;
&lt;br /&gt;
====Special Commands====&lt;br /&gt;
show dependancies&lt;br /&gt;
 brew deps gnuplot&lt;br /&gt;
search for a package/version&lt;br /&gt;
 brew search gnuplot&lt;br /&gt;
manual download file into cache to install a local file [http://mygeekdaddy.net/2014/12/05/how-to-install-a-local-file-in-homebrew/]&lt;br /&gt;
ignore dependencies&lt;br /&gt;
 brew install gnuplot --ignore-dependencies&lt;br /&gt;
&lt;br /&gt;
===Eraze Disk/fill by Zeros===&lt;br /&gt;
 diskutil zeroDisk /Volumes/myDiskName/&lt;br /&gt;
alternative: create a big file&lt;br /&gt;
 dd if=/dev/zero of=tmp_file.txt&lt;br /&gt;
&lt;br /&gt;
===Spelling: unlearn a word/edit custom words===&lt;br /&gt;
 vim ~/Library/Spelling/LocalDictionary&lt;br /&gt;
&lt;br /&gt;
==Apps==&lt;br /&gt;
* https://rectangleapp.com Window mover (Control+Command+Arrows)&lt;br /&gt;
* https://maccy.app clipboard manager (Command+Shift+C)&lt;br /&gt;
* https://apphousekitchen.com/ AlDente: charge limiter&lt;br /&gt;
&lt;br /&gt;
Not in use&lt;br /&gt;
* https://github.com/exelban/stats/ system stats&lt;br /&gt;
* https://matthewpalmer.net/rocket/ emoji typing&lt;br /&gt;
* https://www.mowglii.com/itsycal/ tiny menu bar calendar&lt;br /&gt;
* https://macmousefix.com/ Mac Mouse Fix brings all features of an Apple Trackpad&lt;br /&gt;
* https://spoti.ca/ controls for Spotify and Apple Music&lt;br /&gt;
&lt;br /&gt;
==Fixing problems==&lt;br /&gt;
this ssh warning&lt;br /&gt;
 perl: warning: Setting locale failed.&lt;br /&gt;
can be fixed by setting this in .zshrc&lt;br /&gt;
 export LC_ALL=en_US.UTF-8&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Video_shrinking&amp;diff=5080</id>
		<title>Video shrinking</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Video_shrinking&amp;diff=5080"/>
		<updated>2024-12-23T15:02:44Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Software]]&lt;br /&gt;
&lt;br /&gt;
===2024===&lt;br /&gt;
Use [https://handbrake.fr Handbrake] on Windows, Mac, Linux&lt;br /&gt;
&lt;br /&gt;
Select Preset -&amp;gt; General -&amp;gt; Fast or Very Fast in 1080 or 720&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Linux===&lt;br /&gt;
Dank an Johannes!&lt;br /&gt;
 mencoder -ovc help # zeigt verfügbare Codecs&lt;br /&gt;
 # lavc ist angeblich der beste. Ab und zu wird auch x264 empfohlen.&lt;br /&gt;
 mencoder -vf -o output.avi -oac copy -ovc lavc input.avi&lt;br /&gt;
 # Drehung mit Option rotate=1 / rotate=2&lt;br /&gt;
&lt;br /&gt;
===Windows as of 2018===&lt;br /&gt;
Today I prefer using [https://handbrake.fr Handbrake] for video shrinking / compression as it work in Windows, Linux and Mac.&lt;br /&gt;
&lt;br /&gt;
For movies captured by my digital camera or smartphone &amp;lt;br&amp;gt;&lt;br /&gt;
I use preset &amp;quot;Matroska -&amp;gt; H.265 MKV 720p30&amp;quot; as template and modify &amp;lt;br&amp;gt;&lt;br /&gt;
Picture -&amp;gt; Height 720 for downscaling to 720pt&amp;lt;br&amp;gt;&lt;br /&gt;
Video -&amp;gt; Quality 25 for most videos&amp;lt;br&amp;gt;&lt;br /&gt;
Audio -&amp;gt; 64bit Mono (for camera or telephone videos) &amp;lt;br&amp;gt;&lt;br /&gt;
(this modifications can be saved as &amp;quot;User Preset&amp;quot; which can be set to default preset)&lt;br /&gt;
&lt;br /&gt;
Trimming some seconds off the ends is achieved in section &amp;quot;Source&amp;quot; by switching from &amp;quot;Chapters&amp;quot; to &amp;quot;Seconds&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Rotate&lt;br /&gt;
On Filters tab choose Rotate&lt;br /&gt;
&lt;br /&gt;
===Windows as of 2010===&lt;br /&gt;
I like the free windows tool [http://www.pcfreetime.com/ Format Factory]!&lt;br /&gt;
&lt;br /&gt;
====My Settings====&lt;br /&gt;
For compressing the videos taken by my digicam (resolution=640x480px) I usually use the following settings:&amp;lt;br&amp;gt;&lt;br /&gt;
Video&lt;br /&gt;
* container: mkv (or mp4)&lt;br /&gt;
* codec: H264 &lt;br /&gt;
* bitrate: between 512 and 1024 kbit/s (512kbit/s for 640x480px seems to be enough) &amp;lt;br&amp;gt;(note: 600MB/hour equals 1365kbit/s)&lt;br /&gt;
* 2 Pass encoding: True (for better quality, but much longer computing time)&lt;br /&gt;
&lt;br /&gt;
Audio&lt;br /&gt;
* only one channel (=Mono) @ 64kbit/s (as the tiny mic. in the digicam is not able to record real stereo)&lt;br /&gt;
* sometimes I even disable the audio channel if there is nothing but noise on it.&lt;br /&gt;
&lt;br /&gt;
Output Folder -&amp;gt; &amp;quot;Output to source file folder&amp;quot;&lt;br /&gt;
&lt;br /&gt;
====Rotating the video by 90°====&lt;br /&gt;
use the following settings:&lt;br /&gt;
* Rotate = Left or Right&lt;br /&gt;
* (AspectRatio=4:3) was needed in earlier versions, now use &#039;&#039;&#039;Automatic&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Beispiele====&lt;br /&gt;
16:9, FullHD:  1920x1080 (oder auch 1280x720) schrumpfen auf 800x450?&lt;br /&gt;
 Video Size: 800x450&lt;br /&gt;
 Aspect Ratio: 16:9&lt;br /&gt;
&lt;br /&gt;
====My Format Factory Profiles====&lt;br /&gt;
Here are my custom profiles, that can be found at &lt;br /&gt;
 c:\Users\...\Documents\FormatFactory\VideoCustom\&lt;br /&gt;
&lt;br /&gt;
h264, 512kbit, mono&lt;br /&gt;
 Type=MP4&lt;br /&gt;
 &lt;br /&gt;
 VideoCodec=AVC(H264)&lt;br /&gt;
 VideoBitrate=512000&lt;br /&gt;
 Width=0&lt;br /&gt;
 Height=0&lt;br /&gt;
 FPS=0.000&lt;br /&gt;
 AspectRatio=0.000 &lt;br /&gt;
 &lt;br /&gt;
 AudioCodec=AAC&lt;br /&gt;
 AudioBitrate=64000&lt;br /&gt;
 SampleRate=0&lt;br /&gt;
 Channel=1&lt;br /&gt;
 Volume=0&lt;br /&gt;
 &lt;br /&gt;
 TowPassEncode=1&lt;br /&gt;
&lt;br /&gt;
h264, 512kbit, mono, rotate (one has to set the aspect ratio by hand!)&lt;br /&gt;
 Type=MP4&lt;br /&gt;
 &lt;br /&gt;
 VideoCodec=AVC(H264)&lt;br /&gt;
 VideoBitrate=512000&lt;br /&gt;
 Width=0&lt;br /&gt;
 Height=0&lt;br /&gt;
 FPS=0.000&lt;br /&gt;
 AspectRatio=1.333 &lt;br /&gt;
 &lt;br /&gt;
 AudioCodec=AAC&lt;br /&gt;
 AudioBitrate=64000&lt;br /&gt;
 SampleRate=0&lt;br /&gt;
 Channel=1&lt;br /&gt;
 Volume=0&lt;br /&gt;
 &lt;br /&gt;
 TowPassEncode=1&lt;br /&gt;
&lt;br /&gt;
mute instead of mono:&lt;br /&gt;
 AudioCodec=AAC&lt;br /&gt;
 AudioBitrate=0&lt;br /&gt;
 SampleRate=0&lt;br /&gt;
 Channel=0&lt;br /&gt;
 Volume=0&lt;br /&gt;
(It turned out, that FF version 2.50 does ignore the Channel=0 setting, so you habe to disable audio in the dialog)&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Sentry&amp;diff=5044</id>
		<title>Sentry</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Sentry&amp;diff=5044"/>
		<updated>2024-12-03T08:01:19Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Performance Date */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Software]][[Category:Coding]][[Category:Python]]&lt;br /&gt;
==Python==&lt;br /&gt;
 import sentry_sdk&lt;br /&gt;
 &lt;br /&gt;
 sentry_sdk.init(&lt;br /&gt;
     dsn=&amp;quot;https://12345@my-server.com/12&amp;quot;,&lt;br /&gt;
     environment=&amp;quot;dev&amp;quot;,&lt;br /&gt;
     # debug=True,&lt;br /&gt;
     enable_tracing=False,&lt;br /&gt;
 )&lt;br /&gt;
 # for self-signed certs&lt;br /&gt;
 # use browser to export the WHOLE certificate chain to&lt;br /&gt;
 # ca_certs=&amp;quot;myCertBundle.crt&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
===Exceptions===&lt;br /&gt;
====Var 1: all unhandled Exceptions are sent to Sentry====&lt;br /&gt;
 # Example&lt;br /&gt;
 division_by_zero = 1 / 0&lt;br /&gt;
&lt;br /&gt;
====Var 2: custom event====&lt;br /&gt;
 event_data = {&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;My Custom Event Title&amp;quot;,&lt;br /&gt;
     &amp;quot;transaction&amp;quot;: &amp;quot;My Transaction&amp;quot;,&lt;br /&gt;
     # &amp;quot;user_id&amp;quot;: 123,&lt;br /&gt;
     # &amp;quot;action&amp;quot;: &amp;quot;custom_event&amp;quot;,&lt;br /&gt;
     # &amp;quot;data&amp;quot;: {&amp;quot;key1&amp;quot;: &amp;quot;value1&amp;quot;, &amp;quot;key2&amp;quot;: &amp;quot;value2&amp;quot;},&lt;br /&gt;
 }&lt;br /&gt;
 with sentry_sdk.push_scope() as scope:&lt;br /&gt;
     # scope.user = {&amp;quot;id&amp;quot;: &amp;quot;user-123&amp;quot;}&lt;br /&gt;
     event = sentry_sdk.capture_event(event_data)&lt;br /&gt;
&lt;br /&gt;
====Var 3: Forward Handled Exception====&lt;br /&gt;
 try:&lt;br /&gt;
     division_by_zero = 1 / 0&lt;br /&gt;
 except Exception as e:&lt;br /&gt;
     sentry_sdk.capture_exception(e)&lt;br /&gt;
&lt;br /&gt;
===Performance Date===&lt;br /&gt;
 # enable tracing&lt;br /&gt;
 sentry_sdk.init(&lt;br /&gt;
 ...&lt;br /&gt;
    enable_tracing=True,&lt;br /&gt;
 )&lt;br /&gt;
 ...&lt;br /&gt;
 with sentry_sdk.start_transaction(op=&amp;quot;task&amp;quot;, name=&amp;quot;DB&amp;quot;):&lt;br /&gt;
     # optional spans inside the transaction&lt;br /&gt;
     # with sentry_sdk.start_span(description=&amp;quot;connect&amp;quot;):&lt;br /&gt;
     # (connection, cursor) = connect()&lt;br /&gt;
     # with sentry_sdk.start_span(description=&amp;quot;execute sql&amp;quot;):&lt;br /&gt;
     # results = execute_sql(cursor=cursor, sql=sql)&lt;br /&gt;
     # with sentry_sdk.start_span(description=&amp;quot;disconnect&amp;quot;):&lt;br /&gt;
     # cursor.close()&lt;br /&gt;
     time.sleep(3)&lt;br /&gt;
&lt;br /&gt;
Add custom tag&lt;br /&gt;
 # set plant as Sentry tag&lt;br /&gt;
 scope = sentry_sdk.get_current_scope()&lt;br /&gt;
 scope.set_tag(&amp;quot;plant&amp;quot;, my_plant_id)&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
</feed>