Verwenden von Umgebungsvariablen in Python für App-Konfiguration und Geheimnisse

Dieser Artikel erschien ursprünglich am doppler.com und wurde von Ryan Blunden, Developer Advocate bei Doppler, geschrieben.

Als Entwickler haben Sie wahrscheinlich Umgebungsvariablen in der Befehlszeile oder in Shell-Skripten verwendet, aber haben Sie sie auch zur Konfiguration Ihrer Python-Anwendungen verwendet?

Dieser Leitfaden zeigt Ihnen den gesamten Code, der zum Abrufen, Festlegen und Laden von Umgebungsvariablen in Python erforderlich ist, einschließlich der Verwendung zum Bereitstellen von Anwendungskonfigurationen und Geheimnissen.

Nicht vertraut mit Umgebungsvariablen? Schauen Sie sich unsere an ultimative Anleitung zur Verwendung von Umgebungsvariablen in Linux und Mac.

Warum Umgebungsvariablen zum Konfigurieren von Python-Anwendungen verwenden?

Bevor Sie sich mit der Verwendung von Umgebungsvariablen in Python befassen, ist es wichtig zu verstehen, warum sie wohl der beste Weg sind, Anwendungen zu konfigurieren. Die Hauptvorteile sind:

  • Stellen Sie Ihre Anwendung in jeder Umgebung ohne Codeänderungen bereit
  • Stellt sicher, dass Geheimnisse wie API-Schlüssel nicht in den Quellcode gelangen

Umgebungsvariablen haben den zusätzlichen Vorteil, dass sie von Ihrer Anwendung abstrahieren, wie Konfiguration und Geheimnisse bereitgestellt werden.

Schließlich ermöglichen Umgebungsvariablen, dass Ihre Anwendung überall ausgeführt werden kann, sei es für die lokale Entwicklung auf macOS, einen Container in einem Kubernetes-Pod oder Plattformen wie Heroku oder Vercel.

Hier sind einige Beispiele für die Verwendung von Umgebungsvariablen zum Konfigurieren eines Python-Skripts oder einer Python-Anwendung:

  • Satz FLASK_ENV Umgebungsvariable zu “Entwicklung” um den Debug-Modus für eine Flask-Anwendung zu aktivieren
  • Zur Verfügung stellen STRIPE_API_KEY Umgebungsvariable für eine E-Commerce-Site
  • Liefern Sie die DISCORD_TOKEN Umgebungsvariable an eine Discord-Bot-App, damit sie einem Server beitreten kann
  • Setzen Sie umgebungsspezifische Datenbankvariablen wie z DB_USER und DB_PASSWORT Datenbankanmeldeinformationen sind also nicht fest codiert

Wie werden Umgebungsvariablen in Python gefüllt?

Wenn ein Python-Prozess erstellt wird, füllen die verfügbaren Umgebungsvariablen die os.umgebung Objekt, das sich wie ein Python-Wörterbuch verhält. Das bedeutet, dass:

  • Alle Änderungen an Umgebungsvariablen, die nach der Erstellung des Python-Prozesses vorgenommen wurden, werden nicht im Python-Prozess widergespiegelt.
  • Alle in Python vorgenommenen Änderungen an Umgebungsvariablen wirken sich nicht auf Umgebungsvariablen im übergeordneten Prozess aus.

Nachdem Sie nun wissen, wie Umgebungsvariablen in Python gefüllt werden, sehen wir uns an, wie Sie darauf zugreifen.

So erhalten Sie eine Python-Umgebungsvariable

Der Zugriff auf Umgebungsvariablen in Python erfolgt über die os. Umgebung Objekt.

Das os.umgebung Das Objekt scheint wie ein Wörterbuch zu sein, ist aber anders, da Werte nur Zeichenfolgen sein können und nicht für JSON serialisierbar sind.

Sie haben einige Optionen, wenn es darum geht, auf die zu verweisen os.umgebung Objekt:

# 1. Standard way
import os
# os.environ['VAR_NAME']

# 2. Import just the environ object
from os import environ
# environ['VAR_NAME']

# 3. Rename the `environ` to env object for more concise code
from os import environ as env
# env['VAR_NAME']

Ich persönlich bevorzuge Version 3, da sie prägnanter ist, werde aber bei der Verwendung bleiben os.umgebung für diesen Artikel.

Der Zugriff auf eine bestimmte Umgebungsvariable in Python kann auf drei Arten erfolgen, je nachdem, was passieren soll, wenn eine Umgebungsvariable nicht vorhanden ist.

Lassen Sie uns anhand einiger Beispiele untersuchen.

Option 1: Erforderlich ohne Standardwert

Sollte Ihre App abstürzen, wenn eine Umgebungsvariable nicht gesetzt ist, dann greifen Sie direkt darauf zu:

