Python製ã³ãã³ãã©ã¤ã³ãã¼ã«ã®ãã£ã¬ã¯ããªæ§æã«ã¤ãã¦ããã®èå¯ã
ã¯ããã«
å»å¹´ãããããPython製ã®ã³ãã³ãã©ã¤ã³ã®ãã¼ã«ãããã¤ãä½ã£ã¦ãã¦ãæ§æãã ãã¶åºã¾ã£ã¦ããã®ã§ãã¾ã¨ãã¦ã¿ããè¦æ¨¡ã¨ãã¦ã¯1ãã¡ã¤ã«ã§ã¯çµãããªããããã§ãé¢æ°ã®æ°ãæ°åã«ãªã£ã¦ã¦ã¼ãã£ãªãã£ãä½ã£ãããã¯ã©ã¹ãããã¤ãä½ããªãã¨ãä¿å®ããã«ãããããªè¦æ¨¡ã®ãã®ãæ³å®ãã¦ãã¾ããå·¥æ°ã¨ãã¦ã¯1æ¥ã§ã¯çµãããªããã©ã2é±éã¯ããããªãç¨åº¦ã®è¦æ¨¡ãæ³å®ã
æ§æ
ã¨ãããã¨ã§ãã¾ãæ§æããããã¦ã¿ã¾ãã ãããªæãã SAMPLE_PROJECTã¬ãã¸ããªããã£ãã¨ãã¦ããã®å ·ä½çãªæ§æã以ä¸ã
. âââ README.md âââ RELEASE.md âââ TODO âââ bin â âââ command1 â âââ command2 â âââ command3 âââ SAMPLE_PROJECT â âââ __init__.py â âââ constants.py â âââ log.py â âââ log_config.ini â âââ constants.py â âââ util.py âââ flake8_config âââ prepare.sh âââ pytest.ini âââ requirements.txt âââ setup.py âââ test_cov.sh âââ tests âââ _settings.py âââ test_log.py âââ test_util.py
ãã£ã¬ã¯ããªã®ãããã«ã¯ã以ä¸ã®ãã¡ã¤ã«ã»ãã£ã¬ã¯ããªãããã
- README.md
- RELEASE.md
- TODO
- bin/
- SAMPLE_PROJECT
- flake8_config
- prepare.sh
- pytest.ini
- requirements.txt
- setup.py
- test_cov.sh
- test/
ã¨ãããã¨ã§ãä¸ã¤ä¸ã¤èª¬æãã¦ããã
説æ
以ä¸ã®èª¬æã§ã¯ããSAMPLE_PROJECTãã¨ããè¨è¿°ããããããã¾ãããèªåã§ä½ãå ´åã¯å®éã®ããã¸ã§ã¯ãåã«ç½®ãæãã¦ãã ããã
README.md
ç¹ã«èª¬æãããã¨ã¯ãªããä»æ§ã ã£ãããã³ãã³ãã®ä½¿ãæ¹ã®ä¾ã ã£ãããæ¸ãã¦ãããJenkinsã®ãã«ãã¹ãã¼ã¿ã¹ãªã©ãè¼ãã¦ããã¨ããããã¸ã§ã¯ãã£ã½ãæãåºãã
RELEASE.md
ã¿ã°ãåã£ãããããããã®ãã¼ã¸ã§ã³ã«ã¤ãã¦è©³ç´°ãæ¸ãã¦ãããæ¥ä»ãå ¥ãã¦ããã¦ãå°ãæ å ±éå¢ããã¦ããã
TODO
åå¿é²ç¨ãç®æ¡æ¸ãã§æ¸ãã¦ãããããããTODO管çã½ãã使ã£ã¦ããããããã®ã¸ãã¯æ°åã ã£ããããã
bin/
å®éã®ã³ãã³ããç½®ããã¦ãããã£ã¬ã¯ããªããã®ãã£ã¬ã¯ããªã«ãããã¡ã¤ã«ã§ã¯ãã¾ãé¢æ°å®ç¾©ã¯ããªãã次ã«èª¬æããSAMPLE_PROJECTãã£ã¬ã¯ããªã«ãããã¡ã¤ã«ã®ã»ãã§é¢æ°å®ç¾©ããã¦ããã¡ãã§ã¯ãããå©ç¨ããã ãã
#!/usr/bin/env python # -*- coding:utf-8 -*- from SAMPLE_PROJECT import log from SAMPLE_PROJECT.lock import create_lock_file from SAMPLE_PROJECT.lock import delete_lock_file from SAMPLE_PROJECT.util import check_python_version from SAMPLE_PROJECT.util import check_exec_user import argparse def argument_parse(): __description='This script is...' parser = argparse.ArgumentParser(description=__description) parser.add_argument('-f', dest='config_file', default=None, help='è¨å®ãã¡ã¤ã«ãæå®ãã¦ãã ããã', required=True ) __args = parser.parse_args() return __args # ã¡ã¤ã³å¦çéå§ if __name__ == '__main__': __args = argument_parse() # Lockä½æ create_lock_file() # ããã«å®å¦çæ¸ã # Lockè§£é¤ delete_lock_file()
æåã®2è¡ã¯å®è¡ã¹ã¯ãªããã§ããã®ã§ã¤ã³ã¿ã¼ããªã¿ã®æå®ããã¦ãã®ã¨ãæåã³ã¼ããæå®ãã¦ãã ã¾ããSAMPLE_PROJECTãã£ã¬ã¯ããªé ä¸ããã®ãã£ã¬ã¯ããªæ§æã§ãimportãæ¸ãã¦ããã
é常ã¯ããã¸ã§ã¯ãé
ä¸ã®importã大å¤(ex: bin/ããSAMPLE_PROJECT/é
ä¸ã®ãã¡ã¤ã«ãimportããã®ãé¢å)ãªã®ã ããã©ã pip install -e .
ãå®è¡ãããã¨ã§ããã¹ãéãããã«ãã¦ããã
ã¾ããã³ãã³ãã©ã¤ã³å¼æ°ã便å©ã«ä½¿ãããã®ã§ãargparseã使ã£ã¦ãargument_parseã¨ããé¢æ°ãä½ã£ã¦ãã
SAMPLE_PROJECT
ä¸çªå¤§äºãªãã£ã¬ã¯ããªãutil.pyãªã©ã«ã¯ä¾¿å©ãªé¢æ°ã沢山å®ç¾©ããã¦ãããå ·ä½çã«ã¯今まで作ったPython, Ruby, Bashの関数を色々晒してみる - カイワレの大冒険 Thirdã§æ¸ãããããªãã®ãå¤ãã ã³ãã³ãã©ã¤ã³ãã¼ã«ã§ããã°ãSSHçµç±ã§ã³ãã³ãå©ãããããã®ã§ããã¨ãã°ã以ä¸ã®ãããªãssh.pyããç¨æãã¦ãããã
import paramiko import sys from SAMPLE_PROJECT.constants import Constants class Ssh: def __init__(self, user_name, remote_host, port, private_key, timeout): self.user = user_name self.host = remote_host self.port = port self.private_key = private_key self.timeout = timeout def _command_execute(self, command): from hogehoge import log log.output('Command Execute: ' + command, 'debug') try: self.__create_connection() stdin, stdout, stderr = self.client.exec_command(command) stdout_msg = stdout.read() stderr_msg = stderr.read() # ã¨ã©ã¼åºåãããã°ãä¾å¤ã¹ãã¼ if stderr_msg: stderr_decoded = stderr_msg.decode(sys.stdout.encoding) log.output(stderr_decoded, 'warning') raise Exception stdout_decoded = stdout_msg.decode(sys.stdout.encoding) return stdout_decoded except paramiko.SSHException as e: log.output("Password is invalid:" + str(e)) except paramiko.AuthenticationException as e: log.output("We had an authentication exception!" + str(e)) except Exception as e: log.output("### SSH Client Exception Catched! ###" + str(e)) def __create_connection(self): u""" ã³ãã¯ã·ã§ã³ãä½æãã¾ãã """ self.conn = self.client.connect( hostname=self.host, port=self.port, username=self.user, key_filename=self.private_key, timeout=self.timeout ) return self.conn def __enter__(self): u""" ãªãã¸ã§ã¯ãçææã«SSHã¯ã©ã¤ã¢ã³ããçæãã¾ãã """ self.client = paramiko.SSHClient() self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) return self def __exit__(self, exec_type, exec_value, traceback): self.client.close()
ãã¨ã¯ãSsh()ã¨ãã¦ã³ã³ã¹ãã©ã¯ã¿ãå¼ã¹ã°ããªãã¸ã§ã¯ãåã§ããã®ã§ãèªç±ã«bin/é ä¸ã§ä½¿ããããã«ãªãã
ä»ã«ã¯ä¸è¨ã®è¨äºã§ç´¹ä»ããããã¯å¦çãè³ããlock.pyããªã©ããããã¾ããå®æ°ãå®ããconstants.pyããããããã¯ä»¥ä¸ã®ãããªãã®ã«ãªãã
# --------------- # COMMON # --------------- # lock lock_file_dir = '' lock_file_name = 'sample.lock' # log log_config_file_name = 'log_config.ini' # ssh ssh_port = 22 # apps bin_dir = 'bin' src_dir = 'SAMPLE_PROJECT' exec_user = 'masudak' CONSTANTS = { 'DEFAULT': { 'lock': { 'FILE_DIR': lock_file_dir, 'FILE_NAME': lock_file_name }, 'log': { 'config_file_name': log_config_file_name }, 'ssh': { 'USER_NAME': 'sample_user', 'PASSWORD': 'sample_pass', 'PORT': ssh_port }, 'apps': { 'BIN_DIR': bin_dir, 'SRC_DIR': src_dir, 'EXEC_USER': exec_user }, }, 'DEV': { }, 'PRODUCTION': { } }
è²ã 調ã¹ãã®ã ããã©ãPythonã®ããã¸ã§ã¯ãã§ç°å¢å¤æ°ãé§ä½¿ã§ãããããªå®æ°ã®ç®¡çæ¹æ³ãã©ããè¦ã¤ãããªãã£ãããã®ãããä¸è¨ã®ãããªç®¡çããããã®ä¸ã§ç°å¢å¤æ°ãåå¾ãã¦ãé©åãªå¤ãåããããã«ãã¦ããã
ãã¨ãã°ãutil.pyã«ä»¥ä¸ã®ãããªé¢æ°ãä½ã£ã¦ããã
def retrieve_env(): u""" ç°å¢å¤æ°ãåç §ããã¹ã¯ãªããå®è¡ç°å¢ãå¤å¥ãã¾ãã å®è¡æããã©ã«ãã¯defaultãè¿ãããã«ãªã£ã¦ãã¾ã ãSCRIPT_ENV=production hogehoge.pyãã®ããã«å®è¡ãããã¨ã§ã ç°å¢ã«å¿ããã³ã³ãã£ã°ãå¾ããã¨ãã§ãã¾ãã """ __env = 'DEFAULT' if 'SCRIPT_ENV' in os.environ: __env = os.environ['SCRIPT_ENV'] return __env
ãããã¦æ¬¡ã«å®æ°ã使ããããã¡ã¤ã«ã§ä»¥ä¸ã®ããã«importããã
from SAMPLE_PROJECT.constants import Constants
ä½åº¦ããããããã®SAMPLE_PROJECTããã®importã¯ãpip install -e .ãããªãã¨ãããªãã
ããã¦ãé¢æ°å ã¨ãã§ä»¥ä¸ã®ããã«ä½¿ãã
from SAMPLE_PROJECT.util import retrieve_env __env = retrieve_env() return CONSTANTS[__env]['apps']['EXEC_USER']
ä»ã«ã¯ãã°ã®è¨å®ããããlog_config.iniãªã©ãããã«ã¯ç½®ããã¦ããã
ãããã«ãã¦ãããã®ãã£ã¬ã¯ããªå ã®å¦çãå å®ããããã¨ã«ããªãæéã使ãããã®ãããå®è£ ã®å段éã§ãã©ããã£ãã¯ã©ã¹ãå¿ è¦ããã©ã®ãããªé¢æ°ãç¨æãã¦ããã°ãããããã£ããã¤ã¡ã¼ã¸ãã¦ä½ã£ã¦ããããã®å¾ãå¿ è¦ã«å¿ãã¦å¥ãã¡ã¤ã«ã«åãåºãããããï¼å¤§åã¯é¢åã«ãªã£ã¦ããã®ã§ãæ©ã段éã§è¨è¨ã¯FIXãã¨ããã»ããããï¼ã
flake8_config
flake8ã®è¨å®ãæ¸ãã¦ããã
[flake8] ignore = E265 max-line-length = 120 max-complexity = 10
ãã®ä¸ã§ã以ä¸ã®ããã«ãã¦å®è¡ããã
$ flake8 bin/ SAMPLE_PROJECT/ tests/ --config=flake8_config
tests/ã¯å¯¾è±¡ã«ãã¦ãããªãã¦ããããããããªããã¨ãããããå¿ è¦ã¨æã£ããã®ã¯flake8éãã¦ããã å®éã¯pytestã¨ã»ããã«ããtest_cov.sh(å¾è¿°ãã)ã使ããã¨ãå¤ãã
prepare.sh
cloneãããã¨ã«æåã«ããå¦çãã¾ã¨ãã¦ããã¦ããã
#!/bin/bash BASE_DIR=`dirname ${0}` pip install -r "${BASE_DIR}"/requirements.txt pip install -e .
pytest.ini
pytestç¨ã®è¨å®ãæ¸ãã¦ããããããªæãã
[pytest] addopts = --junitxml=pytest.xml --cov tests -v --cov-report=xml
Jenkinsã§ãã¹ãã ãã§ãªãã«ãã¬ãã¸ãè¨æ¸¬ãããã®ã§ãpytest-covã®è¨å®ãããã¦ãã(ã§ããã«ãã¬ãã¸ãããã¾ã§æ£ç¢ºã§ã¯ãªãæ°ããã)ã
requirements.txt
å¿ è¦ãªããã±ã¼ã¸ãè¨å ¥ãã¦ããããã¨ãã°ã以ä¸ã®ãããªæãã
PyYAML==3.11 cov-core==1.13.0 coverage==3.7.1 mock==1.0.1 pep8==1.5.7 pytest==2.6.0 pytest-cov==1.7.0 pytest-pep8==1.0.6 requests==2.3.0 flake8==2.2.3
ãããã¯åè¿°ããprepare.shã§ã¤ã³ã¹ãã¼ã«ãããã
setup.py
pip install -e .
ãããã®ã§ãç°¡åã«ä½ã£ã¦ããã
import os from distutils.core import setup setup(name='SAMPLE_PROJECT', version='1.0.0' )
test_cov.sh
ãã¼ã«ã«ã§ã³ã¼ãæ¸ãã¦ãã¨ãã«å®è¡ããç¨ããã¬ã³ãããã§å ¥ãã¦ãããããã¨ãã£ã¿ã«è¨å®ãã¦ãããã
#!/bin/bash py.test && flake8 . --config=flake8_config
ããã§ãã¹ãã¨ã¹ã¿ã¤ã«ãã§ãã¯ãã§ããã
test/
ããã¯ãã¹ããç½®ããã£ã¬ã¯ããªããã¹ãã®æ¸ãæ¹ã¯ã°ã°ã£ã¦ãããã¨ãã¦ãåºæ¬çã«ã¯SAMPLE_PROJECT/é ä¸ã«ãããã¡ã¤ã«åä½ã§ãã¹ãç¨ãã¡ã¤ã«ãä½æãæ¸ãã¦ãã
ã¾ãããã®ãã£ã¬ã¯ããªã«ã¯ã_settings.pyã¨ãããã¡ã¤ã«ãä½ãã以ä¸ã®è¨è¿°ããã¦ããã
import os import sys def get_home_dir(file): u""" ã¢ããªã±ã¼ã·ã§ã³ã®ãã¼ã ãã£ã¬ã¯ããªãè¿ãã¾ã """ return os.path.split(os.path.dirname(os.path.abspath(file)))[0] def append_home_to_path(file): u""" ã¢ããªã±ã¼ã·ã§ã³ã®ãã¼ã ãã£ã¬ã¯ããªãsys.pathã«è¿½å ãã¾ã """ sys.path.insert(0, get_home_dir(file))
ããã¦ããã®_setting.pyãåãã¹ããã¡ã¤ã«ã®åé ã§ä»¥ä¸ã®ããã«importããã
import _settings _settings.append_home_to_path(__file__)
ãããããã¨ã§ããã¹ããã¡ã¤ã«ã®ãªãã§ãSAMPLE_PROJECTãããããã®importãå¯è½ã«ãªãããpip install -e .ãã¨åããããªæ©è½ãæããã¦ãããã
ãããã«
è²ã
試è¡é¯èª¤ãã¦ãã®å½¢ã«ãªã£ããã®ã® pip install -e .
ãã¦ãã¹ãéãã®ã¯å¦¥å½ãã©ããã®èªä¿¡ããªãããå®æ°ã®ç®¡çæ¹æ³ãã¾ã ç´å¾ãããªãã£ãããããæ°ã«å
¥ããªãã¨ãããã¾ã ã¾ã ããã®ã§ãæ°å¹´å¾ãã®è¨äºãè¦ããããªãã§ãããªãã¨ã«æ©ãã§ããã®ãã¨æããããããªãã
ãã ãç¾ç¶èªåãæ¸ããç¯å²ã§æ§æã¯æ´çãã¦ã¿ãããä¸ä¾¿ã¯ãããã®ã®ãèªåã¨ãã¦ã¯ããã¨è¦ãããã»è¿½ããããã®ã§ãä»æã¦ãç¥èã§æ大éã¾ã¨ãã¦ã¯ããã
ãªã®ã§ããããããããªã¨æãç¹ããã£ãããæ¯éæããããã³ããé ããã¨ãããå©ããã¾ããããããè¨è¨ã§ãèªã¿ãããã³ã¼ããæ¸ãã¦ããããã®ã§ãèè
ã®æ¹ã
ãå©è¨æ¯éå®ãããé¡ãè´ãã¾ãã
ç¶ããæ¸ãã¾ããã Pythonでコマンドラインツールを作りたいときには、setup.pyには何を書くべきか - カイワレの大冒険 Third