Python 配置管理的幾種方式

1、 爲何要使用配置

若是咱們在較複雜的項目中不使用配置文件,咱們可能會面臨下面的狀況:python

  • 你決定更改你的項目中數據庫的 host, 由於你要將項目從測試環境轉移到實際的上產環境中。若是你的項目中多個位置用到了這個 host,那你不得不一個一個找到這些位置再修改爲新的 host。花了半天,而後過了一天,你發現項目在生產環境有些問題,須要從新移回測試環境,你得再次修改,這樣工做很繁瑣很不優雅。
  • 你開發了一個很棒的開源項目,你想將其放到版本控制系統例如github上,可是你服務器的主機的地址、帳號、密碼也都上傳上去了,可是你沒有意識到,直到有個 bad guy 拿到了你的信息,從你的服務器竊取信息、攻擊你的服務器,讓你產生了極大的損失。而後你想把程序改動一下,把涉密的信息好比地址密碼都刪掉,但是因爲版本控制的緣由,別人依然能看到你之前版本的代碼。因而你不得不改掉你的帳戶、密碼等,真的是個悲傷的開源項目經歷。

可是,若是你使用了配置管理呢,那會有如下幾個優勢:mysql

  • 這樣就提升了代碼的重用性,再也不每次都去修改代碼內部
  • 這意味着其餘不太懂你代碼內部的人也可使用你的項目,只用根據需求更改配置便可
  • 有利於團隊協做
  • 有利於安全數據/祕密數據的管理

2、Python 中進行配置管理的幾種方式

因爲使用 Python 較多,所以基於 Python 進行配置管理的相關說明,固然其餘語言也都是大同小異,主要思想仍是不變。git

2.1 使用 Python 內置的數據結構(如字典)

2.1.1單個文件下的單個配置

咱們很天然就能想到這一點,例如如下代碼:github

# main.py

import pymysql

DATABASE_CONFIG  = {
    'host':'localhost',
    'dbname':'db',
    'user':'user',
    'password':'pwd',
    'port':3306
}

def connect_db_do_something1(dbname):
    if dbname != config.DATABASE_CONFIG['dbname']:
        raise ValueError("Couldn't not find DB with given name")
    conn = pymysql.connect(host=config.DATABASE_CONFIG['host'],
                           user=config.DATABASE_CONFIG['user'],
                           password=config.DATABASE_CONFIG['password'],
                           db=config.DATABASE_CONFIG['dbname'])
    '''
    do something 1
    '''

def connect_db_do_something2(dbname):
    if dbname != config.DATABASE_CONFIG['dbname']:
        raise ValueError("Couldn't not find DB with given name")
    conn = pymysql.connect(host=config.DATABASE_CONFIG['host'],
                           user=config.DATABASE_CONFIG['user'],
                           password=config.DATABASE_CONFIG['password'],
                           db=config.DATABASE_CONFIG['dbname'])
    '''
    do something 2
    '''

connect_db_do_something1('db')
connect_db_do_something2('db')

在上面的代碼中,咱們能夠看到,同一數據庫配置,咱們反覆使用了兩次,若是咱們須要更改數據庫相關的數據如password,咱們不須要在兩個方法內部修改,而是隻用修改DATABASE_CONFIG字典中的相關值便可。和之前沒有配置管理的時候相比,減小了太多的工做量了。web

2.1.2多個文件下的單個配置

可是當你的項目開始變得複雜的時候,你的文件就不止一個這麼簡單了,這時候若是我須要在 main2.py 裏面須要用 DATABASE_CONFIG 的時候就不是很方便了,由於若是直接 import main 的時候,雖然可以使用 main.DATABASE_CONFIG ,但同時 mian.py 中的sql

connect_db_do_something1('db')
connect_db_do_something2('db')

也被執行了,這可不是咱們想看到的,所以咱們有了新的需求,能在同一個項目下的不一樣文件裏簡單快速的導入咱們的數據庫配置 DATABASE_CONFIG,因而咱們想出了下面的方法來解決這個問題:數據庫

# config.py
DATABASE_CONFIG = {
    'host': 'localhost',
    'dbname': 'db',
    'user': 'user',
    'password': 'pwd',
    'port': 3306
}

# main1.py
import pymysql
import config

def connect_db_do_something1(dbname):
    if dbname != config.DATABASE_CONFIG['dbname']:
        raise ValueError("Couldn't not find DB with given name")
    conn = pymysql.connect(host=config.DATABASE_CONFIG['host'],
                           user=config.DATABASE_CONFIG['user'],
                           password=config.DATABASE_CONFIG['password'],
                           db=config.DATABASE_CONFIG['dbname'])
    '''
    do something 1
    '''
