<?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-07-17T23:41:11Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.1</generator>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Biome&amp;diff=5483</id>
		<title>Biome</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Biome&amp;diff=5483"/>
		<updated>2026-07-13T08:55:24Z</updated>

		<summary type="html">&lt;p&gt;Torben: Created page with &amp;quot;== Biome Formatter == https://biomejs.dev  global HOME/biome.js  {    &amp;quot;json&amp;quot;: {      &amp;quot;formatter&amp;quot;: {        &amp;quot;enabled&amp;quot;: true,        &amp;quot;indentStyle&amp;quot;: &amp;quot;space&amp;quot;,        &amp;quot;indentWidth&amp;quot;: 2,        &amp;quot;lineEnding&amp;quot;: &amp;quot;lf&amp;quot;,        &amp;quot;expand&amp;quot;: &amp;quot;auto&amp;quot;      }    }  }&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Biome Formatter ==&lt;br /&gt;
https://biomejs.dev&lt;br /&gt;
&lt;br /&gt;
global HOME/biome.js&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;json&amp;quot;: {&lt;br /&gt;
     &amp;quot;formatter&amp;quot;: {&lt;br /&gt;
       &amp;quot;enabled&amp;quot;: true,&lt;br /&gt;
       &amp;quot;indentStyle&amp;quot;: &amp;quot;space&amp;quot;,&lt;br /&gt;
       &amp;quot;indentWidth&amp;quot;: 2,&lt;br /&gt;
       &amp;quot;lineEnding&amp;quot;: &amp;quot;lf&amp;quot;,&lt;br /&gt;
       &amp;quot;expand&amp;quot;: &amp;quot;auto&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Python&amp;diff=5482</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5482"/>
		<updated>2026-07-08T08:18:36Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Check host:port is reachable */&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;
==Secrets/Credentials==&lt;br /&gt;
use python-dotenv instead of dotenv&lt;br /&gt;
 uv add (--dev) python-dotenv / pip install python-dotenv&lt;br /&gt;
&lt;br /&gt;
 from os import getenv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 from dotenv import load_dotenv&lt;br /&gt;
 load_dotenv(Path(&amp;quot;.env&amp;quot;))&lt;br /&gt;
 DB_PASSWORD = getenv(&amp;quot;PASSWORD&amp;quot;, &amp;quot;&amp;quot;)&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;
===MSSQL===&lt;br /&gt;
====Simple Connect using MS mssql-python====&lt;br /&gt;
https://github.com/microsoft/mssql-python&lt;br /&gt;
 from mssql_python import connect&lt;br /&gt;
 conn = connect(&lt;br /&gt;
     server=DB_CON,&lt;br /&gt;
     database=DB_NAME,&lt;br /&gt;
     uid=DB_USER,&lt;br /&gt;
     pwd=DB_PASS,&lt;br /&gt;
     timeout=5,&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 # cspell: disable&lt;br /&gt;
 query = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT TOP 10 *&lt;br /&gt;
 FROM dbo.V_DPS_SAP_HR;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 # cspell: enable&lt;br /&gt;
 &lt;br /&gt;
 cursor = conn.cursor()&lt;br /&gt;
 cursor.execute(query)&lt;br /&gt;
 records = cursor.fetchall()&lt;br /&gt;
 for r in records:&lt;br /&gt;
     print(r)&lt;br /&gt;
&lt;br /&gt;
====NTLM Login====&lt;br /&gt;
mssql-python (ODBC Driver 18) has no native NTLM-with-explicit-creds keyword.&lt;br /&gt;
We impersonate the target Windows account on the current thread via&lt;br /&gt;
LogonUser (LOGON32_LOGON_NEW_CREDENTIALS) and then open the connection&lt;br /&gt;
with `Trusted_Connection=yes` so SSPI uses the impersonated identity.&lt;br /&gt;
&lt;br /&gt;
 from contextlib import contextmanager&lt;br /&gt;
 from typing import TYPE_CHECKING, Any&lt;br /&gt;
 &lt;br /&gt;
 import win32con&lt;br /&gt;
 import win32security&lt;br /&gt;
 &lt;br /&gt;
 if TYPE_CHECKING:&lt;br /&gt;
     from collections.abc import Generator&lt;br /&gt;
 &lt;br /&gt;
 DOMAIN = &amp;quot;MY_DOMAIN&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 @contextmanager&lt;br /&gt;
 def ntlm_impersonate(&lt;br /&gt;
     user: str, password: str, domain: str = DOMAIN&lt;br /&gt;
 ) -&amp;gt; Generator[None, Any]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Logon as the given Windows user and impersonate on current thread.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     token = win32security.LogonUser(&lt;br /&gt;
         user,&lt;br /&gt;
         domain,&lt;br /&gt;
         password,&lt;br /&gt;
         win32con.LOGON32_LOGON_NEW_CREDENTIALS,&lt;br /&gt;
         win32con.LOGON32_PROVIDER_WINNT50,&lt;br /&gt;
     )&lt;br /&gt;
     try:&lt;br /&gt;
         win32security.ImpersonateLoggedOnUser(token)  # pyright: ignore[reportArgumentType]&lt;br /&gt;
         try:&lt;br /&gt;
             yield&lt;br /&gt;
         finally:&lt;br /&gt;
             win32security.RevertToSelf()&lt;br /&gt;
     finally:&lt;br /&gt;
         token.Close()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 with ntlm_impersonate(DB_USER, DB_PASS):&lt;br /&gt;
     conn = connect(&lt;br /&gt;
         f&amp;quot;server={DB_CON};Trusted_Connection=yes;TrustServerCertificate=yes;&amp;quot;,&lt;br /&gt;
         database=DB_NAME,&lt;br /&gt;
         timeout=5,&lt;br /&gt;
     )&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;br /&gt;
&lt;br /&gt;
==Check host:port is reachable==&lt;br /&gt;
 import socket&lt;br /&gt;
 def check_port_is_reachable(host: str, port: int = 1433, timeout: int = 5) -&amp;gt; bool:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Check if a TCP port on a given host is reachable.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     try:&lt;br /&gt;
         with socket.create_connection((host, port), timeout=timeout):&lt;br /&gt;
             return True&lt;br /&gt;
     except Exception as e:  # noqa: BLE001&lt;br /&gt;
         return False&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Python&amp;diff=5481</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5481"/>
		<updated>2026-07-08T08:18:11Z</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;
==Secrets/Credentials==&lt;br /&gt;
use python-dotenv instead of dotenv&lt;br /&gt;
 uv add (--dev) python-dotenv / pip install python-dotenv&lt;br /&gt;
&lt;br /&gt;
 from os import getenv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 from dotenv import load_dotenv&lt;br /&gt;
 load_dotenv(Path(&amp;quot;.env&amp;quot;))&lt;br /&gt;
 DB_PASSWORD = getenv(&amp;quot;PASSWORD&amp;quot;, &amp;quot;&amp;quot;)&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;
===MSSQL===&lt;br /&gt;
====Simple Connect using MS mssql-python====&lt;br /&gt;
https://github.com/microsoft/mssql-python&lt;br /&gt;
 from mssql_python import connect&lt;br /&gt;
 conn = connect(&lt;br /&gt;
     server=DB_CON,&lt;br /&gt;
     database=DB_NAME,&lt;br /&gt;
     uid=DB_USER,&lt;br /&gt;
     pwd=DB_PASS,&lt;br /&gt;
     timeout=5,&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 # cspell: disable&lt;br /&gt;
 query = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT TOP 10 *&lt;br /&gt;
 FROM dbo.V_DPS_SAP_HR;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 # cspell: enable&lt;br /&gt;
 &lt;br /&gt;
 cursor = conn.cursor()&lt;br /&gt;
 cursor.execute(query)&lt;br /&gt;
 records = cursor.fetchall()&lt;br /&gt;
 for r in records:&lt;br /&gt;
     print(r)&lt;br /&gt;
&lt;br /&gt;
====NTLM Login====&lt;br /&gt;
mssql-python (ODBC Driver 18) has no native NTLM-with-explicit-creds keyword.&lt;br /&gt;
We impersonate the target Windows account on the current thread via&lt;br /&gt;
LogonUser (LOGON32_LOGON_NEW_CREDENTIALS) and then open the connection&lt;br /&gt;
with `Trusted_Connection=yes` so SSPI uses the impersonated identity.&lt;br /&gt;
&lt;br /&gt;
 from contextlib import contextmanager&lt;br /&gt;
 from typing import TYPE_CHECKING, Any&lt;br /&gt;
 &lt;br /&gt;
 import win32con&lt;br /&gt;
 import win32security&lt;br /&gt;
 &lt;br /&gt;
 if TYPE_CHECKING:&lt;br /&gt;
     from collections.abc import Generator&lt;br /&gt;
 &lt;br /&gt;
 DOMAIN = &amp;quot;MY_DOMAIN&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 @contextmanager&lt;br /&gt;
 def ntlm_impersonate(&lt;br /&gt;
     user: str, password: str, domain: str = DOMAIN&lt;br /&gt;
 ) -&amp;gt; Generator[None, Any]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Logon as the given Windows user and impersonate on current thread.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     token = win32security.LogonUser(&lt;br /&gt;
         user,&lt;br /&gt;
         domain,&lt;br /&gt;
         password,&lt;br /&gt;
         win32con.LOGON32_LOGON_NEW_CREDENTIALS,&lt;br /&gt;
         win32con.LOGON32_PROVIDER_WINNT50,&lt;br /&gt;
     )&lt;br /&gt;
     try:&lt;br /&gt;
         win32security.ImpersonateLoggedOnUser(token)  # pyright: ignore[reportArgumentType]&lt;br /&gt;
         try:&lt;br /&gt;
             yield&lt;br /&gt;
         finally:&lt;br /&gt;
             win32security.RevertToSelf()&lt;br /&gt;
     finally:&lt;br /&gt;
         token.Close()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 with ntlm_impersonate(DB_USER, DB_PASS):&lt;br /&gt;
     conn = connect(&lt;br /&gt;
         f&amp;quot;server={DB_CON};Trusted_Connection=yes;TrustServerCertificate=yes;&amp;quot;,&lt;br /&gt;
         database=DB_NAME,&lt;br /&gt;
         timeout=5,&lt;br /&gt;
     )&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;br /&gt;
&lt;br /&gt;
==Check host:port is reachable==&lt;br /&gt;
 import socket&lt;br /&gt;
 def check_port_is_reachable(host: str, port: int = 1433, timeout: int = 5) -&amp;gt; bool:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Check if a TCP port is open on a given host.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     try:&lt;br /&gt;
         with socket.create_connection((host, port), timeout=timeout):&lt;br /&gt;
             return True&lt;br /&gt;
     except Exception as e:  # noqa: BLE001&lt;br /&gt;
         return False&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Npm&amp;diff=5480</id>
		<title>Npm</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Npm&amp;diff=5480"/>
		<updated>2026-07-04T09:33:10Z</updated>

		<summary type="html">&lt;p&gt;Torben: &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;
 brew upgrade pnpm&lt;br /&gt;
 &lt;br /&gt;
 # or &lt;br /&gt;
 npm install -g pnpm&lt;br /&gt;
 &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=AI_LLM_for_Coding&amp;diff=5479</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=5479"/>
		<updated>2026-07-02T21:44:21Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* OpenCode */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
===Caveman speech===&lt;br /&gt;
Accoring to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
Caveman speech can be can be reduced to 6 hard coded lines in (global) AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Ponytail, lazy senior dev mode===&lt;br /&gt;
add contents of [https://github.com/DietrichGebert/ponytail/blob/main/.agents/rules/ponytail.md] to (global) AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===llmfit: Right-sizes LLM models to your system&#039;s RAM, CPU, and GPU===&lt;br /&gt;
https://www.llmfit.org offers a tool that suggests local LLMs that run on your hardware.&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
===Codeburn===&lt;br /&gt;
 https://github.com/getagentseal/codeburn&lt;br /&gt;
 npm install -g codeburn&lt;br /&gt;
 # or&lt;br /&gt;
 brew install codeburn&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
  &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # update models&lt;br /&gt;
 opencode models --refresh&lt;br /&gt;
&lt;br /&gt;
Config ~/.config/opencode/opencode.json&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;$schema&amp;quot;: &amp;quot;https://opencode.ai/config.json&amp;quot;,&lt;br /&gt;
   &amp;quot;share&amp;quot;: &amp;quot;disabled&amp;quot;,&lt;br /&gt;
   &amp;quot;formatter&amp;quot;: true&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Addons&lt;br /&gt;
 # add Caveman and Ponytail instructions to global&lt;br /&gt;
 # ~/.config/opencode/AGENTS.md&lt;br /&gt;
 # see below&lt;br /&gt;
 &lt;br /&gt;
 # add Ruff Token Killer&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup --&amp;gt; choose automatic&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5478</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=5478"/>
		<updated>2026-06-27T15:54:30Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* OpenCode */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
===Caveman speech===&lt;br /&gt;
Accoring to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
Caveman speech can be can be reduced to 6 hard coded lines in (global) AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Ponytail, lazy senior dev mode===&lt;br /&gt;
add contents of [https://github.com/DietrichGebert/ponytail/blob/main/.agents/rules/ponytail.md] to (global) AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===llmfit: Right-sizes LLM models to your system&#039;s RAM, CPU, and GPU===&lt;br /&gt;
https://www.llmfit.org offers a tool that suggests local LLMs that run on your hardware.&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
===Codeburn===&lt;br /&gt;
 https://github.com/getagentseal/codeburn&lt;br /&gt;
 npm install -g codeburn&lt;br /&gt;
 # or&lt;br /&gt;
 brew install codeburn&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Ruff Token Killer&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Caveman and Ponytail instructions to global&lt;br /&gt;
 # ~/.config/opencode/AGENTS.md&lt;br /&gt;
 # see below&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # update models&lt;br /&gt;
 opencode models --refresh&lt;br /&gt;
&lt;br /&gt;
Config ~/.config/opencode/opencode.json&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;$schema&amp;quot;: &amp;quot;https://opencode.ai/config.json&amp;quot;,&lt;br /&gt;
   &amp;quot;share&amp;quot;: &amp;quot;disabled&amp;quot;,&lt;br /&gt;
   &amp;quot;formatter&amp;quot;: true&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup --&amp;gt; choose automatic&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5477</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=5477"/>
		<updated>2026-06-27T15:54:03Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* OpenCode */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
===Caveman speech===&lt;br /&gt;
Accoring to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
Caveman speech can be can be reduced to 6 hard coded lines in (global) AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Ponytail, lazy senior dev mode===&lt;br /&gt;
add contents of [https://github.com/DietrichGebert/ponytail/blob/main/.agents/rules/ponytail.md] to (global) AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===llmfit: Right-sizes LLM models to your system&#039;s RAM, CPU, and GPU===&lt;br /&gt;
https://www.llmfit.org offers a tool that suggests local LLMs that run on your hardware.&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
===Codeburn===&lt;br /&gt;
 https://github.com/getagentseal/codeburn&lt;br /&gt;
 npm install -g codeburn&lt;br /&gt;
 # or&lt;br /&gt;
 brew install codeburn&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Ruff Token Killer&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Caveman instructions to global&lt;br /&gt;
 # ~/.config/opencode/AGENTS.md&lt;br /&gt;
 # see below&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # update models&lt;br /&gt;
 opencode models --refresh&lt;br /&gt;
&lt;br /&gt;
Config ~/.config/opencode/opencode.json&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;$schema&amp;quot;: &amp;quot;https://opencode.ai/config.json&amp;quot;,&lt;br /&gt;
   &amp;quot;share&amp;quot;: &amp;quot;disabled&amp;quot;,&lt;br /&gt;
   &amp;quot;formatter&amp;quot;: true&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup --&amp;gt; choose automatic&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Python&amp;diff=5476</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5476"/>
		<updated>2026-06-22T11:30:43Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Simple Connect */&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;
==Secrets/Credentials==&lt;br /&gt;
use python-dotenv instead of dotenv&lt;br /&gt;
 uv add (--dev) python-dotenv / pip install python-dotenv&lt;br /&gt;
&lt;br /&gt;
 from os import getenv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 from dotenv import load_dotenv&lt;br /&gt;
 load_dotenv(Path(&amp;quot;.env&amp;quot;))&lt;br /&gt;
 DB_PASSWORD = getenv(&amp;quot;PASSWORD&amp;quot;, &amp;quot;&amp;quot;)&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;
===MSSQL===&lt;br /&gt;
====Simple Connect using MS mssql-python====&lt;br /&gt;
https://github.com/microsoft/mssql-python&lt;br /&gt;
 from mssql_python import connect&lt;br /&gt;
 conn = connect(&lt;br /&gt;
     server=DB_CON,&lt;br /&gt;
     database=DB_NAME,&lt;br /&gt;
     uid=DB_USER,&lt;br /&gt;
     pwd=DB_PASS,&lt;br /&gt;
     timeout=5,&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 # cspell: disable&lt;br /&gt;
 query = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT TOP 10 *&lt;br /&gt;
 FROM dbo.V_DPS_SAP_HR;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 # cspell: enable&lt;br /&gt;
 &lt;br /&gt;
 cursor = conn.cursor()&lt;br /&gt;
 cursor.execute(query)&lt;br /&gt;
 records = cursor.fetchall()&lt;br /&gt;
 for r in records:&lt;br /&gt;
     print(r)&lt;br /&gt;
&lt;br /&gt;
====NTLM Login====&lt;br /&gt;
mssql-python (ODBC Driver 18) has no native NTLM-with-explicit-creds keyword.&lt;br /&gt;
We impersonate the target Windows account on the current thread via&lt;br /&gt;
LogonUser (LOGON32_LOGON_NEW_CREDENTIALS) and then open the connection&lt;br /&gt;
with `Trusted_Connection=yes` so SSPI uses the impersonated identity.&lt;br /&gt;
&lt;br /&gt;
 from contextlib import contextmanager&lt;br /&gt;
 from typing import TYPE_CHECKING, Any&lt;br /&gt;
 &lt;br /&gt;
 import win32con&lt;br /&gt;
 import win32security&lt;br /&gt;
 &lt;br /&gt;
 if TYPE_CHECKING:&lt;br /&gt;
     from collections.abc import Generator&lt;br /&gt;
 &lt;br /&gt;
 DOMAIN = &amp;quot;MY_DOMAIN&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 @contextmanager&lt;br /&gt;
 def ntlm_impersonate(&lt;br /&gt;
     user: str, password: str, domain: str = DOMAIN&lt;br /&gt;
 ) -&amp;gt; Generator[None, Any]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Logon as the given Windows user and impersonate on current thread.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     token = win32security.LogonUser(&lt;br /&gt;
         user,&lt;br /&gt;
         domain,&lt;br /&gt;
         password,&lt;br /&gt;
         win32con.LOGON32_LOGON_NEW_CREDENTIALS,&lt;br /&gt;
         win32con.LOGON32_PROVIDER_WINNT50,&lt;br /&gt;
     )&lt;br /&gt;
     try:&lt;br /&gt;
         win32security.ImpersonateLoggedOnUser(token)  # pyright: ignore[reportArgumentType]&lt;br /&gt;
         try:&lt;br /&gt;
             yield&lt;br /&gt;
         finally:&lt;br /&gt;
             win32security.RevertToSelf()&lt;br /&gt;
     finally:&lt;br /&gt;
         token.Close()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 with ntlm_impersonate(DB_USER, DB_PASS):&lt;br /&gt;
     conn = connect(&lt;br /&gt;
         f&amp;quot;server={DB_CON};Trusted_Connection=yes;TrustServerCertificate=yes;&amp;quot;,&lt;br /&gt;
         database=DB_NAME,&lt;br /&gt;
         timeout=5,&lt;br /&gt;
     )&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=5475</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5475"/>
		<updated>2026-06-22T11:29:19Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* MSSQL */&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;
==Secrets/Credentials==&lt;br /&gt;
use python-dotenv instead of dotenv&lt;br /&gt;
 uv add (--dev) python-dotenv / pip install python-dotenv&lt;br /&gt;
&lt;br /&gt;
 from os import getenv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 from dotenv import load_dotenv&lt;br /&gt;
 load_dotenv(Path(&amp;quot;.env&amp;quot;))&lt;br /&gt;
 DB_PASSWORD = getenv(&amp;quot;PASSWORD&amp;quot;, &amp;quot;&amp;quot;)&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;
===MSSQL===&lt;br /&gt;
====Simple Connect====&lt;br /&gt;
 from mssql_python import connect&lt;br /&gt;
 conn = connect(&lt;br /&gt;
     server=DB_CON,&lt;br /&gt;
     database=DB_NAME,&lt;br /&gt;
     uid=DB_USER,&lt;br /&gt;
     pwd=DB_PASS,&lt;br /&gt;
     timeout=5,&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 # cspell: disable&lt;br /&gt;
 query = &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 SELECT TOP 10 *&lt;br /&gt;
 FROM dbo.V_DPS_SAP_HR;&lt;br /&gt;
 &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
 # cspell: enable&lt;br /&gt;
 &lt;br /&gt;
 cursor = conn.cursor()&lt;br /&gt;
 cursor.execute(query)&lt;br /&gt;
 records = cursor.fetchall()&lt;br /&gt;
 for r in records:&lt;br /&gt;
     print(r)&lt;br /&gt;
&lt;br /&gt;
====NTLM Login====&lt;br /&gt;
mssql-python (ODBC Driver 18) has no native NTLM-with-explicit-creds keyword.&lt;br /&gt;
We impersonate the target Windows account on the current thread via&lt;br /&gt;
LogonUser (LOGON32_LOGON_NEW_CREDENTIALS) and then open the connection&lt;br /&gt;
with `Trusted_Connection=yes` so SSPI uses the impersonated identity.&lt;br /&gt;
&lt;br /&gt;
 from contextlib import contextmanager&lt;br /&gt;
 from typing import TYPE_CHECKING, Any&lt;br /&gt;
 &lt;br /&gt;
 import win32con&lt;br /&gt;
 import win32security&lt;br /&gt;
 &lt;br /&gt;
 if TYPE_CHECKING:&lt;br /&gt;
     from collections.abc import Generator&lt;br /&gt;
 &lt;br /&gt;
 DOMAIN = &amp;quot;MY_DOMAIN&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 @contextmanager&lt;br /&gt;
 def ntlm_impersonate(&lt;br /&gt;
     user: str, password: str, domain: str = DOMAIN&lt;br /&gt;
 ) -&amp;gt; Generator[None, Any]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Logon as the given Windows user and impersonate on current thread.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     token = win32security.LogonUser(&lt;br /&gt;
         user,&lt;br /&gt;
         domain,&lt;br /&gt;
         password,&lt;br /&gt;
         win32con.LOGON32_LOGON_NEW_CREDENTIALS,&lt;br /&gt;
         win32con.LOGON32_PROVIDER_WINNT50,&lt;br /&gt;
     )&lt;br /&gt;
     try:&lt;br /&gt;
         win32security.ImpersonateLoggedOnUser(token)  # pyright: ignore[reportArgumentType]&lt;br /&gt;
         try:&lt;br /&gt;
             yield&lt;br /&gt;
         finally:&lt;br /&gt;
             win32security.RevertToSelf()&lt;br /&gt;
     finally:&lt;br /&gt;
         token.Close()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 with ntlm_impersonate(DB_USER, DB_PASS):&lt;br /&gt;
     conn = connect(&lt;br /&gt;
         f&amp;quot;server={DB_CON};Trusted_Connection=yes;TrustServerCertificate=yes;&amp;quot;,&lt;br /&gt;
         database=DB_NAME,&lt;br /&gt;
         timeout=5,&lt;br /&gt;
     )&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=5474</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5474"/>
		<updated>2026-06-22T11:28:05Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* NTLM Login */&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;
==Secrets/Credentials==&lt;br /&gt;
use python-dotenv instead of dotenv&lt;br /&gt;
 uv add (--dev) python-dotenv / pip install python-dotenv&lt;br /&gt;
&lt;br /&gt;
 from os import getenv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 from dotenv import load_dotenv&lt;br /&gt;
 load_dotenv(Path(&amp;quot;.env&amp;quot;))&lt;br /&gt;
 DB_PASSWORD = getenv(&amp;quot;PASSWORD&amp;quot;, &amp;quot;&amp;quot;)&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;
===MSSQL===&lt;br /&gt;
====NTLM Login====&lt;br /&gt;
mssql-python (ODBC Driver 18) has no native NTLM-with-explicit-creds keyword.&lt;br /&gt;
We impersonate the target Windows account on the current thread via&lt;br /&gt;
LogonUser (LOGON32_LOGON_NEW_CREDENTIALS) and then open the connection&lt;br /&gt;
with `Trusted_Connection=yes` so SSPI uses the impersonated identity.&lt;br /&gt;
&lt;br /&gt;
 from contextlib import contextmanager&lt;br /&gt;
 from typing import TYPE_CHECKING, Any&lt;br /&gt;
 &lt;br /&gt;
 import win32con&lt;br /&gt;
 import win32security&lt;br /&gt;
 &lt;br /&gt;
 if TYPE_CHECKING:&lt;br /&gt;
     from collections.abc import Generator&lt;br /&gt;
 &lt;br /&gt;
 DOMAIN = &amp;quot;MY_DOMAIN&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 @contextmanager&lt;br /&gt;
 def ntlm_impersonate(&lt;br /&gt;
     user: str, password: str, domain: str = DOMAIN&lt;br /&gt;
 ) -&amp;gt; Generator[None, Any]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Logon as the given Windows user and impersonate on current thread.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     token = win32security.LogonUser(&lt;br /&gt;
         user,&lt;br /&gt;
         domain,&lt;br /&gt;
         password,&lt;br /&gt;
         win32con.LOGON32_LOGON_NEW_CREDENTIALS,&lt;br /&gt;
         win32con.LOGON32_PROVIDER_WINNT50,&lt;br /&gt;
     )&lt;br /&gt;
     try:&lt;br /&gt;
         win32security.ImpersonateLoggedOnUser(token)  # pyright: ignore[reportArgumentType]&lt;br /&gt;
         try:&lt;br /&gt;
             yield&lt;br /&gt;
         finally:&lt;br /&gt;
             win32security.RevertToSelf()&lt;br /&gt;
     finally:&lt;br /&gt;
         token.Close()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 with ntlm_impersonate(DB_USER, DB_PASS):&lt;br /&gt;
     conn = connect(&lt;br /&gt;
         f&amp;quot;server={DB_CON};Trusted_Connection=yes;TrustServerCertificate=yes;&amp;quot;,&lt;br /&gt;
         database=DB_NAME,&lt;br /&gt;
         timeout=5,&lt;br /&gt;
     )&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=5473</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5473"/>
		<updated>2026-06-22T11:26:53Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* MSSQL */&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;
==Secrets/Credentials==&lt;br /&gt;
use python-dotenv instead of dotenv&lt;br /&gt;
 uv add (--dev) python-dotenv / pip install python-dotenv&lt;br /&gt;
&lt;br /&gt;
 from os import getenv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 from dotenv import load_dotenv&lt;br /&gt;
 load_dotenv(Path(&amp;quot;.env&amp;quot;))&lt;br /&gt;
 DB_PASSWORD = getenv(&amp;quot;PASSWORD&amp;quot;, &amp;quot;&amp;quot;)&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;
===MSSQL===&lt;br /&gt;
====NTLM Login====&lt;br /&gt;
 from contextlib import contextmanager&lt;br /&gt;
 from typing import TYPE_CHECKING, Any&lt;br /&gt;
 &lt;br /&gt;
 import win32con&lt;br /&gt;
 import win32security&lt;br /&gt;
 &lt;br /&gt;
 if TYPE_CHECKING:&lt;br /&gt;
     from collections.abc import Generator&lt;br /&gt;
 &lt;br /&gt;
 DOMAIN = &amp;quot;MY_DOMAIN&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 @contextmanager&lt;br /&gt;
 def ntlm_impersonate(&lt;br /&gt;
     user: str, password: str, domain: str = DOMAIN&lt;br /&gt;
 ) -&amp;gt; Generator[None, Any]:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Logon as the given Windows user and impersonate on current thread.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     token = win32security.LogonUser(&lt;br /&gt;
         user,&lt;br /&gt;
         domain,&lt;br /&gt;
         password,&lt;br /&gt;
         win32con.LOGON32_LOGON_NEW_CREDENTIALS,&lt;br /&gt;
         win32con.LOGON32_PROVIDER_WINNT50,&lt;br /&gt;
     )&lt;br /&gt;
     try:&lt;br /&gt;
         win32security.ImpersonateLoggedOnUser(token)  # pyright: ignore[reportArgumentType]&lt;br /&gt;
         try:&lt;br /&gt;
             yield&lt;br /&gt;
         finally:&lt;br /&gt;
             win32security.RevertToSelf()&lt;br /&gt;
     finally:&lt;br /&gt;
         token.Close()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 with ntlm_impersonate(DB_USER, DB_PASS):&lt;br /&gt;
     conn = connect(&lt;br /&gt;
         f&amp;quot;server={DB_CON};Trusted_Connection=yes;TrustServerCertificate=yes;&amp;quot;,&lt;br /&gt;
         database=DB_NAME,&lt;br /&gt;
         timeout=5,&lt;br /&gt;
     )&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=5472</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5472"/>
		<updated>2026-06-22T11:25:16Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Internet Access */&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;
==Secrets/Credentials==&lt;br /&gt;
use python-dotenv instead of dotenv&lt;br /&gt;
 uv add (--dev) python-dotenv / pip install python-dotenv&lt;br /&gt;
&lt;br /&gt;
 from os import getenv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 from dotenv import load_dotenv&lt;br /&gt;
 load_dotenv(Path(&amp;quot;.env&amp;quot;))&lt;br /&gt;
 DB_PASSWORD = getenv(&amp;quot;PASSWORD&amp;quot;, &amp;quot;&amp;quot;)&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;
===MSSQL===&lt;br /&gt;
&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=PostgreSQL&amp;diff=5471</id>
		<title>PostgreSQL</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=PostgreSQL&amp;diff=5471"/>
		<updated>2026-06-22T11:22:41Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Cron Jobs */&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;br /&gt;
&lt;br /&gt;
==Cron Jobs==&lt;br /&gt;
1. prepare DB as postgresadmin on postgres DB&lt;br /&gt;
 CREATE EXTENSION IF NOT EXISTS pg_cron;&lt;br /&gt;
 &lt;br /&gt;
 GRANT USAGE ON SCHEMA cron TO myuser;&lt;br /&gt;
 GRANT SELECT ON cron.job TO myuser;&lt;br /&gt;
 GRANT SELECT ON cron.job_run_details TO myuser;&lt;br /&gt;
 GRANT EXECUTE ON FUNCTION cron.schedule_in_database(text, text, text, text, text, boolean) TO myuser;&lt;br /&gt;
&lt;br /&gt;
2. connect as user &#039;myuser&#039; to DB &#039;postgres&#039;, schema &#039;cron&#039;&lt;br /&gt;
 -- create job my_db_cron_job&lt;br /&gt;
 SELECT cron.schedule_in_database(&lt;br /&gt;
     &#039;my_db_cron_job&#039;,          -- job name&lt;br /&gt;
     &#039;0 4 * * 0&#039;,               -- every Sunday 4:00 UTC&lt;br /&gt;
     $$DELETE FROM my_table WHERE my_date &amp;lt; now() - interval &#039;30 days&#039;$$,&lt;br /&gt;
     &#039;mydb&#039;                     -- target database&lt;br /&gt;
 );&lt;br /&gt;
 &lt;br /&gt;
 -- list all jobs&lt;br /&gt;
 SELECT jobid, jobname, schedule, command, database, username, active&lt;br /&gt;
 FROM cron.job&lt;br /&gt;
 ORDER BY jobname;&lt;br /&gt;
 &lt;br /&gt;
 -- delete a job:&lt;br /&gt;
 SELECT cron.unschedule(&#039;my_db_cron_job&#039;);&lt;br /&gt;
 &lt;br /&gt;
 -- check recent run history:&lt;br /&gt;
 SELECT jobid, start_time, end_time, status, return_message&lt;br /&gt;
 FROM cron.job_run_details&lt;br /&gt;
 ORDER BY start_time DESC&lt;br /&gt;
 LIMIT 20;&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=PostgreSQL&amp;diff=5470</id>
		<title>PostgreSQL</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=PostgreSQL&amp;diff=5470"/>
		<updated>2026-06-22T11:21:41Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Cron Jobs */&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;br /&gt;
&lt;br /&gt;
==Cron Jobs==&lt;br /&gt;
1. prepare DB as postgresadmin on postgres DB&lt;br /&gt;
 CREATE EXTENSION IF NOT EXISTS pg_cron;&lt;br /&gt;
 &lt;br /&gt;
 GRANT USAGE ON SCHEMA cron TO myuser;&lt;br /&gt;
 GRANT SELECT ON cron.job TO myuser;&lt;br /&gt;
 GRANT SELECT ON cron.job_run_details TO myuser;&lt;br /&gt;
 GRANT EXECUTE ON FUNCTION cron.schedule_in_database(text, text, text, text, text, boolean) TO myuser;&lt;br /&gt;
&lt;br /&gt;
2. connect as user &#039;myuser&#039; to DB &#039;postgres&#039;, schema &#039;cron&#039;&lt;br /&gt;
 -- create job my_db_cron_job&lt;br /&gt;
 SELECT cron.schedule_in_database(&lt;br /&gt;
     &#039;my_db_cron_job&#039;,          -- job name&lt;br /&gt;
     &#039;0 4 * * 0&#039;,                           -- every Sunday 4:00 UTC&lt;br /&gt;
     $$DELETE FROM errorlog_dfq_field WHERE logged_at &amp;lt; now() - interval &#039;30 days&#039;$$,&lt;br /&gt;
     &#039;myuser&#039;                                 -- target database&lt;br /&gt;
 );&lt;br /&gt;
 &lt;br /&gt;
 -- list all jobs&lt;br /&gt;
 SELECT jobid, jobname, schedule, command, database, username, active&lt;br /&gt;
 FROM cron.job&lt;br /&gt;
 ORDER BY jobname;&lt;br /&gt;
 &lt;br /&gt;
 -- delete a job:&lt;br /&gt;
 SELECT cron.unschedule(&#039;my_db_cron_job&#039;);&lt;br /&gt;
 &lt;br /&gt;
 -- check recent run history:&lt;br /&gt;
 SELECT jobid, start_time, end_time, status, return_message&lt;br /&gt;
 FROM cron.job_run_details&lt;br /&gt;
 ORDER BY start_time DESC&lt;br /&gt;
 LIMIT 20;&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=PostgreSQL&amp;diff=5469</id>
		<title>PostgreSQL</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=PostgreSQL&amp;diff=5469"/>
		<updated>2026-06-22T11:20:53Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Cron Jobs */&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;br /&gt;
&lt;br /&gt;
==Cron Jobs==&lt;br /&gt;
1. prepare DB as postgresadmin on postgres DB&lt;br /&gt;
 CREATE EXTENSION IF NOT EXISTS pg_cron;&lt;br /&gt;
 &lt;br /&gt;
 GRANT USAGE ON SCHEMA cron TO myuser;&lt;br /&gt;
 GRANT SELECT ON cron.job TO myuser;&lt;br /&gt;
 GRANT SELECT ON cron.job_run_details TO myuser;&lt;br /&gt;
 GRANT EXECUTE ON FUNCTION cron.schedule_in_database(text, text, text, text, text, boolean) TO myuser;&lt;br /&gt;
&lt;br /&gt;
2. connect as user myuser to DB &#039;postgres&#039;, schema &#039;cron&#039;&lt;br /&gt;
 -- create job my_db_cron_job&lt;br /&gt;
 SELECT cron.schedule_in_database(&lt;br /&gt;
     &#039;my_db_cron_job&#039;,          -- job name&lt;br /&gt;
     &#039;0 4 * * 0&#039;,                           -- every Sunday 4:00 UTC&lt;br /&gt;
     $$DELETE FROM errorlog_dfq_field WHERE logged_at &amp;lt; now() - interval &#039;30 days&#039;$$,&lt;br /&gt;
     &#039;myuser&#039;                                 -- target database&lt;br /&gt;
 );&lt;br /&gt;
 &lt;br /&gt;
 -- list all jobs&lt;br /&gt;
 SELECT jobid, jobname, schedule, command, database, username, active&lt;br /&gt;
 FROM cron.job&lt;br /&gt;
 ORDER BY jobname;&lt;br /&gt;
 &lt;br /&gt;
 -- delete a job:&lt;br /&gt;
 SELECT cron.unschedule(&#039;my_db_cron_job&#039;);&lt;br /&gt;
 &lt;br /&gt;
 -- check recent run history:&lt;br /&gt;
 SELECT jobid, start_time, end_time, status, return_message&lt;br /&gt;
 FROM cron.job_run_details&lt;br /&gt;
 ORDER BY start_time DESC&lt;br /&gt;
 LIMIT 20;&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=PostgreSQL&amp;diff=5468</id>
		<title>PostgreSQL</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=PostgreSQL&amp;diff=5468"/>
		<updated>2026-06-22T11:15:28Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Performance Analysis */&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;br /&gt;
&lt;br /&gt;
==Cron Jobs==&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=5467</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=5467"/>
		<updated>2026-06-22T07:08:56Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Ponytail, lazy senior dev mode */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
===Caveman speech===&lt;br /&gt;
Accoring to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
Caveman speech can be can be reduced to 6 hard coded lines in (global) AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Ponytail, lazy senior dev mode===&lt;br /&gt;
add contents of [https://github.com/DietrichGebert/ponytail/blob/main/.agents/rules/ponytail.md] to (global) AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===llmfit: Right-sizes LLM models to your system&#039;s RAM, CPU, and GPU===&lt;br /&gt;
https://www.llmfit.org offers a tool that suggests local LLMs that run on your hardware.&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
===Codeburn===&lt;br /&gt;
 https://github.com/getagentseal/codeburn&lt;br /&gt;
 npm install -g codeburn&lt;br /&gt;
 # or&lt;br /&gt;
 brew install codeburn&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Ruff Token Killer&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Caveman instructions to global&lt;br /&gt;
 # ~/.config/opencode/AGENTS.md&lt;br /&gt;
 # see below&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
Config ~/.config/opencode/opencode.json&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;$schema&amp;quot;: &amp;quot;https://opencode.ai/config.json&amp;quot;,&lt;br /&gt;
   &amp;quot;share&amp;quot;: &amp;quot;disabled&amp;quot;,&lt;br /&gt;
   &amp;quot;formatter&amp;quot;: true&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup --&amp;gt; choose automatic&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5466</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=5466"/>
		<updated>2026-06-22T07:08:22Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Cavemen style */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
===Caveman speech===&lt;br /&gt;
Accoring to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
Caveman speech can be can be reduced to 6 hard coded lines in (global) AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Ponytail, lazy senior dev mode===&lt;br /&gt;
See [https://github.com/DietrichGebert/ponytail/blob/main/.agents/rules/ponytail.md]&lt;br /&gt;
&lt;br /&gt;
===llmfit: Right-sizes LLM models to your system&#039;s RAM, CPU, and GPU===&lt;br /&gt;
https://www.llmfit.org offers a tool that suggests local LLMs that run on your hardware.&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
===Codeburn===&lt;br /&gt;
 https://github.com/getagentseal/codeburn&lt;br /&gt;
 npm install -g codeburn&lt;br /&gt;
 # or&lt;br /&gt;
 brew install codeburn&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Ruff Token Killer&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Caveman instructions to global&lt;br /&gt;
 # ~/.config/opencode/AGENTS.md&lt;br /&gt;
 # see below&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
Config ~/.config/opencode/opencode.json&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;$schema&amp;quot;: &amp;quot;https://opencode.ai/config.json&amp;quot;,&lt;br /&gt;
   &amp;quot;share&amp;quot;: &amp;quot;disabled&amp;quot;,&lt;br /&gt;
   &amp;quot;formatter&amp;quot;: true&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup --&amp;gt; choose automatic&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5465</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=5465"/>
		<updated>2026-06-22T07:06:46Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
From [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in (global) AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Ponytail, lazy senior dev mode===&lt;br /&gt;
See [https://github.com/DietrichGebert/ponytail/blob/main/.agents/rules/ponytail.md]&lt;br /&gt;
&lt;br /&gt;
===llmfit: Right-sizes LLM models to your system&#039;s RAM, CPU, and GPU===&lt;br /&gt;
https://www.llmfit.org offers a tool that suggests local LLMs that run on your hardware.&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
===Codeburn===&lt;br /&gt;
 https://github.com/getagentseal/codeburn&lt;br /&gt;
 npm install -g codeburn&lt;br /&gt;
 # or&lt;br /&gt;
 brew install codeburn&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Ruff Token Killer&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Caveman instructions to global&lt;br /&gt;
 # ~/.config/opencode/AGENTS.md&lt;br /&gt;
 # see below&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
Config ~/.config/opencode/opencode.json&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;$schema&amp;quot;: &amp;quot;https://opencode.ai/config.json&amp;quot;,&lt;br /&gt;
   &amp;quot;share&amp;quot;: &amp;quot;disabled&amp;quot;,&lt;br /&gt;
   &amp;quot;formatter&amp;quot;: true&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup --&amp;gt; choose automatic&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5464</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=5464"/>
		<updated>2026-06-19T10:12:01Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
From [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in (global) AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Ponytail, lazy senior dev mode===&lt;br /&gt;
See [https://github.com/DietrichGebert/ponytail/blob/main/.agents/rules/ponytail.md]&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
===Codeburn===&lt;br /&gt;
 https://github.com/getagentseal/codeburn&lt;br /&gt;
 npm install -g codeburn&lt;br /&gt;
 # or&lt;br /&gt;
 brew install codeburn&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Ruff Token Killer&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Caveman instructions to global&lt;br /&gt;
 # ~/.config/opencode/AGENTS.md&lt;br /&gt;
 # see below&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
Config ~/.config/opencode/opencode.json&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;$schema&amp;quot;: &amp;quot;https://opencode.ai/config.json&amp;quot;,&lt;br /&gt;
   &amp;quot;share&amp;quot;: &amp;quot;disabled&amp;quot;,&lt;br /&gt;
   &amp;quot;formatter&amp;quot;: true&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup --&amp;gt; choose automatic&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=Python&amp;diff=5463</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5463"/>
		<updated>2026-06-19T02:56:09Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Secrets/Credentials */&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;
==Secrets/Credentials==&lt;br /&gt;
use python-dotenv instead of dotenv&lt;br /&gt;
 uv add (--dev) python-dotenv / pip install python-dotenv&lt;br /&gt;
&lt;br /&gt;
 from os import getenv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 from dotenv import load_dotenv&lt;br /&gt;
 load_dotenv(Path(&amp;quot;.env&amp;quot;))&lt;br /&gt;
 DB_PASSWORD = getenv(&amp;quot;PASSWORD&amp;quot;, &amp;quot;&amp;quot;)&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=5462</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5462"/>
		<updated>2026-06-19T02:55:23Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Secrets/Credentials */&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;
==Secrets/Credentials==&lt;br /&gt;
 uv add (--dev) python-dotenv / pip install python-dotenv&lt;br /&gt;
&lt;br /&gt;
 from os import getenv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 from dotenv import load_dotenv&lt;br /&gt;
 load_dotenv(Path(&amp;quot;.env&amp;quot;))&lt;br /&gt;
 DB_PASSWORD = getenv(&amp;quot;PASSWORD&amp;quot;, &amp;quot;&amp;quot;)&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=5461</id>
		<title>Python</title>
		<link rel="alternate" type="text/html" href="https://entorb.net//wiki/index.php?title=Python&amp;diff=5461"/>
		<updated>2026-06-19T02:43:46Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* File Access */&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;
==Secrets/Credentials==&lt;br /&gt;
 uv add (--dev) dotenv / pip install dotenv&lt;br /&gt;
&lt;br /&gt;
 from os import getenv&lt;br /&gt;
 from pathlib import Path&lt;br /&gt;
 from dotenv import load_dotenv&lt;br /&gt;
 load_dotenv(Path(&amp;quot;.env&amp;quot;))&lt;br /&gt;
 DB_PASSWORD = getenv(&amp;quot;PASSWORD&amp;quot;, &amp;quot;&amp;quot;)&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=AI_LLM_for_Coding&amp;diff=5460</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=5460"/>
		<updated>2026-06-17T10:31:24Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in (global) AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
===Codeburn===&lt;br /&gt;
 https://github.com/getagentseal/codeburn&lt;br /&gt;
 npm install -g codeburn&lt;br /&gt;
 # or&lt;br /&gt;
 brew install codeburn&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Ruff Token Killer&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Caveman instructions to global&lt;br /&gt;
 # ~/.config/opencode/AGENTS.md&lt;br /&gt;
 # see below&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
Config ~/.config/opencode/opencode.json&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;$schema&amp;quot;: &amp;quot;https://opencode.ai/config.json&amp;quot;,&lt;br /&gt;
   &amp;quot;share&amp;quot;: &amp;quot;disabled&amp;quot;,&lt;br /&gt;
   &amp;quot;formatter&amp;quot;: true&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup --&amp;gt; choose automatic&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5459</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=5459"/>
		<updated>2026-06-17T10:14:14Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* OpenCode */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
===Codeburn===&lt;br /&gt;
 https://github.com/getagentseal/codeburn&lt;br /&gt;
 npm install -g codeburn&lt;br /&gt;
 # or&lt;br /&gt;
 brew install codeburn&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Ruff Token Killer&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Caveman instructions to global&lt;br /&gt;
 # ~/.config/opencode/AGENTS.md&lt;br /&gt;
 # see below&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
Config ~/.config/opencode/opencode.json&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;$schema&amp;quot;: &amp;quot;https://opencode.ai/config.json&amp;quot;,&lt;br /&gt;
   &amp;quot;share&amp;quot;: &amp;quot;disabled&amp;quot;,&lt;br /&gt;
   &amp;quot;formatter&amp;quot;: true&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup --&amp;gt; choose automatic&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5458</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=5458"/>
		<updated>2026-06-17T10:13:48Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* OpenCode */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
===Codeburn===&lt;br /&gt;
 https://github.com/getagentseal/codeburn&lt;br /&gt;
 npm install -g codeburn&lt;br /&gt;
 # or&lt;br /&gt;
 brew install codeburn&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Ruff Token Killer&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Caveman instructions to ~/.config/opencode/AGENTS.md&lt;br /&gt;
 # see below&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
Config ~/.config/opencode/opencode.json&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;$schema&amp;quot;: &amp;quot;https://opencode.ai/config.json&amp;quot;,&lt;br /&gt;
   &amp;quot;share&amp;quot;: &amp;quot;disabled&amp;quot;,&lt;br /&gt;
   &amp;quot;formatter&amp;quot;: true&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup --&amp;gt; choose automatic&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5457</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=5457"/>
		<updated>2026-06-17T10:13:13Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* OpenCode */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
===Codeburn===&lt;br /&gt;
 https://github.com/getagentseal/codeburn&lt;br /&gt;
 npm install -g codeburn&lt;br /&gt;
 # or&lt;br /&gt;
 brew install codeburn&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Ruff Token Killer&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Caveman instructions to ~/.config/opencode/AGENTS.md&lt;br /&gt;
 # see below&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup --&amp;gt; choose automatic&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5456</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=5456"/>
		<updated>2026-06-17T10:12:58Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
===Codeburn===&lt;br /&gt;
 https://github.com/getagentseal/codeburn&lt;br /&gt;
 npm install -g codeburn&lt;br /&gt;
 # or&lt;br /&gt;
 brew install codeburn&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Ruff Token Killer&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 &lt;br /&gt;
 # add Caveman instructions to ~/.config/opencode/AGENTS.md&lt;br /&gt;
 # see below&lt;br /&gt;
&lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup --&amp;gt; choose automatic&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5455</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=5455"/>
		<updated>2026-06-15T08:50:59Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
===Codeburn===&lt;br /&gt;
 https://github.com/getagentseal/codeburn&lt;br /&gt;
 npm install -g codeburn&lt;br /&gt;
 # or&lt;br /&gt;
 brew install codeburn&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup --&amp;gt; choose automatic&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5454</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=5454"/>
		<updated>2026-06-12T20:37:27Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Mistral Devstral */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup --&amp;gt; choose automatic&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5453</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=5453"/>
		<updated>2026-06-12T20:25:12Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Mistral Devstral */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 # install&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # setup&lt;br /&gt;
 vibe --setup&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
to set Cavemen style global, create ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5452</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=5452"/>
		<updated>2026-06-12T20:05:08Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Mistral Devstral */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
set Cavemen style in global ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5451</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=5451"/>
		<updated>2026-06-12T20:04:46Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Mistral Devstral */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
set Cavemen style in global AGENTS.md at&lt;br /&gt;
 ~/.vibe/AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5450</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=5450"/>
		<updated>2026-06-12T19:47:48Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Coding Agents */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&lt;br /&gt;
 npm install -g @anthropic-ai/claude-code&lt;br /&gt;
 cd myProject&lt;br /&gt;
 claude&lt;br /&gt;
Uninstall&lt;br /&gt;
 npm uninstall -g @anthropic-ai/claude-code&lt;br /&gt;
&lt;br /&gt;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
Uninstall&lt;br /&gt;
 npm uninstall -g @google/gemini-cli&lt;br /&gt;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5449</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=5449"/>
		<updated>2026-06-12T15:26:14Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Google Antigravity */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
&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;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
	<entry>
		<id>https://entorb.net//wiki/index.php?title=AI_LLM_for_Coding&amp;diff=5448</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=5448"/>
		<updated>2026-06-12T15:25:52Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* OpenCode */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
https://opencode.ai&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
&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;
Website: [https://antigravity.google/download]&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=5447</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=5447"/>
		<updated>2026-06-12T15:25:41Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* RTK - Rust Token Killer */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
Website: [https://opencode.ai]&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
&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;
Website: [https://antigravity.google/download]&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=5446</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=5446"/>
		<updated>2026-06-12T15:25:24Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* RTK - Rust Token Killer */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app wrapps/reduces shell command output&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
Website: [https://opencode.ai]&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
&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;
Website: [https://antigravity.google/download]&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=5445</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=5445"/>
		<updated>2026-06-12T15:24:35Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Context7 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
https://context7.com offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
Website: [https://opencode.ai]&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
&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;
Website: [https://antigravity.google/download]&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=5444</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=5444"/>
		<updated>2026-06-12T15:24:22Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Cavemen style */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
[https://context7.com] offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
Website: [https://opencode.ai]&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
&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;
Website: [https://antigravity.google/download]&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=5443</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=5443"/>
		<updated>2026-06-12T15:24:12Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be reduced to 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
Original Repo: [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;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
[https://context7.com] offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
Website: [https://opencode.ai]&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
&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;
Website: [https://antigravity.google/download]&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=5442</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=5442"/>
		<updated>2026-06-12T15:23:29Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
Repo: [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;br /&gt;
&lt;br /&gt;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be replaced by 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
[https://context7.com] offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&lt;br /&gt;
&lt;br /&gt;
==Coding Agents==&lt;br /&gt;
&lt;br /&gt;
===OpenCode===&lt;br /&gt;
Website: [https://opencode.ai]&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
===Mistral Devstral===&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
===Claude===&lt;br /&gt;
====Web====&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
====Claude Console====&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
===GitHub Copilot===&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
&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;
Website: [https://antigravity.google/download]&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=5441</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=5441"/>
		<updated>2026-06-12T14:25:40Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* RTK - Rust Token Killer */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==OpenCode==&lt;br /&gt;
Website: [https://opencode.ai]&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
==Mistral Devstral==&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
==Claude==&lt;br /&gt;
===Web===&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
===Claude Console===&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
==GitHub Copilot==&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
&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;
Website: [https://antigravity.google/download]&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;
Repo: [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;br /&gt;
&lt;br /&gt;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be replaced by 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
[https://context7.com] offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app&lt;br /&gt;
 brew install rtk&lt;br /&gt;
 brew upgrade rtk&lt;br /&gt;
 rtk init --global&lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&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=5440</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=5440"/>
		<updated>2026-06-12T14:24:07Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==OpenCode==&lt;br /&gt;
Website: [https://opencode.ai]&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
==Mistral Devstral==&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
==Claude==&lt;br /&gt;
===Web===&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&lt;br /&gt;
* Cons: Need to manually upload single files&lt;br /&gt;
&lt;br /&gt;
===Claude Console===&lt;br /&gt;
Claude Console via API and VISA Pre-Paid: [https://console.anthropic.com]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
==GitHub Copilot==&lt;br /&gt;
global config file:&lt;br /&gt;
 .copilot\copilot-instructions.md&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;
&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;
Website: [https://antigravity.google/download]&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;
Repo: [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;br /&gt;
&lt;br /&gt;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be replaced by 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
[https://context7.com] offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app&lt;br /&gt;
 rtk init --global &lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&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=5439</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=5439"/>
		<updated>2026-06-12T14:18:05Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==OpenCode==&lt;br /&gt;
Website: [https://opencode.ai]&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
==Mistral Devstral==&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
==Claude==&lt;br /&gt;
===Web===&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&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]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
==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;
&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;
Website: [https://antigravity.google/download]&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;
Repo: [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;br /&gt;
&lt;br /&gt;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be replaced by 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
[https://context7.com] offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app&lt;br /&gt;
 rtk init --global &lt;br /&gt;
 rtk init --global --opencode&lt;br /&gt;
 rtk init --global --gemini&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=5438</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=5438"/>
		<updated>2026-06-11T03:10:28Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==OpenCode==&lt;br /&gt;
Website: [https://opencode.ai]&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
==Mistral Devstral==&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
==Claude==&lt;br /&gt;
===Web===&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&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]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
==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;
&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;
Website: [https://antigravity.google/download]&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;
Repo: [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;br /&gt;
&lt;br /&gt;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be replaced by 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
[https://context7.com] offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app&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=5437</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=5437"/>
		<updated>2026-06-10T12:39:01Z</updated>

		<summary type="html">&lt;p&gt;Torben: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Opencode==&lt;br /&gt;
Website: [https://opencode.ai]&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
==Mistral Devstral==&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
==Claude==&lt;br /&gt;
===Web===&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&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]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
==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;
&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;
Website: [https://antigravity.google/download]&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;
Repo: [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;br /&gt;
&lt;br /&gt;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be replaced by 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
[https://context7.com] offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;br /&gt;
&lt;br /&gt;
===RTK - Rust Token Killer===&lt;br /&gt;
https://www.rtk-ai.app&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=5436</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=5436"/>
		<updated>2026-06-09T05:13:11Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Cavemen style */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Opencode==&lt;br /&gt;
Website: [https://opencode.ai]&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
==Mistral Devstral==&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
==Claude==&lt;br /&gt;
===Web===&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&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]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
==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;
&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;
Website: [https://antigravity.google/download]&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;
Repo: [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;br /&gt;
&lt;br /&gt;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be replaced by 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
[https://context7.com] offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&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=5435</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=5435"/>
		<updated>2026-06-09T05:12:59Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Google Antigravity */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Opencode==&lt;br /&gt;
Website: [https://opencode.ai]&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
==Mistral Devstral==&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
==Claude==&lt;br /&gt;
===Web===&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&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]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
==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;
&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;
Website: [https://antigravity.google/download]&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;br /&gt;
&lt;br /&gt;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be replaced by 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
[https://context7.com] offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&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=5434</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=5434"/>
		<updated>2026-06-09T05:10:56Z</updated>

		<summary type="html">&lt;p&gt;Torben: /* Web */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Opencode==&lt;br /&gt;
Website: [https://opencode.ai]&lt;br /&gt;
 brew install anomalyco/tap/opencode&lt;br /&gt;
 &lt;br /&gt;
 # add context7 MCP&lt;br /&gt;
 npx ctx7 setup --opencode&lt;br /&gt;
 &lt;br /&gt;
 # initialize Opencode and create/update/improve/compress AGENTS.md file&lt;br /&gt;
  /init&lt;br /&gt;
  &lt;br /&gt;
 # update&lt;br /&gt;
 brew update&lt;br /&gt;
 brew upgrade anomalyco/tap/opencode&lt;br /&gt;
&lt;br /&gt;
For saving of tokens, add Caveman speech to AGENTS.md&lt;br /&gt;
&lt;br /&gt;
==Mistral Devstral==&lt;br /&gt;
Obtain API key from [https://console.mistral.ai/codestral/cli]&lt;br /&gt;
&lt;br /&gt;
 uv tool install mistral-vibe&lt;br /&gt;
 # run via&lt;br /&gt;
 vibe&lt;br /&gt;
&lt;br /&gt;
==Claude==&lt;br /&gt;
===Web===&lt;br /&gt;
Website: [https://claude.ai]&lt;br /&gt;
* Pros: Daily reset free quota&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]&lt;br /&gt;
* Pros: Fully integrated in codebase&lt;br /&gt;
* Cons: Costs money (e.g., 5€ are easily spent)&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;
==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;
&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;
==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;br /&gt;
&lt;br /&gt;
According to [https://medium.com/@KubaGuzik/i-benchmarked-the-viral-caveman-prompt-to-save-llm-tokens-then-my-6-line-version-beat-it-d8e565f95e15]&lt;br /&gt;
It can be replaced by 6 hard coded lines in AGENTS.md:&lt;br /&gt;
 Respond like smart caveman. Cut all filler, keep technical substance.&lt;br /&gt;
 - Drop articles (a, an, the), filler (just, really, basically, actually).&lt;br /&gt;
 - Drop pleasantries (sure, certainly, happy to).&lt;br /&gt;
 - No hedging. Fragments fine. Short synonyms.&lt;br /&gt;
 - Technical terms stay exact. Code blocks unchanged.&lt;br /&gt;
 - Pattern: [thing] [action] [reason]. [next step].&lt;br /&gt;
&lt;br /&gt;
===Context7===&lt;br /&gt;
[https://context7.com] offers an MCP server of latest spec to many languages, enabling the LLM to support latest versions and best practice.&lt;br /&gt;
 npx ctx7 setup&lt;br /&gt;
In prompt write:&lt;br /&gt;
 use context7. do xxx&lt;/div&gt;</summary>
		<author><name>Torben</name></author>
	</entry>
</feed>