PyDev for Eclipseを参照のこと
Python では、言語仕様としてファイルヘッダや関数のメソッドのコメントについて 書式を定めていない。コメントの書き方はいくつかの流儀があるが、ここでは 「Google Style Python Docstrings」に基くコメントの書き方を示す。
def myfunction(arg1: str, arg2: int=0):
'''<関数の概要説明>
<関数の処理内容の詳細説明>
Note:
<注意事項の説明>
Args:
<引数名> (<型>): <必須の引数の説明>
<引数名> (:obj:`<型>`, optional): <省略可能な引数の説明>
...
Returns:
<型>: <戻り値の説明>
Raises:
<例外クラス>: <例外発生条件の説明>
Examples:
<例の説明>
<サンプルコード>
'''
<関数の実装>引数の記述で、Google Style の省略可能な引数の形式は、pdoc で API ドキュメントを生成する際にた正しく解釈されない模様である。pdoc で API ドキュメントを生成する際は、「(:obj:`型`, optional)」と書く代わりに 「(型, optional)」と書いてこの問題を回避する。
class MyClass(object):
'''<クラスの概要説明>
<クラスの処理内容の詳細説明>
'''
def __init__(self, arg1, arg2):
'''<クラス初期化処理の概要説明>
<クラス初期化処理の詳細説明>
Attributes:
arg1 (<型>): <必須の引数の説明>
arg2 (:obj:`<型>`, optional): <省略可能な引数の説明>
'''
self._myproperty: str = None
...
@property
def myproperty(self):
'''<型>: <プロパティの説明はsetterでコメントする>'''
return self._myproperty
@myproperty.setter
def myproperty(self, value):
self._myproperty = value
@myproperty.deleter
del self._myproperty
def classMethod(arg1: str, arg2: int=0):
'''<メソッドの概要説明>
※ 関数コメントと同じ内容を記述する
'''
<メソッドの実装>$ python -m pip install --user pdoc3
$ python -m pdoc --html -o output-dir --force {package-dir | module-path ...}引数の説明
作成したモジュール/パッケージが依存するパッケージを管理する手順を説明する。
作成したモジュール/パッケージと同じディレクトリーに、「requirements.txt」 という名前のテキストファイルを配置して、依存するパッケージを記述する。 このファイルを使用して、pip コマンドでパッケージのインストールや アンインストールを行う。
ファイルの名前は特に定められていないが、慣習的に「requirements.txt」が 用いられている。以下に requirements.txt の簡単な例を示す。
# ####### example-requirements.txt ####### # ###### Requirements without Version Specifiers ###### nose nose-cov beautifulsoup4 # ###### Requirements with Version Specifiers ###### # See https://www.python.org/dev/peps/pep-0440/#version-specifiers docopt == 0.6.1 # Version Matching. Must be version 0.6.1 keyring >= 4.1.1 # Minimum version 4.1.1 coverage != 3.5 # Version Exclusion. Anything except version 3.5 Mopidy-Dirble ~= 1.1 # Compatible release. Same as >= 1.1, == 1.*
[requirements.txt の簡単な例]
Requirements File の詳細については、以下の「参考文献」を参照のこと。
以下のコマンドを実行して、Requireents File に記述されたパッケージを インストールする。
$ python -m pip install [-t <dir> | --user] -r <requirements-file-path>
指定したディレクトリーにパッケージを配置する。
ユーザーのホームディレクトリの下(~/.local/lib/python3.x/site-packages/) にパッケージをインストールする。
Requirements File のパスを指定する。
-t オプション、--user オプションを指定しない場合は、システム・ディレクトリー (/usr/lib/python3.x/site-packages) にパッケージがインストールされる。この 場合は、root 権限でコマンドを実行する必要がある。
1つのシステム上に多数のモジュール/パッケージを配置して、それぞれの モジュール/パッケージに依存したパッケージのインストール/アンインストール を繰り返す場面では、以下の理由によりモジュールと同じディレクトリーに依存 パッケージをインストールすることを検討した方が良い。
以下のコマンドを実行して、Requireents File に記述されたパッケージを インストールする。
$ python -m pip uninstall [-y] {-r <requirements-file-path>|<package-name> ...}アンインストールされるパッケージは指定したパッケージのみで、 アンインストール対象のパッケージをインストールした際にそれに依存して インストールされたパッケージはアンインストールされないことに注意すること。
pip-licenses を使用して、依存関係にあるパッケージとそのライセンスを表示する 手順を説明する。pip-licenses は、pip でインストールされたモジュールで、python のパスが通っているもの(ただし、pip、setuptools などのシステム・パッケージや pip-licenses を除く)について、ライセンスを表示する。従って、あるパッケージが 依存するモジュールのライセンスのみを表示するためには、python の環境に対象 パッケージ以外のパッケージが pip でインストールされていないことが必要である。
作業ディレクトリに移動し、以下のコマンドを実行して、対象パッケージが 使用するモジュールと pip-licenses パッケージをインストールする。
$ pythom -m pip install -t site-packages -r <package-dir>/requirements.txt \
pip-licenses$ PYTHONPATH=./site-packages python -m piplicenses --format=html \ --output-file <html-file-path>
オプションの説明
python 標準の unittest を使用してテストクラスを実装、実行する手順を説明する。
テストクラスの実装例(TestStringMethods.py)を以下に示す。これは、 https://docs.python.org/ja/3/library/unittest.html#basic-exampleに 記載されているコードである。
import unittest
class TestStringMethods(unittest.TestCase):
def setUp(self):
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
logging.debug('Set up test.')
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')
def test_isupper(self):
self.assertTrue('FOO'.isupper())
self.assertFalse('Foo'.isupper())
def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', 'world'])
# check that s.split fails when the separator is not a string
with self.assertRaises(TypeError):
s.split(2)
def tearDown(self):
logging.debug('Tear down test.')
if __name__ == '__main__':
unittest.main()[TestStringMethods.py]
説明
if __name__ == '__main__':
unittest.main()assertXxx メソッドに msg キーワード引数を追加してメッセージを指定する。
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO', msg='All characters must be uppcer case.')ただし、assertRaises()、assertRaisesRegex()、assertWarns()、 assertWarnsRegex() の各メソッドについては、これらをコンテキストマネージャと して使用した場合(with キーワードを使用した場合)のみ msg キーワード引数が 有効となることに注意すること。
def test_split(self):
s = 'hello world'
# check that s.split fails when the separator is not a string
with self.assertRaises(TypeError, msg='TypeError exception must be raised.'):
s.split(2)テストクラスを実装した python ファイルを指定して、python コマンドを実行する。 実行結果は、以下のように標準出力に表示される。
---------------------------------------------------------------------- Ran 1 test in 0.000s
F
======================================================================
FAIL: test_find_repeat (__main__.TestMyModule)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/home/foo/git/python-prog/src/test/python/TestMyModule.py", line 31, in test_find_repeat
self.assertEqual(mymodule.find_repeat('abcijkxyz', 'ijk', 4, 7), [3, 1], \
AssertionError: Lists differ: [-1, 0] != [3, 1]
First differing element 0:
-1
3
- [-1, 0]
+ [3, 1] : 'ijk' must be found at index:3 of 'ijk' and 1 repeat.
----------------------------------------------------------------------
Ran 1 test in 0.004s
FAILED (failures=1)テストモジュールを配置したディレクトリーに移動して、以下のコマンドを実行し、 html-testRunner パッケージをインストールする。
$ python -m pip install -t . html-testRunner
-t target-dir オプションについて
テストクラスの実装は変えずに、テストクラスのスクリプトに以下の処理を追加 する。テストクラスのスクリプトを実行すると、テストクラスのスクリプトを配置 したディレクトリー配下の report-html/python-progs.html にレポートが出力 される。
具体的には、「if __name__ == '__main__':」の部分で以下の処理を追加する。
テストクラスのソースコードの実装例は以下のとおり。
import unittest
import HtmlTestRunner
import os
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')
# テストメソッドは以下省略
if __name__ == '__main__':
html_runner = HtmlTestRunner.HTMLTestRunner( \
output=os.path.dirname(__file__) + '/report-html', add_timestamp=False)
unittest.main(testRunner=html_runner)[TestStringMethods.py]
上記の方法では、テストクラス毎にテストの実行を行い、HTML形式のレポートは テストクラス毎に出力される。一度に複数のテストクラスのテストを実行し、 テスト結果のHTML形式のレポートを1つにまとめるための手順を以下に示す。
以下の python コードは、mytest1.py に MyTest1 テストクラス、mytest2.py に MyTest2 テストクラスが定義されている場合に、これらのスクリプトを一度に実行 して、その結果をこのスクリプト(test_all.py)と同じディレクトリの report-html/python-progs.html ファイルに出力する処理の例である。
from unittest import TestLoader, TestSuite
from HtmlTestRunner import HTMLTestRunner
import io
import os
import yaml
def load_config():
config_path = os.path.splitext(__file__)[0] + '.yml'
with io.open(config_path, 'r') as file:
config = yaml.load(file)
return config
def run_test_all():
# Loding config file
config = load_config()
# Getting test script patterns form config
patterns = config["module_patterns"]
# Getting test suites from script patterns.
suite = TestSuite()
for pattern in patterns:
suite.addTest(TestLoader().discover(os.path.dirname(__file__), pattern))
runner = HTMLTestRunner( \
output=os.path.dirname(__file__) + '/../target/site/test-report', \
report_name='python-progs', add_timestamp=False, combine_reports=True)
runner.run(suite)[test_all.py]
module_patterns: - '*Test.py' - 'Test*.py'
[test_all.yml]
説明
テストに失敗すると、HtmlTestRenner を実行ときに下記のエラーが発生する。
AttributeError: 'HtmlTestResult' object has no attribute '_count_relevant_tb_levels'.
このエラーは、Pythonのバージョンが上がったことにより、HtmlTestResult クラスの継承元クラス TextTestResult の _count_relevant_tb_levels 属性が 廃止されたことが原因である。このエラーを回避するためには、HtmlTestRunner パッケージの result.py ファイルを以下のように修正する。
if exctype is test.failureException:
# Skip assert*() traceback levels
length = self._count_relevant_tb_levels(tb)
msg_lines = traceback.format_exception(exctype, value, tb, length) if exctype is test.failureException:
# Skip assert*() traceback levels
msg_lines = traceback.format_exception(exctype, value, tb)sed コマンドで修正する例は以下のとおり。
$ sed -e 's/^[ \\t]*length = self._count_relevant_tb_levels(tb)$//g' \
-e 's/msg_lines = traceback.format_exception(exctype, value, tb, length)/msg_lines = traceback.format_exception(exctype, value, tb)/g' \
-i <python-lib-dir>/site-packages/HtmlTestRunner/result.pyMagicMock クラスは Mock クラスと互換のより強力な機能を持つので、MagicMock クラスの利用が推奨されている。
unittest.mock パッケージの Mockクラスは、このインスタンスの属性 (プロパティやメソッド)にアクセスすると、その属性、関数が自動的に定義 される。自動的に作成されたメソッドに対して、assert_called_with 関数を呼び出すと、メソッドの呼び出しをアサートすることができる。
Mock オブジェクトの return_value 属性に値を設定すると、Mock オブジェクト にアクセスすると戻り値が return_value 属性に設定した値となる。
モジュールの属性を Mock オブジェクトに置き換える場合は、 unittest.mock.patch を使用する。patch 関数をコンテキスト・マネージャーと して使用すると、as で置換されたオブジェクトを参照でき、 コンテキスト・マネージャーのスコープを抜けると置換対象のオブジェクトの 属性は元に戻る。
unittest.mock.path(モジュールの属性名 ,[置換するオブジェクト], [キーワード=値[, ...]])
例:'モジュール名.勘数名'、パッケージ名.モジュール名.勘数名
unittest.mock.patch.object 関数に置換対象のオブジェクトと属性名 (例:クラスのメソッド)を指定して、それを Mock オブジェクト に置き換える。patch 関数も patch.object と同様にコンテキスト・マネージャー として利用できる。
unittest.mock.path.object(オブジェクト, オブジェクトの属性名, [置換するオブジェクト], [キーワード=値][, ...]])
モジュールの関数やクラスのメソッドの動作をテストするのではなく、それらが 正しい引数で呼び出され方をテストしたい場合に利用する。
import unittest
from unittest.mock import patch
class MyClass():
'''テスト対象のクラス
'''
def __init__(self):
self.attr1 = None
def doSomething(self, no: int):
print(f'No.{no} Good morning.')
def doAnother(self, no: int):
print(f"No.{no} Good by.")
class TestMyClass(unittest.TestCase):
def testAccess(self):
'''MyClassの属性、メソッドの呼び出し確認をする
MyClass の属性やメソッドが正しい引数で呼び出されたことをテストする。
'''
with patch('MockTest.MyClass') as mock:
# MyClass のインスタンスを Mock オブジェクトから取得する
instance = mock.return_value
# nyClass(実はMockオブジェクト)のメソッド、属性にアクセスする
instance.doSomething(1)
instance.doAnother(2)
instance.attr1 = 3
# MyClass の doSomething メソッドが引数「1」を指定して呼び出された
# ことを確認する
instance.doSomething.assert_called_with(1)
# MyClass の doAnother メソッドが引数「2」を指定して呼び出された
# ことを確認する
instance.doAnother.assert_called_with(2)メソッドAをテストしたいが、メソッドAが内部で呼び出しているメソッドBの 実行が難しい環境なので、メソッドBをを実行せずにメソッドBが 「Hello world.」を返すことにしてテストを実行する。
import requests
import unittest
from unittest.mock import patch
class MyClass():
'''テスト対象のクラス
'''
def methodA(self, url):
return self.methodB(url)
def methodB(self, url):
response = requests.get(url)
result = response.text
return result
class TestMyClass(unittest.TestCase):
def testMethodA(self):
'''MyClass の methodA のテストを実行する
methodBの内部で request.get により呼び出している Web API を実行できない
ので、methodB が 'Hello world.' が返すことにしてテストを実行する。
'''
instance = MyClass()
# オブジェクト(MyClass)の methodB を Mock オブジェクトに置換し、
# 置換されたMock オブジェクトの return_value に 'Hello world.' を設定
with patch.object(MyClass, 'methodB', return_value='Hello world.'):
result = instance.methodA('http://www.foo.bar.xyz/func1/')
self.assertEqual(result, 'Hello world.')上記と同様な場合だが、メソッドBが毎回同じ値を返すのではなく、呼び出す 順番に異なる値を返すようにしてテストする。
import unittest
from unittest.mock import patch
class MyClass():
'''テスト対象のクラス
'''
def methodA(self, url):
return self.methodB(url)
def methodB(self, url):
response = requests.get(url)
result = response.text
return result
class TestMyClass(unittest.TestCase):
def testMethodAMultiple(self):
'''呼び出される度に戻り値が異なるメソッドをモックしてテストする
methodBの内部で request.get により呼び出している Web API を実行できない
ので、methodB が 'Hello world.'、'Good by.'を順に返すことにしてテストを
実行する。
'''
myClass = MyClass()
# オブジェクト(MyClass)の methodB を Mock オブジェクトに置換し、
# 置換されたMock オブジェクトの return_value に 'Hello world.' を設定
with patch.object(MyClass, 'methodB',
side_effect=['Hello world.', 'Good by.']):
# methodA から medhodB への1回目の呼び出し
result = myClass.methodA('http://www.foo.bar.xyz/func1/')
self.assertEqual(result, 'Hello world.')
# methodA から medhodB への2回目の呼び出し
result = myClass.methodA('http://www.foo.bar.xyz/func1/')
self.assertEqual(result, 'Good by.')例外を発生させるのが難しい状況で、例外発生パターンをテストしたい場合に 利用する。
import requests
import unittest
from unittest.mock import patch
class MyClass():
'''テスト対象のクラス
'''
def methodB(self, url):
response = requests.get(url)
result = response.text
return result
class TestMyClass(unittest.TestCase):
def testMethodBException(self):
'''requests.get 関数の例外発生テストをする。
requests.get を実行したら ConnectionError が発生した場合のテストを
実行する。
'''
myClass = MyClass()
with patch('requests.get', side_effect=requests.ConnectionError):
with self.assertRaises(requests.ConnectionError):
myClass.methodB('http://www.foo.bar.xyz/func1/')パッケージの関数の実行が難しい場合、例えば、Web APIを呼び出す機能をテスト したいがテスト環境ではWeb APIにアクセスできない場合に Web API の呼び出し 処理を別の関数に代替させてテストを実行する。
import requests
import unittest
from unittest.mock import patch
class MyClass():
'''テスト対象のクラス
'''
def methodB(url):
response = requests.get(url)
result = response.text
return result
class MyResponse():
'''requests.Responseオブジェクトの代わりに使用するオブジェクト
'''
def __init__(self, status_code, text):
self.status_code = status_code
self.text = text
def mock_get(url) :
'''request.get メソッドの代わりに使用するメソッド
urlの最後の「/」とその前の「/」との間の文字列が「func1」の場合は、
「200: Success.」を返し、それ以外の場合は「404: Error.」を返す。
'''
items = url.split('/')
action = items[len(items) - 2]
if action == 'func1':
return MyResponse(200, 'Success.')
else:
return MyResponse(404, 'Error.')
class TestMyClass(unittest.TestCase):
def testMethodReplace(self):
'''requests.getメソッドをテスト用のロジックに置き換えてテストする
request.getメソッドで呼び出すWeb APIが実行できないので、テスト用の
ロジックに置き換えてテストする。
'''
instance = MyClass()
# requestsモジュールの get 関数を mock_get 関数に置き換える
with patch('requests.get', side_effect=mock_get):
response = instance.methodB('http://www.foo.bar.xyz/func1/')
self.assertTrue(response, 'Success.')
response = instance.methodB('http://www.foo.bar.xyz/func2/')
self.assertTrue(response, 'Error.')テストスクリプトを配置したディレクトリーに移動して以下のコマンドを実行し、 coverage パッケージをインストールする。
$ python -m pip install -t . coverage
以下のコマンドを実行して、テストを実行しカバレッジデータを収集する。 「-t target-dir」オプションを指定して coverage パッケージを インストールした場合は、「target-dir/bin/coverage」を実行すること。
$ coverage run --branch <test-script-path>
上記のコマンドを実行すると、収集された条件網羅のカバレッジデータが カレントディレクトリーの「.coverage」ファイルに出力される。
以下のコマンドを実行して、HTML形式のカバレッジレポートを出力する。
$ coverage html
上記のコマンドを実行すると、カレントディレクトリー配下の「htmlcov」 ディレクトリーの下に、HTML形式のカバレッジレポートが出力される。
プロキシサーバーを通してインターネットにアクセスするときの設定について説 明する。
Python プログラムがインターネットにアクセス可能にするためには、下記の環境 変数にプロキシサーバーの設定をする。
httpプロトコルで通信する場合は、この環境変数を設定する。設定する値は、 認証のユーザー名、パスワード、プロキシサーバー名、ポート番号を以下の 形式で指定する。
http://[<ユーザ名>:<パスワード>@]<プロキシサーバー名>[:<ポート番号>]
httpsプロトコルで通信する場合は、この環境変数を設定する。設定する値は、 http_proxy 環境変数の同じである。
プロキシサーバーを経由せず、直接アクセスするサーバー、IPアドレスを指定 する場合は、この環境変数を設定する。設定する値は、除外するサーバー名、 IPアドレスの suffix (後方一致の文字列)で、複数の場合はカンマで区切る。
"<サーバー名のsuffix>[:<ポート番号>],<IPアドレスのsuffix>,..."
設定ファイルで読み取った値を以下のようにプログラムで環境変数に設定する。
import os os.environ['http_proxy'] = <設定ファイルから読み取った値> os.environ['https_proxy'] = ... os.environ['no_proxy'] = ...
pip コマンド、または、「python -m pip」コマンドを実行する際に、プロキシ サーバー経由でインターネットにアクセスするときの設定は、以下のとおりである。
pip コマンド、または「python -m pip」コマンドに以下のパラメータを指定する。
--proxy=http://[<ユーザ名>:<パスワード>@]<プロキシサーバー名>[:<ポート番号>]
python の実行プログラムと同じディレクトリに pip.ini ファイルを配置して、 以下のように設定する。
[global] proxy = http://[<ユーザ名>:<パスワード>@]<プロキシサーバー名>[:<ポート番号>]
Pythonの仮想環境は、ベースのPython(システムにインストールされているPythonの 環境)と独立したPythonの環境を提供する物である。仮想環境を構築すると、 pythonの十個環境や site-packages がベースのPythonととは別に容易され、それを 利用することが可能になる。
以下のコマンドを実行して、仮想環境を作成する。
$ python3 -m venv <仮想環境作成ディレクトリ>
仮想環境のディレクトリは、「.venv」が推奨される。
以下のコマンドを実行して、仮想環境を有効化する。
$ source <仮想環境作成ディレクトリ>/bin/activate
仮想環境を有効化すると、シェルプロンプトに括弧付きの仮想環境作成 ディレクトリの名前が表示される。以降このシェルプロンプトでpythonコマンドを 実行すると、仮想環境作成ディレクトリの下に作成されたPythonの環境が適用 される。
仮想環境が有効な状態で以下のコマンドを実行すると仮想環境が無効化され、 シェルプロンプトから括弧付きの仮想環境のディレクトリ名が表示されなくなる。
(<仮想環境ディレクトリ名>) $ diactivate
# dnf install dnf-plugins-core # dnf builddep python3
https://www.python.org/downloads/source/より Python 3.x.x の Gzipped source tarball (Python-3.x.x.tgz) をダウンロードする。
「tar xvfz Python-3.x.x.tgz」を実行して、ダウンロードしたファイルを解凍し、 解凍先のディレクトリ(Python-x.x.x)に移動して以下のコマンドを実行する。
$ ./configure --enable-optimizations $ make $ make altinstall DESTDIR=<work-dir>
上記のコマンドを実行した結果、work-dir/usr/local ディレクトリの下に、 Pythonnの bin、include、lib、share ディレクトリーが作成される。
work-dir/usr/local/bin ディレクトリ配下の実行ファイル「xxx3.xx」に対する シンボリック・リンクを作成し、名前を「xxx3」にする。作成するシンボリック・ リンクは以下のとおりである。
work-dir/usr/local ディレクトリを名前を変えて、適切な場所に配置する。 例えば、/opt/python3 ディレクトリの下に Python の各ディレクトリを配置する 場合は、以下のコマンドを実行する。
# cp -Rvi <work-dir>/usr/local /opt/python3
Windows版のPythonには、zipを解凍したらすぐに使用可能となる embeddable package がある。ただし、python コマンドしかないため、pip によるパッケージの インストールを可能とするためには、そのための手順が必要である。
https://www.python.org/downloads/windows/ より 「Windows embeddable package (64-bit)」のリンクをクリックして zip ファイルを ダウンロードし、配置先のディレクトリに移動して zip ファイルを解凍する。
embeddable package を解凍したディレクトリに存在する pythonバージョン._pth を以下のように編集する。
embeddable package を解凍したディレクトリに「current.pth」ファイル (ファイル名は任意だが、拡張子は .pth とすること)を作成し、以下の内容を 設定する。
sys.path.append('')https://bootstrap.pypa.io/get-pip.py をダウンロードして作業ディレクトリに 配置し、作業ディレクトリに移動して以下のコマンドを実行する。
$ <embeddableパッケージ配置ディレクトリ>\python.exe get-pip.py
$ python -m pip install [--user] Django
まだプロジェクトを作成していない場合は、プロジェクトを作成する。 Webアプリケーションは、プロジェクトに配置される。
プロジェクトを作成するディレクトリに移動して、以下のコマンドを実行する。
$ django-admin startproject <プロジェクト名>
【注意】
プロジェクト作成コマンドを実行すると、プロジェクト名で指定した名前の ディレクトリが作成され、更にその下にプロジェクト設定用の同名のディレクトリ (Pythonパッケージ)が作成される。
<プロジェクト名> -- プロジェクト・コンテナ
|-- manage.py
`-- <プロジェクト名> -- Pythoんパッケージ
|-- __init__.py
|-- asgi.py
|-- settings.py
|-- urls.py[プロジェクトディレクトリの構成]
以降は、上位のプロジェクト名のディレクトリ(manage.py が配置された ディレクトリ)を「プロジェクト・コンテナ」と表現する。
プロジェクト・コンテナの プロジェクト名/settings.py に対して以下の設定をする。
規定では sqlite3 を使用するように設定されている。データベースに sqlite3 を 使用する場合は、変更せずに使用可能である。
他のRDBを使用する場合は、以下の部分を設定し、データバインド(PostgreSQL の場合は、psycopg2 パッケージ)をインストールする。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydatabase',
'USER': 'mydatabaseuser',
'PASSWORD': 'mypassword',
'HOST': '127.0.0.1',
'PORT': '5432',
'ATOMIC_REQUESTS': True|False
}
}[PostgreSQLの設定例]
以上の設定を終えたら、プロジェクト・コンテナに移動して、以下のコマンドを 実行してアプリケーションの実行に必要なデータベースのテーブルを作成する。
$ python manage.py migrate
「TIME_ZONE =」にタイムゾーンの設定をする。
TIME_ZONE = 'Asia/Tokyo'
[タイムゾーンの設定例]
プロジェクト・コンテナで以下のコマンドを実行して、Django の アプリケーションを実行する。
$ python manage.py runserver [{<ポート番号> | <IPアドレス>[:<ポート番号>]}]Django Admin は、コンテンツ(アプリケーションのモデル)の追加、変更、削除 などを管理するための Webアプリケーションである。利用開始の前のに、以下の 設定をする。
プロジェクト・コンテナに移動して、以下のコマンドを実行する。
$ python manage.py createsuperuser
以下の入力を要求されるので、それぞれを設定する。
http://127.0.0.1:8000/admin/ にアクセスして、管理者のログイン名、 パスワードでログインし、adminサイトを表示する。
プロジェクト・コンテナに移動して、以下のコマンドを実行する。
$ python manage.py startapp <アプリケーション名>
【注意】
アプリケーション作成コマンドを実行すると、アプリケーション名で指定した 名前のディレクトリが作成され、そこにアプリケーションのディレクトリ、 ファイルが作成される。
プロジェクト名/settings.py のファイルを開き、以下の設定を追加する。
INSTALLED_APPS = [
'<アプリケーション名>.apps.<構成クラス名>',
...
]モデルはデータベースのテーブル定義を格納するクラスである。 アプリケーション名/models.py ファイルにモデルの定義を記述する。
モデルには、以下の内容を定義する。
以下に示すものは、Django公式ドキュメントの 「はじめてのDjangoアプリ作成、その2」に記載されているモデルの定義である。
import datetime
from django.db import models
from django.utils import timezone
# Create your models here.
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text[モデルの定義例]
以下の説明で「instance」はモデルのインスタンスを格納した変数を表わす。 また、「Model」はモデルのクラス名を表わす。
データベースからモデルの全レコードを検索し、Model のインスタンス の配列を返す。
Model のフィールド field-name の値が value と一致する レコードを検索し、Modelのインスタンスを返す。該当するレコードが ない場合、または該当するレコードが複数存在する場合は例外が発生する。
Model のフィールド名 field-name の値が valueで始まる (__startswidth)/valueを含む(__contains)レコードを検索し、 Model のインスタンスを返す。該当するレコードがない場合、 または該当するレコードが複数存在する場合は例外が発生する。
Modelのフィールド名 field-name の値が valueで始まるレコードを 検索し、Model のインスタンスの配列を返す。
model は Model の先頭を小文字にしたもの。Modelの外部キーが instanceを参照している Model のインスタンスをすべて取得して、 その配列を返す。
【補足】
.-----------. |Article | .--------. |* reporter-+----->|Reporter| `-----------' `--------'
オブジェクトを新規作成または変更する。
Model の フィールド名 field-name-1 の値が value 以下の レコードを取得し、field-name-2の昇順にソートしたものを Modelの インスタンスの配列で返す。order_by の field-name-2の前に「-」を 付けると降順のソートになる。
instance を Modelの外部キーで参照している Model のインスタンス から フィールド名 field-name の値が valueと一致するものを取得 して Model のインスタンスの配列で返す。
プロジェクト・コンテナのディレクトリで以下のコマンドを実行して、モデルの 定義からデータベースを作成する。
$ python manage.py makemigrations polls
なお、このコマンドの実行により以降のモデルの変更はデータベースに反映 されるとともにマイグレーションの記録として 「アプリケーション名/migrations」ディレクトリの下にテキストファイルで 記録されるようになる。
アプリケーション名/admin.py ファイルに対して以下の設定をする。
from django.contrib import admin from .models import <モデルクラス名> # Register your models here. admin.site.register(<モデルクラス名>)
URLディスパッチャは、リクエストURLをビューの関数に紐付ける。 URLディスパッチャは、以下の2つのファイルで設定する。
URLとビューの関数の関連付けをこのファイルに設定する。urlpatterns に 以下のようにpath関数の戻り値を設定する。なお、下記のコードの app_name 変数はアプリケーションの名前空間を指定するもので、テンプレートでURLを 指定するときに使用する。
from django.urls import include, path
from . import views
app_name = 'polls'
urlpatterns = [
path('<int:question_id>/results/', views.results, name='results'),
...
}URLのディスパッチは以下のように行われる。
URLディスパッチの詳細は、django topics の URL Dispatcher を参照のこと。
【例】
path('<int:question_id>/results/', views.results, name='results')それぞれのアプリケーションのURLディスパッチャの設定をプロジェクトに反映 する。既定の状態では、プロジェクト名/urls.py は以下のように設定されて いる。
urlpatterns = [
path('admin/', admin.site.urls),
]アプリケーションを追加したら、urlpatters に以下の設定を追加する。
path('<アプリケーション名>/', include('<アプリケーション名>.urls')),テンプレートは、アプリケーション名/templates/アプリケーション名 ディレクトリの下に配置する。
以下のチュートリアルのセクションを参照のこと。
テンプレートの中で以下のように記述したとする。
<li><a href="/polls/{{ question_id }}/">{{ question.question_text }}</a></li>この場合、アプリケーションの名前空間が変わったり、urls.py で URL の パスの変更が行われた場合にビューの修正が発生する。以下のように 「{%url%}」を使用すれば、この問題を避けることができる。
<li><a href="{% url '<名前空間>:<pathのname>' <パラメータ> ... %}">...</a></li>上記の設定の場合は、以下条件に一致する path関数の第一パラメータの値の 前に「アプリケーション名/」を加えたものが{%ul%}の結果となる。
URLの文字列野中に「<...>」が含まれる場合は、{%url%}の2番目 以降の内容が順番に適用される。
例えば、テンプレートに以下の記述をした場合は、{%ur%}の部分は以下の ように置き換わる。
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>path('<int:question_id>/', views.detail, name='detail'),/pols/34/ (question.id の値が 34 の場合)
ビューは、リクエストを受け取ってデータベースの検索や更新等の処理を実行し、 何らかの応答を返す処理である。
アプリケーション名/views.py のURLディスパッチャから呼び出される関数を 作成し、以下のように記述する。
from django.http import HttpResponse
from django.template import loader
def index(request):
...
template = loader.get_template('<テンプレートファイルのパス>')
context = {
'<テンプレートの変数名>': <この関数の変数>
}
return HttpResponse(template.render(context, request))loader.get_template 関数を実行してテンプレートをロードする。 引数の テンプレートファイルのパス にはtemplates ディレクトリからの 相対パスを指定する。
template.render 関数を実行して応答で返すHTMLを作成する。 contextにはテンプレートに渡す引数をディクショナリで以下のように設定する。
django.shortcuts モジュールの render 関数 を使用して、template モジュールの loader.get_template 関数の呼び出しをショートカットする ことが可能である。
from django.shortcuts import render
def index(request):
...
context = {
'<テンプレートの変数名>': <この関数の変数>
}
return render(request, '<テンプレートファイルのパス>', context)以下のように モデルの objects.get 関数を呼び出す代わりに、 django.shortcuts モジュールの get_object_or_404 関数を使用することで、 検索条件に該当するデータが見つからない場合に404エラーを簡単に送出する ことができる。
from django.shortcuts import render
from django.shortcuts import get_object_or_404, get_list_or_404
from django.http import HttpResponse
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
context = {'question': question}
return render(request, 'polls/detail.html', context)汎用ビューは、データベースからモデルの一覧や1つのモデルを取得して テンプレートに渡す処理を汎用化したクラスである。これを使用すると、 djsnho.shortcuts モジュールの render関数の呼び出しを省略できる。
汎用ビューを使用する手順は以下のとおり。
from django.views import generic
from .models import Question
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
return Question.objects.order_by('-pub_date')[:5][一覧を表示するビュークラスの例]
説明
from django.views import generic
from .models import Question
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'[モデルの内容を表示するビュークラスの例]
説明
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
...
}path 関数の設定内容
チュートリアルの 簡単なフォームを書く に記載されているフォームの例は以下のとおり。
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
<legend><h1>{{ question.question_text }}</h1></legend>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
</fieldset>
<input type="submit" value="Vote">
</form>二重送信を防止するためのテクニック。処理が成功したら、リダイレクトで 画面遷移する。
django.urls モジュールの reverse 関数を呼び出すと、テンプレートの {%url%}と同様な URL を取得できる。
from django.http import HttpResponseRedirect
from django.urls import reverse
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
...
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))Djangoの自動テストには、モデルのテストとビューのテストがある。どちらも アプリケーション名/test.py ファイルにテストのコードを記述する。
import datetime
from django.test import TestCase
from django.utils import timezone
from .models import Question
class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self):
'''
was_published_recently() returns False for questions whose pub_date
is in the future.
'''
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)[モデルのテストクラスの実装例]
import datetime
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from .models import Question
def create_question(question_text, days):
'''
Create a question with the given 'question_text' and published the
given number of 'days' offset to now (negative for questions published
in the past, positive for questions that have yet to be published).
'''
time = timezone.now() + datetime.timedelta(days=days)
return Question.objects.create(question_text=question_text, pub_date=time)
class QuestionModelTests(TestCase):
def test_future_question(self):
'''
Question with a pub_date in the future aren't displayed on
the index page.
'''
question = create_question(question_text='Future question.', days=30)
response = self.client.get(reverse('polls:index'))
self.assertContains(response, 'No polls are available.')
self.assertQuerysetEqual(response.context['latest_question_list'], [])[ビューのテストクラスの実装例]
以下のコマンドを実行してテストを実行する。
$ python manage.py test polls
{% load static %}
<link rel="stylesheet" href="{% static 'polls/style.css' %}"/>li a {
color: green;
}
body {
background: white url("images/background.jpg");
}URLのPathコンバーターにマッチした部分をpath関数の第2引数で指定した関数 または汎用ビューのメソッドから参照する方法は以下のとおり。
urls.py ファイルの urlpatters 変数に以下の設定をした場合について説明 する。
urlpatterns = [
path('<int:param1>/<str:param2>/<path:param3>', views.func1, name='detail'),
]リクエストURLの「アプリケーション名」より後ろの部分が 「123/abcXYZ/foo/bar?aaa=bbb」の場合は、viewsモジュールのfunc1関数が 呼び出される。このとき以下の名前付きパラメータで、それぞれのPath コンバーターに一致する部分を取得できる。
Path コンバーター(URLのパスにマッチするキーワード)には、以下のものがある。 ただし、URLのパラメータはマッチする文字列に含まれないことに注意すること。
プロジェクト・コンテナ配下の プロジェクト名/settings.py ファイルを以下の ように設定する。
DEBUG = False ALLOWED_HOSTS = ['<ホスト名>',...]
既定値は「True」となっているが、「False」に設定する。「True」に設定されて いると、サーバーエラーが発生した場合に画面にデバッグ用の詳細情報が出力 されるため、本番では抑止すべきである。
HTTPリクエストヘッダの「Host」に設定された値が、ホスト名 と一致下場合 だけリクエストを受け付ける。
このエラーが発生する場合は、下記のメッセージに従って対処する。
django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.
具体的には、アプリケーションのパッケージディレク問い(チュートリアルの 場合は「polls」)にある「__init__.py」に以下の内容を記述する。
import os
if not os.environ.get('DJANGO_SETTINGS_MODULE'):
import django
os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
django.setup()「pip install」でパッケージをインストールした後、成果物の配備などで依存 パッケージを再配置する場合に、インストールしたバージョンに固定したい場合は、 以下の手順を実施する。
$ python -m pip list [--path <install-dir> | --user]
「--path install-dir」を指定すると、指定したディレクトリーにインストール されたパッケージをリスト表示する。「--user」を指定すると、ユーザー・ ホームディレクトリーにインストールされたパッケージをリスト表示する。 これらのオプションを指定しない場合は、システム・ディレクトリーにインストール されたパッケージをリスト表示する。
リスト表示されるパッケージは、インストール時に指定したパッケージの他に、 それに依存してインストールされたものも表示されるので、目的のパッケージを 探してバージョンを確認する。
以下の形式でパッケージのバージョンを指定する。
<package-name> == <version>
logging.basicConfig 関数は、logger の handler が1つも存在しない場合のみ、 引数で設定した内容の handler を設定するように実装されている。従って、既に logger の handler が存在する場合は、何も処理が行われない。確実に logging.basicConfig の設定内容を反映させるには、以下のように logger の handerを全て削除した上で、logging.basicConfig 関数を呼び出す必要がある。
import logging
root = logging.getLogger()
if root.handlers:
for handler in root.handlers:
root.removeHandler(handler)
logging.basicConfig(stream=sys.stdout, level=logging.INFO)<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ycookjp.myproject.python.packages</groupId>
<artifactId>mypackage</artifactId>
<!-- inheritated from parent
-->
<version>0.0.1-SNAPSHOT</version>
<!-- end -->
<packaging>pom</packaging>
<description>Python My Package</description>
<!--
<parent>
<groupId>ycookjp.myproject.python</groupId>
<artifactId>packages</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
-->
<name>Python My Package</name>
<properties>
<!-- Inheritated from parent project.
-->
<p.sep>${path.separator}</p.sep>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven-site.ver>3.12.1</maven-site.ver>
<maven-project-info-reports.ver>3.4.1</maven-project-info-reports.ver>
<maven-assembly.var>3.4.2</maven-assembly.var>
<maven-resources.ver>3.3.0</maven-resources.ver>
<maven-antrun.ver>3.1.0</maven-antrun.ver>
<exec-maven-plugin.ver>3.1.0</exec-maven-plugin.ver>
<maven-clean.ver>3.2.0</maven-clean.ver>
<!-- end -->
<PYTHONEXE>python3</PYTHONEXE>
<PROJECT_NAME>${project.artifactId}</PROJECT_NAME>
<MAIN_PATH>${project.build.directory}/build-tmp/src/main</MAIN_PATH>
<TEST_PATH>${project.build.directory}/build-tmp</TEST_PATH>
<PACKAGE_NAME>${PROJECT_NAME}</PACKAGE_NAME>
<TEST_PY>test_all.py</TEST_PY>
<TEST_REPORT_DIR>${project.build.directory}/site</TEST_REPORT_DIR>
</properties>
<!--
- To generate test report, run "mvn clean test site"
-->
<build>
<!-- Inheritated from parent project.
-->
<pluginManagement>
<plugins>
<!-- Basic report creation. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>${maven-site.ver}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>${maven-project-info-reports.ver}</version>
</plugin>
</plugins>
</pluginManagement>
<!-- end -->
<plugins>
<!-- cleaning files -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven-clean.ver}</version>
<configuration>
<filesets>
<fileset>
<directory>${basedir}/</directory>
<includes>
<include>src/**/__pycache__/</include>
<include>src/**/site-packages/</include>
</includes>
<followSymlinks>false</followSymlinks>
</fileset>
</filesets>
</configuration>
</plugin>
<!-- copying resources -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources.ver}</version>
<executions>
<!-- copying main files -->
<execution>
<id>copy-main-files</id>
<phase>process-test-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/build-tmp/src/main</outputDirectory>
<resources>
<resource>
<directory>${basedir}/src/main</directory>
</resource>
</resources>
</configuration>
</execution>
<!-- copying test files -->
<execution>
<id>copy-test-files</id>
<phase>process-test-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/build-tmp/tests</outputDirectory>
<resources>
<resource>
<directory>${basedir}/tests</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- Creating output directory for coverage report -->
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>${maven-antrun.ver}</version>
<executions>
<execution>
<id>mkoutdir-piplicenses</id>
<phase>process-test-resources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<mkdir dir="${TEST_REPORT_DIR}" />
</target>
</configuration>
</execution>
</executions>
</plugin>
<!-- Generating test reports -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<!-- Installing main dependency modules -->
<execution>
<id>install-main-dependencies</id>
<phase>process-test-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${PYTHONEXE}</executable>
<workingDirectory>${MAIN_PATH}</workingDirectory>
<commandlineArgs>-m pip install -t site-packages -r ${PACKAGE_NAME}/requirements.txt</commandlineArgs>
</configuration>
</execution>
<!-- Installing pip-licenses -->
<execution>
<id>install-piplicenses</id>
<phase>process-test-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${PYTHONEXE}</executable>
<workingDirectory>${TEST_PATH}</workingDirectory>
<commandlineArgs>-m pip install -t piplicenses-packages pip-licenses</commandlineArgs>
</configuration>
</execution>
<!-- Generating dependencies report -->
<execution>
<id>execute-piplicenses</id>
<phase>process-test-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${PYTHONEXE}</executable>
<workingDirectory>${MAIN_PATH}/${PACKAGE_NAME}</workingDirectory>
<commandlineArgs>-m piplicenses --format=html --output-file ${TEST_REPORT_DIR}/python-dependencies.html</commandlineArgs>
<environmentVariables>
<PYTHONPATH>${MAIN_PATH}/site-packages${p.sep}${TEST_PATH}/piplicenses-packages</PYTHONPATH>
</environmentVariables>
</configuration>
</execution>
<!-- Installing test dependency modules and pdoc3 -->
<execution>
<id>install-test-dependencies</id>
<phase>process-test-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${PYTHONEXE}</executable>
<workingDirectory>${TEST_PATH}</workingDirectory>
<commandlineArgs>-m pip install -t site-packages -r tests/requirements.txt pdoc3</commandlineArgs>
</configuration>
</execution>
<!-- Generating API document -->
<execution>
<id>python-apidocs</id>
<phase>process-test-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${PYTHONEXE}</executable>
<workingDirectory>${MAIN_PATH}</workingDirectory>
<commandlineArgs>-m pdoc --html -o ${TEST_REPORT_DIR}/apidocs --config show_source_code=False --force ${PACKAGE_NAME}</commandlineArgs>
<environmentVariables>
<PYTHONPATH>${MAIN_PATH}/site-packages${p.sep}${TEST_PATH}/site-packages</PYTHONPATH>
</environmentVariables>
</configuration>
</execution>
<!-- Generating test spec document -->
<execution>
<id>python-testspecs</id>
<phase>process-test-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${PYTHONEXE}</executable>
<workingDirectory>${TEST_PATH}</workingDirectory>
<commandlineArgs>-m pdoc --skip-errors --html -o ${TEST_REPORT_DIR}/testspecs --config show_source_code=False --force tests</commandlineArgs>
<environmentVariables>
<PYTHONPATH>${MAIN_PATH}/site-packages${p.sep}${TEST_PATH}/site-packages</PYTHONPATH>
</environmentVariables>
</configuration>
</execution>
<!-- Collecting coverage data and generating test result report -->
<execution>
<id>python-coverage</id>
<phase>test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${TEST_PATH}/site-packages/bin/coverage</executable>
<workingDirectory>${MAIN_PATH}</workingDirectory>
<arguments>
<argument>run</argument>
<argument>--branch</argument>
<argument>--source</argument>
<argument>${MAIN_PATH}/${PACKAGE_NAME}</argument>
<argument>${TEST_PATH}/tests/${TEST_PY}</argument>
</arguments>
<environmentVariables>
<PYTHONPATH>${MAIN_PATH}/site-packages${p.sep}${MAIN_PATH}${p.sep}${TEST_PATH}/site-packages${p.sep}${TEST_PATH}/tests</PYTHONPATH>
</environmentVariables>
<successCodes>
<!--
Continue generating reports even if unit test fails.
-->
<successCode>0</successCode>
<successCode>1</successCode>
</successCodes>
</configuration>
</execution>
<!-- Generating coverage report -->
<execution>
<id>python-coverage-html</id>
<phase>test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${TEST_PATH}/site-packages/bin/coverage</executable>
<workingDirectory>${MAIN_PATH}</workingDirectory>
<arguments>
<argument>html</argument>
<argument>--directory=${TEST_REPORT_DIR}/htmlcov</argument>
</arguments>
<environmentVariables>
<PYTHONPATH>${MAIN_PATH}/site-packages${p.sep}${MAIN_PATH}${p.sep}${TEST_PATH}/site-packages${p.sep}${TEST_PATH}/tests</PYTHONPATH>
</environmentVariables>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>${maven-antrun.ver}</version>
<executions>
<!-- Patching HtmlTestRunner -->
<execution>
<id>patching-htmltestrunner</id>
<phase>process-test-classes</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<failOnError>false</failOnError>
<target>
<!-- Patching HtmlTestReport package -->
<echo>Patching ${TEST_PATH}/site-packages/HtmlTestRunner/result.py</echo>
<replaceregexp match='^[ \t]*length = self._count_relevant_tb_levels\(tb\)$' replace="" encoding="UTF-8" byline="true" flags="g">
<fileset dir="${TEST_PATH}/site-packages/HtmlTestRunner" includes="result.py"/>
</replaceregexp>
<replaceregexp match='msg_lines = traceback.format_exception\(exctype, value, tb, length\)' replace="msg_lines = traceback.format_exception(exctype, value, tb)" encoding="UTF-8" byline="true" flags="g">
<fileset dir="${TEST_PATH}/site-packages/HtmlTestRunner" includes="result.py"/>
</replaceregexp>
</target>
</configuration>
</execution>
<!-- Copying test reports from build-tmp and replacing file path -->
<execution>
<id>copy-testreports</id>
<phase>site</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<failOnError>false</failOnError>
<target>
<!-- Copying test reports from build-temp directory -->
<mkdir dir="${TEST_REPORT_DIR}" />
<copy todir="${TEST_REPORT_DIR}">
<fileset dir="${project.build.directory}/build-tmp/target/site" includes="**/*" />
</copy>
<!-- Replacing file path in test reports -->
<replaceregexp match='[-_#$%()+=@0-9a-zA-Z\^`\[\]{};:\/\\]*[\/\\]*target[\/\\]*build-tmp[\/\\]*' replace="" encoding="UTF-8" byline="true" flags="g">
<fileset dir="${TEST_REPORT_DIR}/htmlcov" includes="**/*"/>
<fileset dir="${TEST_REPORT_DIR}/test-report" includes="**/*"/>
<fileset dir="${TEST_REPORT_DIR}/apidocs" includes="**/*"/>
<fileset dir="${TEST_REPORT_DIR}/testspecs" includes="**/*"/>
</replaceregexp>
<replaceregexp match='[-_#$%()+=@0-9a-zA-Z\^`\[\]{}]*_target_build-tmp_' replace="" encoding="UTF-8" byline="true" flags="g">
<fileset dir="${TEST_REPORT_DIR}/htmlcov" includes="**/*"/>
</replaceregexp>
<move todir="${TEST_REPORT_DIR}/htmlcov">
<fileset dir="${TEST_REPORT_DIR}/htmlcov" />
<mapper type="regexp" from="^.*_target_build-tmp_(.*)" to="\1" />
</move>
</target>
</configuration>
</execution>
</executions>
</plugin>
<!-- Archiving source script files -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>${maven-assembly.var}</version>
<configuration>
<descriptors>
<descriptor>src/assenbly/distribution.xml</descriptor>
</descriptors>
<finalName>${project.artifactId}-${project.version}</finalName>
<outputDirectory>${TEST_REPORT_DIR}</outputDirectory>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<id>distribution</id>
<phase>site</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- Inheritated from parent project.
-->
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>${maven-project-info-reports.ver}</version>
</plugin>
</plugins>
</reporting>
<!-- end -->
</project>[pom.xml]
<basedir>
|- pom.xml
`- src
|- assembly
| `- distribution.xml
|- main
| `- <package-name>
| |- <source-scripts>
| `- requirements.txt
|- test
| `- tests
| |- <test-scripts>
| |- test_all.py
| |- test_all.yml
| '- requirements.txt
`- site
`- site.xml