connect_db_do_something1('db')

# main2.py
import pymysql
import config

def connect_db_do_something2(dbname):
    if dbname != config.DATABASE_CONFIG['dbname']:
        raise ValueError("Couldn't not find DB with given name")
    conn = pymysql.connect(host=config.DATABASE_CONFIG['host'],
                           user=config.DATABASE_CONFIG['user'],
                           password=config.DATABASE_CONFIG['password'],
                           db=config.DATABASE_CONFIG['dbname'])
    '''
    do something 2
    '''
connect_db_do_something2('db')

按照上面的代碼,咱們能夠在兩個不一樣的文件 main1.py 和 main2.py 中分別引用 config.py 中配置了,咱們的配置管理看起來更進一步了。編程

2.1.3 單個文件下的多個配置

有可能咱們的項目須要多個配置文件,好比測試環境和生產環境。先從單個文件講起,咱們能夠採用以下解決方案:json

# config.py
class Config:
    APP_NAME = 'myapp'
    SECRET_KEY = 'secret-key-of-myapp'
    ADMIN_NAME = 'administrator'

    AWS_DEFAULT_REGION = 'ap-northeast-2'
    
    STATIC_PREFIX_PATH = 'static'
    ALLOWED_IMAGE_FORMATS = ['jpg', 'jpeg', 'png', 'gif']
    MAX_IMAGE_SIZE = 5242880 # 5MB


class DevelopmentConfig(Config):
    DEBUG = True
    
    AWS_ACCESS_KEY_ID = 'aws-access-key-for-dev'
    AWS_SECERT_ACCESS_KEY = 'aws-secret-access-key-for-dev'
    AWS_S3_BUCKET_NAME = 'aws-s3-bucket-name-for-dev'
    
    DATABASE_URI = 'database-uri-for-dev'


class TestConfig(Config):
    DEBUG = True
    TESTING = True
    
    AWS_ACCESS_KEY_ID = 'aws-access-key-for-test'
    AWS_SECERT_ACCESS_KEY = 'aws-secret-access-key-for-test'
    AWS_S3_BUCKET_NAME = 'aws-s3-bucket-name-for-test'
    
    DATABASE_URI = 'database-uri-for-dev'
  

class ProductionConfig(Config):
    DEBUG = False

    AWS_ACCESS_KEY_ID = 'aws-access-key-for-prod'
    AWS_SECERT_ACCESS_KEY = 'aws-secret-access-key-for-prod'
    AWS_S3_BUCKET_NAME = 'aws-s3-bucket-name-for-prod'

    DATABASE_URI = 'database-uri-for-dev'

# main.py
import sys
import config
import sys
import config

'''
do some important things
'''

if __name__ == '__main__':
    env = sys.argv[1] if len(sys.argv) > 2 else 'dev'
    
    if env == 'dev':
        app.config = config.DevelopmentConfig
    elif env == 'test':
        app.config = config.TestConfig
    elif env == 'prod':
        app.config = config.ProductionConfig
    else:
        raise ValueError('Invalid environment name')

這樣咱們就能夠從一個配置文件中獲取不一樣級別的不一樣配置了。安全

2.1.4 多個文件下的多個配置

和上面相似,只不過換成了從不一樣的文件中讀取同一個配置文件的不一樣配置:

# config.py
class Config:
    APP_NAME = 'myapp'
    SECRET_KEY = 'secret-key-of-myapp'
    ADMIN_NAME = 'administrator'

    AWS_DEFAULT_REGION = 'ap-northeast-2'
    
    STATIC_PREFIX_PATH = 'static'
    ALLOWED_IMAGE_FORMATS = ['jpg', 'jpeg', 'png', 'gif']
    MAX_IMAGE_SIZE = 5242880 # 5MB

    
class DevelopmentConfig(Config):
    DEBUG = True
    
    AWS_ACCESS_KEY_ID = 'aws-access-key-for-dev'
    AWS_SECERT_ACCESS_KEY = 'aws-secret-access-key-for-dev'
    AWS_S3_BUCKET_NAME = 'aws-s3-bucket-name-for-dev'
    
    DATABASE_URI = 'database-uri-for-dev'
    
class TestConfig(Config):
    DEBUG = True
    TESTING = True
    
    AWS_ACCESS_KEY_ID = 'aws-access-key-for-test'
    AWS_SECERT_ACCESS_KEY = 'aws-secret-access-key-for-test'
    AWS_S3_BUCKET_NAME = 'aws-s3-bucket-name-for-test'
    
    DATABASE_URI = 'database-uri-for-dev'
  

