GAEアプリをGitHubに上げるために、app.yamlやAPIキーを書き換えるためのツールを作ってみた

以前、GAEアプリをGitHubに上げるためのツールとして、以下のものを作ってみた。
GitHubに上げるために、app.yamlのapplicationを変更するツールの作成 - メモ的な思考的な


しかし、GitHubで公開できない情報はapp.yamlのapplicationだけではなく、各種APIのキーもあることに気づいたため、APIのキーも削除するようなツールを作ってみた。

■環境

■前提・仕様

  • 各種APIのキーは「api.yaml」へと、以下の形で記載されていること。
<プログラム中で取得するAPIのキー名称>: <値>
  • .pycファイルはコピーしない

■実装するときに悩んだことと対応

yamlファイルの順番を崩さずに読込→出力する方法はないか?

何もせずにPyYAMLでloadとdumpをすると、loadした段階で辞書型で取り込まれるため、元々のyamlファイルの順番が崩れてしまう(辞書型は順序を保持しない)。
崩さないようにするには、Python2.7であればOrderedDictが使えるため、それとPyYAMLの機能を組み合わせればよい。
方法としては、以下の方法をそのまま利用した。ありがとうございます。
OrderedDictをYAMLに変換する - スコトプリゴニエフスク通信


使用例は、以下。

import sys
import yaml
from collections import OrderedDict

# OrderedDictを出力するための関数
def represent_odict(dumper, instance):
     return dumper.represent_mapping(u'tag:yaml.org,2002:map', instance.items())

# OrderedDictを読み込むための関数
def construct_odict(loader, node):
    return OrderedDict(loader.construct_pairs(node))



def main(path, convertedPath):
    # 記述順で読み込めるようにadd_constructorで設定
    yaml.add_constructor(u'tag:yaml.org,2002:map', construct_odict)
    data = yaml.load(open(path).read().decode('utf-8'))

    data['application'] = u'<your application id>'

    # OrderedDictを出力できるように、add_representerにて設定
    yaml.add_representer(OrderedDict, represent_odict)
    yaml.dump(
        data,
        file(convertedPath, 'w'),
        default_flow_style=False,
        encoding='utf-8',
        allow_unicode=True
        )
C#のFuncデリゲートのようなものはないか

app.yamlapi.yamlの編集方法だけ別で、後は同じ処理であったため、Funcみたいなものはないかを探したが、そもそもPythonでは関数も引数として渡せるとのこと。楽ですね。
初めてのPython(4) 関数でさえオブジェクトであるPython



特定条件でファイルを検索する方法

そのものがありました。
http://www.nishiohirokazu.org/blog/2006/03/python.html


os.walk()については、以下を参照。
145:ディレクトリ内のファイルを再帰的に処理



ファイルのコピー

shutil.copyfile()で良い模様。
ファイルをコピーする - Python Tips



ソースコード

gistにもアップ(GAEtoGitHubConverter.py)。

# -*- coding: utf-8 -*-

import sys
import os
import shutil
import yaml
from collections import OrderedDict

# OrderedDictを出力するための関数
def represent_odict(dumper, instance):
     return dumper.represent_mapping(u'tag:yaml.org,2002:map', instance.items())

# OrderedDictを読み込むための関数
def construct_odict(loader, node):
    return OrderedDict(loader.construct_pairs(node))



class Converter(object):
    def __init__(self, dirFrom, dirTo):
        self._dirFrom = dirFrom
        self._dirTo = dirTo


    def convert(self):
        for (root, dirs, files) in os.walk(self._dirFrom):
            # コピー先のディレクトリがなければ、作成する
            self._create_dir(root)

            for f in files:
                # .pyc終わりなら、コピーしない
                if f[-4:] == '.pyc':
                    continue

                filepathFrom = os.path.join(root, f)

                if f == 'app.yaml':
                    # app.yamlなら、読み込んで、applicationをリプレイスして、コピー
                    # 編集する関数(self._convert_app_yaml)は、引数に渡して、_convert_yaml内で実行する
                    self._convert_yaml(filepathFrom, self._convert_app_yaml)

                elif f == 'api.yaml':
                    # api.yamlなら、読み込んで、すべてを''へとリプレイスして、コピー
                    self._convert_yaml(filepathFrom, self._convert_api_yaml)

                else:
                    # 他はそのままコピー
                    filepathTo = self._create_filepath_to(filepathFrom)
                    shutil.copyfile(filepathFrom, filepathTo)



    def _create_dir(self, root):
        rootTo = root.replace(self._dirFrom, self._dirTo)

        if os.path.isdir(rootTo) == False:
            os.makedirs(rootTo)



    def _create_filepath_to(self, filepathFrom):
        return filepathFrom.replace(self._dirFrom, self._dirTo)



    def _convert_yaml(self, filepathFrom, func):
        # 記述順で読み込めるようにadd_constructorで設定
        yaml.add_constructor(u'tag:yaml.org,2002:map', construct_odict)
        load = yaml.load(open(filepathFrom).read().decode('utf-8'))

        dump = func(load)
        self._export_yaml(filepathFrom, dump)



    def _convert_app_yaml(self, data):
        data['application'] = '<your application id>'
        return data



    def _convert_api_yaml(self, data):
        for key in data:
            data[key] = ''
        return data



    def _export_yaml(self, filepathFrom, data):
        filepathTo = self._create_filepath_to(filepathFrom)

        # OrderedDictを出力できるように、add_representerにて設定
        yaml.add_representer(OrderedDict, represent_odict)
        yaml.dump(
            data,
            file(filepathTo, 'w'),
            default_flow_style=False,
            encoding='utf-8',
            allow_unicode=True
            )



if __name__ == '__main__':
    if len(sys.argv) < 3:
        print 'Usage: %s [filename]' % sys.argv[0]
        sys.exit(1)

    dirFrom = sys.argv[1]
    dirTo = sys.argv[2]

    cnv = Converter(dirFrom, dirTo)
    cnv.convert()

■使い方

GAEtoGitHubConverter.py "GAEのディレクトリ" "GitHub用のディレクトリ"

で、「GAEのディレクトリファイルを必要に応じて編集し、GitHub用のディレクトリへコピーする」という動作をします。