print(os.environ['HOME']
# >> '/home/dev'

print(os.environ['DOES_NOT_EXIST']
# >> Will raise a KeyError exception

Beispielsweise sollte eine Anwendung nicht gestartet werden können, wenn eine erforderliche Umgebungsvariable nicht gesetzt ist und kein Standardwert bereitgestellt werden kann, z. B. ein Datenbankkennwort.

Wenn anstelle der Vorgabe Schlüsselfehler Wenn eine Ausnahme ausgelöst wird (was nicht mitteilt, warum Ihre App nicht gestartet werden konnte), können Sie die Ausnahme erfassen und eine hilfreiche Nachricht ausdrucken:

import os
import sys

# Ensure all required environment variables are set
try:  
  os.environ['API_KEY']
except KeyError: 
  print('[error]: `API_KEY` environment variable required')
  sys.exit(1)

Option 2: Erforderlich mit Standardwert

Sie können einen Standardwert zurückgeben lassen, wenn eine Umgebungsvariable nicht vorhanden ist, indem Sie die verwenden os.environ.get -Methode und Bereitstellen des Standardwerts als zweiten Parameter:

# If HOSTNAME doesn't exist, presume local development and return localhost
print(os.environ.get('HOSTNAME', 'localhost')

Wenn die Variable nicht existiert und Sie verwenden os.environ.get ohne Standardwert, Keiner ist zurück gekommen

assert os.environ.get('NO_VAR_EXISTS') == None

Option 3: Bedingte Logik, wenn Wert vorhanden ist

Möglicherweise müssen Sie überprüfen, ob eine Umgebungsvariable vorhanden ist, kümmern sich aber nicht unbedingt um ihren Wert. Beispielsweise kann Ihre Anwendung in einen “Debug-Modus” versetzt werden, wenn die DEBUGGEN Umgebungsvariable gesetzt.

Sie können überprüfen, ob nur eine Umgebungsvariable vorhanden ist:

if 'DEBUG' in os.environ:
  print('[info]: app is running in debug mode')

Oder überprüfen Sie, ob es einem bestimmten Wert entspricht:

if os.environ.get('DEBUG') == 'True':
  print('[info]: app is running in debug mode')

So legen Sie eine Python-Umgebungsvariable fest

Das Festlegen einer Umgebungsvariablen in Python ist dasselbe wie das Festlegen eines Schlüssels für ein Wörterbuch:

os.environ['TESTING'] = 'true'

Was macht os.umgebung Anders als bei einem Standard-Wörterbuch sind nur String-Werte erlaubt:

os.environ['TESTING'] = True
# >> TypeError: str expected, not bool

In den meisten Fällen muss Ihre Anwendung nur Umgebungsvariablen abrufen, aber es gibt auch Anwendungsfälle, um sie festzulegen.

Bauen Sie zum Beispiel eine DB_URL Umgebungsvariable beim Anwendungsstart mit DB_HOST , DB_PORT , DB_USER , DB_PASSWORT und DB_NAME Umgebungsvariablen:

os.environ['DB_URL'] = 'psql://{user}:{password}@{host}:{port}/{name}'.format(
  user=os.environ['DB_USER'],
  password=os.environ['DB_PASSWORD'],
  host=os.environ['DB_HOST'],
  port=os.environ['DB_PORT'],
  name=os.environ['DB_NAME']
)

Ein weiteres Beispiel ist das Festlegen einer Variablen auf einen Standardwert basierend auf dem Wert einer anderen Variablen:

# Set DEBUG and TESTING to 'True' if ENV is 'development'
if os.environ.get('ENV') == 'development':
  os.environ.setdefault('DEBUG', 'True') # Only set to True if DEBUG not set
  os.environ.setdefault('TESTING', 'True') # Only set to True if TESTING not set

So löschen Sie eine Python-Umgebungsvariable

Wenn Sie eine Python-Umgebungsvariable löschen müssen, verwenden Sie die os.environ.pop Funktion:

Zur Erweiterung unserer DB_URL Beispiel oben, möchten Sie vielleicht das andere löschen DB_ vorangestellte Felder, um sicherzustellen, dass die App nur über eine Verbindung zur Datenbank herstellen kann DB_URL :

Ein weiteres Beispiel ist das Löschen einer Umgebungsvariablen, sobald sie nicht mehr benötigt wird:

auth_api(os.environ['API_KEY']) # Use API_KEY
os.environ.pop('API_KEY') # Delete API_KEY as it's no longer needed

So listen Sie Python-Umgebungsvariablen auf

So zeigen Sie alle Umgebungsvariablen an:

Die Ausgabe dieses Befehls ist jedoch schwer zu lesen, da sie als ein riesiges Wörterbuch gedruckt wird.

Ein besserer Weg ist, eine Convenience-Funktion zu erstellen, die konvertiert os.umgebung in ein tatsächliches Wörterbuch, damit wir es für den hübschen Druck in JSON serialisieren können:

import os
import json

def print_env():
  print(json.dumps({ **{},** os.environ}, indent=2))

Warum Standardwerte für Umgebungsvariablen vermieden werden sollten

Sie werden vielleicht überrascht sein zu erfahren, dass es am besten ist Vermeiden Sie es, Standardwerte anzugeben so viel wie möglich. Wieso den?

Standardwerte können das Debuggen einer falsch konfigurierten Anwendung erschweren, da die endgültigen Konfigurationswerte wahrscheinlich eine Kombination aus hartcodierten Standardwerten und Umgebungsvariablen sind.

Wenn Sie sich ausschließlich auf Umgebungsvariablen (oder so weit wie möglich) verlassen, haben Sie eine einzige Quelle für die Wahrheit darüber, wie Ihre Anwendung konfiguriert wurde, was die Fehlerbehebung vereinfacht.

Verwenden einer .env-Datei für Python-Umgebungsvariablen

Mit zunehmender Größe und Komplexität einer Anwendung wächst auch die Anzahl der Umgebungsvariablen.

Viele Projekte erleben wachsende Schmerzen bei der Verwendung von Umgebungsvariablen für die App-Konfiguration und Geheimnisse, da es keine klare und konsistente Strategie für deren Verwaltung gibt, insbesondere bei der Bereitstellung in mehreren Umgebungen.

Eine einfache (aber nicht leicht skalierbare) Lösung ist die Verwendung von a .env Datei, die alle Variablen für eine bestimmte Umgebung enthält.

Dann würden Sie eine Python-Bibliothek wie z python-dotenv die zu analysieren .env Datei und füllen Sie die os.umgebung Objekt.

Um mitzumachen, erstellen und aktivieren Sie eine neue virtuelle Umgebunginstallieren Sie dann die python-dotenv Bibliothek:

python3 -m venv ~/.virtualenvs/doppler-tutorial # 1. Create
source ~/.virtualenvs/doppler-tutorial/bin/activate # 2. Activate
pip install python-dotenv # 3. Install dotenv

Speichern Sie nun das Folgende in einer Datei mit dem Namen .env (Beachten Sie, dass es sich um dieselbe Syntax zum Festlegen einer Variablen in der Shell handelt):

API_KEY="357A70FF-BFAA-4C6A-8289-9831DDFB2D3D"
HOSTNAME="0.0.0.0"
PORT="8080"

Speichern Sie dann Folgendes unter dotenv-test.py :

# Rename `os.environ` to `env` for nicer code
from os import environ as env

from dotenv import load_dotenv
load_dotenv()

print('API_KEY: {}'.format(env['API_KEY']))
print('HOSTNAME: {}'.format(env['HOSTNAME']))
print('PORT: {}'.format(env['PORT']))

Dann renne dotenv-test.py Zum Testen werden die Umgebungsvariablen gefüllt:

python3 dotenv-test.py
# >> API_KEY: 357A70FF-BFAA-4C6A-8289-9831DDFB2D3D
# >> HOSTNAME: 0.0.0.0
# >> PORT: 8080

Während .env Dateien sind am Anfang einfach und leicht zu handhaben, sie verursachen auch eine Reihe neuer Probleme, wie zum Beispiel:

  • Wie zu halten .env Dateien für jeden Entwickler in seiner lokalen Umgebung synchron?
  • Wenn es aufgrund einer Fehlkonfiguration zu einem Ausfall kommt, greifen Sie direkt auf den Container oder die VM zu, um den Inhalt der .env kann für die Fehlerbehebung erforderlich sein.
  • Wie generiert man eine .env Datei für einen CI/CD-Job wie GitHub Actions, ohne die .env Datei ins Repository?
  • Wenn eine Mischung aus Umgebungsvariablen und a .env Datei verwendet wird, könnte die einzige Möglichkeit, die endgültigen Konfigurationswerte zu bestimmen, darin bestehen, die Anwendung selbst zu untersuchen.
  • Onboarding eines Entwicklers durch Teilen einer unverschlüsselten .env Datei mit potenziell sensiblen Daten in einer Chat-Anwendung wie Slack könnte Sicherheitsprobleme aufwerfen.

Dies sind nur einige der Gründe, warum wir empfehlen Abkehr von .env-Dateien und mit so etwas wie Doppler stattdessen.

Doppler bietet ein zugriffsgesteuertes Dashboard zum Verwalten von Umgebungsvariablen für jede Umgebung mit einem Einfach zu bedienende CLI für den Zugriff auf Konfiguration und Geheimnisse das für jede Sprache, jedes Framework und jede Plattform funktioniert.

Zentralisieren Sie die Anwendungskonfiguration mithilfe einer Python-Datenstruktur

Das Erstellen einer konfigurationsspezifischen Datenstruktur abstrahiert davon, wie die Konfigurationswerte festgelegt werden, welche Felder Standardwerte haben (falls vorhanden) und bietet stattdessen eine einzige Schnittstelle für den Zugriff auf Konfigurationswerte os.umgebung in Ihrer Codebasis verstreut sein.

Nachfolgend finden Sie eine einigermaßen voll funktionsfähige Lösung, die Folgendes unterstützt:

  • Benötigte Felder
  • Optionale Felder mit Standardwerten
  • Typprüfung und Typumwandlung

Um es auszuprobieren, speichern Sie diesen Code unter config.py :

import os
from typing import get_type_hints, Union
from dotenv import load_dotenv

load_dotenv()

class AppConfigError(Exception):
    pass

def _parse_bool(val: Union[str, bool]) -> bool: # pylint: disable=E1136 
    return val if type(val) == bool else val.lower() in ['true', 'yes', '1']

# AppConfig class with required fields, default values, type checking, and typecasting for int and bool values
class AppConfig:
    DEBUG: bool = False
    ENV: str="production"
    API_KEY: str
    HOSTNAME: str
    PORT: int

    """
    Map environment variables to class fields according to these rules:
      - Field won't be parsed unless it has a type annotation
      - Field will be skipped if not in all caps
      - Class field and environment variable name are the same
    """
    def __init__ (self, env):
        for field in self. __annotations__ :
            if not field.isupper():
                continue

            # Raise AppConfigError if required field not supplied
            default_value = getattr(self, field, None)
            if default_value is None and env.get(field) is None:
                raise AppConfigError('The {} field is required'.format(field))

            # Cast env var value to expected type and raise AppConfigError on failure
            try:
                var_type = get_type_hints(AppConfig)[field]
                if var_type == bool:
                    value = _parse_bool(env.get(field, default_value))
                else:
                    value = var_type(env.get(field, default_value))

                self. __setattr__ (field, value)
            except ValueError:
                raise AppConfigError('Unable to cast value of "{}" to type "{}" for "{}" field'.format(
                    env[field],
                    var_type,
                    field
                )
            )

    def __repr__ (self):
        return str(self. __dict__ )

# Expose Config object for app to import
Config = AppConfig(os.environ)

Das Konfig Objekt freigelegt config.py wird dann von verwendet app.py unter:

from config import Config

print('ENV: {}'.format(Config.ENV))
print('DEBUG: {}'.format(Config.DEBUG))
print('API_KEY: {}'.format(Config.API_KEY))
print('HOSTNAME: {}'.format(Config.HOSTNAME))
print('PORT: {}'.format(Config.PORT))

Stellen Sie sicher, dass Sie die haben .env Datei, die noch von früher gespeichert wurde, und führe dann Folgendes aus:

python3 app.py 
# >> ENV: production
# >> DEBUG: False
# >> API_KEY: 357A70FF-BFAA-4C6A-8289-9831DDFB2D3D
# >> HOSTNAME: 0.0.0.0
# >> PORT: 8080

Du kannst Sehen Sie sich diesen Code auf GitHub an und wenn Sie nach einer umfassenderen typsicheren Konfigurationslösung suchen, dann schauen Sie sich die ausgezeichnete an Pydantische Bibliothek.

Zusammenfassung

Tolle Arbeit! Jetzt wissen Sie, wie Sie Umgebungsvariablen in Python für Anwendungskonfigurationen und Geheimnisse verwenden.

Obwohl wir etwas voreingenommen sind, ermutigen wir Sie dazu versuche es mal mit Doppler als Quelle der Wahrheit für Ihre Anwendungsumgebungsvariablen, und es ist kostenlos, mit unserem Community-Plan (unbegrenzte Projekte und Geheimnisse) zu beginnen.

Wir haben auch ein Tutorial für den Aufbau eines Zufälliger Mandalorion-GIF-Generatordie die in diesem Artikel gezeigten Techniken in die Praxis umsetzt.

Ich hoffe, Ihnen hat der Beitrag gefallen, und wenn Sie Fragen oder Feedback haben, würden wir uns gerne mit Ihnen in unserem unterhalten Gemeinschaftsforum.

Großen Dank an Die Steveis, Olivier Pilotte, Jakob Kasnerund Alex Halle für ihren Beitrag und ihre Bewertung!

Foto von Hitesh Choudhary auf Unsplash.

Similar Posts

Leave a Reply

Your email address will not be published.