class ProductionConfig(Config):
    DEBUG = False

    AWS_ACCESS_KEY_ID = 'aws-access-key-for-prod'
    AWS_SECERT_ACCESS_KEY = 'aws-secret-access-key-for-prod'
    AWS_S3_BUCKET_NAME = 'aws-s3-bucket-name-for-prod'

    DATABASE_URI = 'database-uri-for-dev'


class CIConfig:
    SERVICE = 'travis-ci'
    HOOK_URL = 'web-hooking-url-from-ci-service'


# main1.py
import config

dev_config = config.DevelopmentConfig
'''
do something
'''

# main2.py
import config

app.ci = config.CIConfig
'''
do otherthing
'''

這樣使用更加靈活了,從不一樣的文件裏讀取不一樣的配置,而咱們對於配置的增刪改只須要在 config.py 中進行,配置管理技能再次進階!

2.2 使用外部配置文件

比起使用 Python 內建的數據結構,更加通用的方法是使用外部配置文件,由於這些文件只會被視爲配置文件,而不會像 config.py 同樣有代碼的屬性。外部配置文件的格式多種多樣,咱們在使用它的時候會根據文件格式有不一樣的讀取方式。例如:*.yaml 或者 *.yml*.json*.cfg*.conf*.ini , 甚至是你自定義的文件 *.yourname

2.2.1 YAML

YAML(/ˈjæməl/,尾音相似camel駱駝)是一個可讀性高,用來表達數據序列化的格式。YAML參考了其餘多種語言,包括:C語言、Python、Perl,並從XML、電子郵件的數據格式(RFC 2822)中得到靈感。Clark Evans在2001年首次發表了這種語言[1],另外Ingy döt Net與Oren Ben-Kiki也是這語言的共同設計者[2]。當前已經有數種編程語言或腳本語言支持(或者說解析)這種語言。 ----- 中文維基百科

YAML 看起來像下面這種格式:

mysql:
    host: localhost
    dbname: db
    user: user
    passwd: pwb
    port: 3306
other:
    host: other_host
    dbname: other_db
    user: other_user
    passwd: other_pwb
    port: 3306

能夠經過相似下面的代碼來讀取裏面的內容:

import yaml

with open("config.yml", 'r') as ymlfile:
    cfg = yaml.load(ymlfile)

print(cfg['mysql'])

將輸出如下內容

{'host': 'localhost',
 'dbname': 'db',
 'user': 'user',
 'password': 'pwd',
 'port': 3306}

若是須要從 python 寫入配置到 YAML 也很容易,只須要使用 yaml.dump(dict) 便可,dict 指的是配置的字典。更加詳細的內容能夠查看 PyYAML Documentation

2.2.2 INI

INI文件是一個無固定標準格式的配置文件。它以簡單的文字與簡單的結構組成,經常使用在Windows操做系統,或是其餘操做系統上,許多程序也會採用INI文件作爲設置程序之用。Windows操做系統後來以註冊表的形式取代掉INI檔。INI文件的命名來源,是取自英文「初始(Initial)」的首字縮寫,正與它的用途——初始化程序相應。有時候,INI文件也會以不一樣的擴展名,如「.CFG」、「.CONF」、或是「.TXT」代替。 ----- 中文維基百科

它長的像這樣:

[mysql]
host=localhost
dbname=db
user=user
passwd=pwd
port=3306

[other]
host=other_host
dbname=other_db
user=other_user
passwd=other_pwb
port=3306

經過如下代碼能夠讀取它:

import configparser

config = configparser.ConfigParser()
config.read("config.ini")
host = config['mysql']['host']
print(host)

這將輸出 INI 配置文件中的 mysql section 中的 host 值

要寫入 INI 配置文件也很簡單,參考以下代碼便可:

import configparser
config = configparser.ConfigParser()
config.read("config.ini")
config['mysql']['test_str'] = 'a test string'
config.write(open("ini", "w"))

如今的配置文件會變成:

[mysql]
host = localhost
dbname = db
user = user
passwd = pwd
port = 3306
test_str = a test string

[other]
host=other_host
dbname=other_db
user=other_user
passwd=other_pwb
port=3306

2.2.3 JSON

JSON是JavaScript對象表示法的縮寫。它很是普遍,所以對許多編程語言都有很好的支持。它的格式你們也很眼熟,看起來和 Python 中的字典很像:

{
    "mysql":{
        "host": "localhost",
        "dbname": "db",
        "user" : "user",
        "password": "pwd",
        "port": 3306
    },
    "other":{
        "host": "other_host",
        "dbname": "other_db",
        "user": "other_user",
        "passwd": "other_pwb",
        "port": 3306
    }

你能夠參考如下代碼讀取:

import json

with open('config.json') as json_data_file:
    config = json.load(json_data_file)

host = config['mysql']['host']
print(host)

# output: localhost

要將配置寫入json中也很簡單,參考如下代碼:

import json

with open('config.json') as json_data_file:
    config = json.load(json_data_file)

config['mysql']['test_str'] = 'a test string'

with open('config.json', 'w') as outfile:
    json.dump(config, outfile)

這樣就會獲得增長了配置的json文件了:

{
    "mysql":{
        "host": "localhost",
        "dbname": "db",
        "user" : "user",
        "password": "pwd",
        "port": 3306,
        "test_str" : "a test string"
    },
    "other":{
        "host": "other_host",
        "dbname": "other_db",
        "user": "other_user",
        "passwd": "other_pwb",
        "port": 3306
    }
}

其餘格式的文件大多如此,就不贅述了。而且外部的配置文件中也能夠配置多個配置(mysql, other等)

2.3 使用環境變量

可是,回到咱們開篇講的問題,以上的兩種配置管理方案(使用 Python 內置的數據結構、使用外部配置文件) 都忽略了兩個問題:

其一,咱們如何應對安全數據直接曝光於公衆的可能問題呢,若是咱們須要使用版本控制系統例如 Github,或許咱們能夠嘗試將 config.py 文件放到 .gitignore 裏面,但咱們若是哪一天修改了倉庫,忘了將 config.py 忽略掉而 push 到了GitHub 上,那麼咱們的安全敏感信息仍然會向公衆泄露,因爲版本控制的存在,即便你刪掉了還會有這條提交記錄,處理起來會很麻煩。

其二,若是咱們要在咱們本地新開一個項目,這個項目也須要引用同樣的數據庫配置文件,或許咱們能夠找到第一個項目的文件夾,複製出 config.py 到 新的項目文件夾。嗯,看起來可行,可是,若是你要新開十幾個項目呢,幾百個項目呢?

所以咱們能夠引入下一種配置管理的方式,對解決上面提出的兩個問題都是較爲友好的解決方案,即便用環境變量,各類開發環境(Win、Mac、Linux)的系統環境變量的設置方式有所不一樣,能夠參考這篇文章。

另外 PyCharm 和 VS Code 有更加方便的配置方式,能夠爲不一樣的項目分配不一樣的設置。

PyCharm 中,在菜單 Run->Edit configurations 中,手動設置Environment variables

VS Code 中,在 Setting 中搜索 env ,在 Terminal 中選擇你的操做系統相關的Terminal > Integrated > Env: Your OS ,點進 settings.json 進行添加

使用環境變量配置值不用做爲單獨的文件進行管理,所以有較小的安全風險,它很容易使用,能夠在你的開發環境中的任何項目任何代碼庫中使用,可是它的管理方式可能有些複雜。有些環境沒法使用環境變量,好比Apache,Nginx等Web服務器,這時候就須要採用其餘的方式。

2.4 使用動態加載

這種方法比利用 Python 內置的數據結構更加先進,內置數據結構的方法要求配置文件必需要在能夠直接 import 的路徑上。可是動態加載中,配置文件沒必要在可直接導入的路徑上,甚至能夠位於其餘存儲庫中,這樣的話,配置文件就和項目分隔開了,其餘的項目也能夠動態加載這個配置文件,例如:

# /opt/settings/config.py
DATABASE_CONFIG = {
    'host': 'localhost',
    'dbname': 'company',
    'user': 'user',
    'password': 'password',
    'port': 3306
}

# main.py
import sys
import pymysql

sys.path.append('/opt/settings')
import config

def connect_db(dbname):
    if dbname != config.DATABASE_CONFIG['dbname']:
        raise ValueError("Couldn't not find DB with given name")
    conn = pymysql.connect(host=config.DATABASE_CONFIG['host'],
                           user=config.DATABASE_CONFIG['user'],
                           password=config.DATABASE_CONFIG['password'],
                           db=config.DATABASE_CONFIG['dbname'])
    return conn

connect_db('company')

3、總結

以上概括了四種配置管理的方式,整體來講沒有優劣之分,看我的的須要,甚至上面的幾種方法能夠混合使用,對於一些軟件項目,它自身可能就提供了相關的變量配置入口,好比 airbnb 的 Airflow 。並且,當系統規模很是大時,最好使用主要提供配置管理的第三方工具或服務。


注:本文爲轉載,原文地址:Python配置管理的幾種方式

相關文章
相關標籤/搜索