<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Python | Democratizing Data</title><link>https://b94feae1.chezo-uno.pages.dev/tags/python/</link><atom:link href="https://b94feae1.chezo-uno.pages.dev/tags/python/index.xml" rel="self" type="application/rss+xml"/><description>Python</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><copyright>© Aki Ariga 2017-2025</copyright><lastBuildDate>Fri, 01 Sep 2023 18:00:00 -0700</lastBuildDate><image><url>https://b94feae1.chezo-uno.pages.dev/media/icon_hu_9ca82a90e0d162db.png</url><title>Python</title><link>https://b94feae1.chezo-uno.pages.dev/tags/python/</link></image><item><title>VanGohan PDF</title><link>https://b94feae1.chezo-uno.pages.dev/project/vangohan-pdf/</link><pubDate>Fri, 01 Sep 2023 18:00:00 -0700</pubDate><guid>https://b94feae1.chezo-uno.pages.dev/project/vangohan-pdf/</guid><description/></item><item><title>4 Steps to Release a CLI in Python</title><link>https://b94feae1.chezo-uno.pages.dev/blog/2022-05-21_fastest-way-to-release-python-cli/</link><pubDate>Fri, 20 May 2022 23:32:41 -0700</pubDate><guid>https://b94feae1.chezo-uno.pages.dev/blog/2022-05-21_fastest-way-to-release-python-cli/</guid><description>&lt;p>This is what I learned from creating a Python CLI (&lt;a href="https://github.com/chezou/digdaglog2sql" target="_blank" rel="noopener">digdaglog2sql&lt;/a>) in a day.&lt;/p>
&lt;p>In just 4 steps, you can release a CLI written in Python easily.&lt;/p>
&lt;h2 id="create-a-project-by-using-poetry">Create a project by using poetry&lt;/h2>
&lt;p>&lt;a href="https://python-poetry.org/" target="_blank" rel="noopener">Poetry&lt;/a> is a modern Python packaging and dependency management tool. Poetry is becoming popular and defacto rapidly.&lt;/p>
&lt;p>By using Poetry, it enables us to manage package dependency, to create a project template, and to publish to PyPI.&lt;/p>
&lt;p>To setup a project with Poetry, this article is the best to read even if you build a CLI.&lt;/p>
&lt;p>&lt;a href="https://future--architect-github-io.translate.goog/articles/20210611a/?_x_tr_sl=auto&amp;amp;_x_tr_tl=en&amp;amp;_x_tr_hl=ja&amp;amp;_x_tr_pto=wapp" target="_blank" rel="noopener">https://future--architect-github-io.translate.goog/articles/20210611a/?_x_tr_sl=auto&amp;_x_tr_tl=en&amp;_x_tr_hl=ja&amp;_x_tr_pto=wapp&lt;/a>
(originally written in Japanese)&lt;/p>
&lt;p>One thing I added to my project is &lt;a href="https://pycqa.github.io/isort/index.html" target="_blank" rel="noopener">isort&lt;/a>. isort is to sort imports automatically.&lt;/p>
&lt;p>Here is the example of my project.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">taskipy&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">tasks&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">test&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">cmd&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;pytest tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">help&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;runs all unit tests&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">pr_test&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;task lint&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">fmt&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">cmd&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;black tests digdaglog2sql &amp;amp;&amp;amp; isort digdaglog2sql tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">help&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;format code&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">lint&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">cmd&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;task lint_black &amp;amp;&amp;amp; task lint_flake8 &amp;amp;&amp;amp; task lint_isort &amp;amp;&amp;amp; task lint_mypy&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">help&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;exec lint&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">lint_flake8&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;flake8 --max-line-length=88 tests digdaglog2sql&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">lint_mypy&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;mypy tests digdaglog2sql&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">lint_black&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;black --check tests digdaglog2sql&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">lint_isort&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;isort digdaglog2sql tests --check-only&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="create-a-cli-with-clickcloup">Create a CLI with Click/Cloup&lt;/h2>
&lt;p>&lt;a href="https://palletsprojects.com/p/click/" target="_blank" rel="noopener">Click&lt;/a> is a famous Python package to build a command line tool.
You can easily create a CLI by using decorator.&lt;/p>
&lt;p>Here is the example from the Click website:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">click&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@click.command&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@click.option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;--count&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Number of greetings.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@click.option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;--name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Your name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;The person to greet.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">hello&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Simple program that greets NAME for a total of COUNT times.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">click&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">echo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Hello, &lt;/span>&lt;span class="si">%s&lt;/span>&lt;span class="s2">!&amp;#34;&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;__main__&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">hello&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;a href="https://cloup.readthedocs.io/en/stable/" target="_blank" rel="noopener">Cloup&lt;/a> is an extension of Click.&lt;/p>
&lt;p>Using by Cloup, you can handle option groups and complex constraints like &lt;code>mutually_exclusive&lt;/code> as:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@option_group&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Cool options&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;--foo&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;This text should describe the option --foo.&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;--bar&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;This text should describe the option --bar.&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">constraint&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">mutually_exclusive&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Constraints of Cloup can validate the dependency and it also renders constraints in help.&lt;/p>
&lt;h2 id="use-poetry-dynamic-versioning-for-version-management">Use poetry-dynamic-versioning for version management&lt;/h2>
&lt;p>&lt;a href="https://pypi.org/project/poetry-dynamic-versioning/" target="_blank" rel="noopener">poetry-dynamic-versioning&lt;/a> is a Python package to do same thing as &lt;a href="https://pypi.org/project/setuptools-scm/" target="_blank" rel="noopener">setuptools-scm&lt;/a>. You don&amp;rsquo;t need to write version number by hand since this package use the version from tag of Git, e.g., &amp;ldquo;v.0.1.0&amp;rdquo;.&lt;/p>
&lt;p>Managing version by Git enables you to release to PyPI from GitHub Actions. This means you can release to PyPI on mobile device by releasing from GitHub.&lt;/p>
&lt;p>After installation of poetry-dynamic-versioning, you just add three thing in pyproject.toml:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;0.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry-dynamic-versioning&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">enable&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">build-system&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">requires&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;poetry-core&amp;gt;=1.0.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;poetry-dynamic-versioning&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">build-backend&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;poetry.core.masonry.api&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that build-system configuration may vary depending on how you install poetry-dynamic-versioning. See the document for detail.&lt;/p>
&lt;h2 id="introduce-github-actions-to-release-the-package-to-pypi">Introduce GitHub Actions to release the package to PyPI&lt;/h2>
&lt;p>As I mentioned above, I highly recommend to use GitHub Actions to release a Package to PyPI.&lt;/p>
&lt;p>Since GitHub provides &lt;a href="https://docs.github.com/ja/repositories/releasing-projects-on-github/automatically-generated-release-notes" target="_blank" rel="noopener">Release notes generation feature&lt;/a> now, creating a release from GitHub with triggering PyPI release is the best way to publish a new version.&lt;/p>
&lt;p>Here is the snippet of GH Actions to release to PyPI by using poetry.&lt;/p>
&lt;div class="iframely-embed">&lt;div class="iframely-responsive" style="height: 140px; padding-bottom: 0;">&lt;a href="https://github.com/chezou/digdaglog2sql/blob/ce35ce9b0220b77a79998f594304d850da231a94/.github/workflows/python-publish.yml" data-iframely-url="//iframely.net/39Qsg8o?card=small">&lt;/a>&lt;/div>&lt;/div>&lt;script async src="//iframely.net/embed.js" charset="utf-8">&lt;/script>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Upload Python Package&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">release&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">types&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">created]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">permissions&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">contents&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">read&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">deploy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/checkout@v3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/checkout@v3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Set up Python&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/setup-python@v3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">python-version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;3.x&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Install dependencies&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> python -m pip install --upgrade pip
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> pip install poetry&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Build and publish package&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> poetry version $(git describe --tags --abbrev=0)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> poetry build
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> poetry publish --username __token__ --password ${{ secrets.PYPI_API_TOKEN }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that, while PyPI API Token can be found on PyPI, if you need to create project scope token, you need to upload a package manually first.&lt;/p></description></item><item><title>digdaglog2sql</title><link>https://b94feae1.chezo-uno.pages.dev/project/digdaglog2sql/</link><pubDate>Thu, 05 May 2022 18:00:00 -0700</pubDate><guid>https://b94feae1.chezo-uno.pages.dev/project/digdaglog2sql/</guid><description/></item><item><title>Pythonistaのためのdigdag py> operator開発ガイド</title><link>https://b94feae1.chezo-uno.pages.dev/post/2019-12-24-python-custom-scripting/</link><pubDate>Tue, 24 Dec 2019 00:00:00 +0900</pubDate><guid>https://b94feae1.chezo-uno.pages.dev/post/2019-12-24-python-custom-scripting/</guid><description>
&lt;details class="print:hidden xl:hidden" >
&lt;summary>Table of Contents&lt;/summary>
&lt;div class="text-sm">
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#workflowをpushするまえにローカル環境で開発とテストをする方法">workflowをpushするまえにローカル環境で開発とテストをする方法&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#基本戦略pythonのタスクを合理的な粒度にまとめる">基本戦略:Pythonのタスクを合理的な粒度にまとめる&lt;/a>&lt;/li>
&lt;li>&lt;a href="#1-treasure-dataの公式dockerイメージを使う">1. Treasure Dataの公式Dockerイメージを使う&lt;/a>&lt;/li>
&lt;li>&lt;a href="#2-dockerイメージと同様のpythonの仮想環境を作る">2. Dockerイメージと同様のPythonの仮想環境を作る&lt;/a>&lt;/li>
&lt;li>&lt;a href="#py-operatorを含んだworkflowをローカルで実行する">py&amp;gt; operatorを含んだworkflowをローカルで実行する&lt;/a>&lt;/li>
&lt;li>&lt;a href="#1-digdagの引数を使う">1. digdagの引数を使う&lt;/a>&lt;/li>
&lt;li>&lt;a href="#2-環境変数を使う">2. 環境変数を使う&lt;/a>&lt;/li>
&lt;li>&lt;a href="#3-digdagの変数を使う">3. digdagの変数を使う&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#pythonやosのパッケージのインストール方法">PythonやOSのパッケージのインストール方法&lt;/a>&lt;/li>
&lt;li>&lt;a href="#py-operatorを含むディレクトリ構成">py&amp;gt; operatorを含むディレクトリ構成&lt;/a>&lt;/li>
&lt;li>&lt;a href="#実行時のエラーを通知する">実行時のエラーを通知する&lt;/a>&lt;/li>
&lt;li>&lt;a href="#まとめ">まとめ&lt;/a>&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;/div>
&lt;/details>
&lt;p>この記事は、&lt;a href="https://adventar.org/calendars/3932" target="_blank" rel="noopener">Arm Treasure Data Advent Calendar 2019&lt;/a>の24日目です。&lt;/p>
&lt;p>今年の夏に新しくTreasure Dataで使えるようになったPython Custom Scriptingですが、
開発する際にどういう点を気をつければ良いのかという質問をいただくことが多いので、
今日はTreasure Workflowとdigdagのpy&amp;gt; operatorを使った開発の際に気をつけることを書いて行こうと思います。&lt;/p>
&lt;p>なお、Treasure WorkflowとありますがOSSのdigdagでも活かせる内容があると思います。&lt;/p>
&lt;p>なお、既にPython Custom Scriptingが試せる環境にある方は、このGoogle Colab notebook &lt;a href="https://bit.ly/191212_cs" target="_blank" rel="noopener">https://bit.ly/191212_cs&lt;/a> で一通り試すことが出来ます。&lt;/p>
&lt;h2 id="workflowをpushするまえにローカル環境で開発とテストをする方法">workflowをpushするまえにローカル環境で開発とテストをする方法&lt;/h2>
&lt;h3 id="基本戦略pythonのタスクを合理的な粒度にまとめる">基本戦略:Pythonのタスクを合理的な粒度にまとめる&lt;/h3>
&lt;p>Treasure Workflowやdigdagのサーバーモードでは、タスク間の中間ファイルを置くためのストレージがないため、一つ一つのタスクの粒度が大きくなりがちです。
特に、Treasure Workflowでは一つ一つのtaskでコンテナが起動するため、そのオーバーヘッドを無視できません。
一方で、一つの巨大なタスクは、開発時のデバッグを難しくさせます。
そこで、開発時の戦略としては一つ一つは最低限の塊に分けた関数を用意します。そして、必要に応じてそれらを束ねる関数を用意するのが良いでしょう。&lt;/p>
&lt;p>Treasure Workflow向けのworkflowをローカル環境で開発するにはいくつかの選択肢があります。&lt;/p>
&lt;ol>
&lt;li>Treasure Dataの公式Dockerイメージを使う&lt;/li>
&lt;li>Dockerイメージと同じPythonの仮想環境を使う&lt;/li>
&lt;/ol>
&lt;h3 id="1-treasure-dataの公式dockerイメージを使う">1. Treasure Dataの公式Dockerイメージを使う&lt;/h3>
&lt;p>基本的にはTreasure Dataが提供している公式イメージを利用すると、本番環境と同じイメージが使うことができるので良いでしょう。&lt;/p>
&lt;p>その際には、他のPythonスクリプトの開発と同様に、切り分けた単体で実行できるようにして開発するのが良いでしょう。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">your_function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;default_argument&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>2019/12/23現在、最新のDocker imageは以下の2つです。&lt;/p>
&lt;ul>
&lt;li>digdag/digdag-python:3.7 &lt;a href="https://hub.docker.com/r/digdag/digdag-python" target="_blank" rel="noopener">https://hub.docker.com/r/digdag/digdag-python&lt;/a>&lt;/li>
&lt;li>digdag/digdag-anaconda3:2019.03 &lt;a href="https://hub.docker.com/r/digdag/digdag-anaconda3" target="_blank" rel="noopener">https://hub.docker.com/r/digdag/digdag-anaconda3&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>PyCharmのProfessional Editionを持っている場合、Dockerのコンテナにremote debuggerがアタッチできるので便利です。
詳しくは&lt;a href="https://www.jetbrains.com/help/pycharm/using-docker-as-a-remote-interpreter.html" target="_blank" rel="noopener">PyCharmのドキュメント&lt;/a>をご参照ください。&lt;/p>
&lt;h3 id="2-dockerイメージと同様のpythonの仮想環境を作る">2. Dockerイメージと同様のPythonの仮想環境を作る&lt;/h3>
&lt;p>Python は仮想環境を作るための &lt;code>venv&lt;/code> と呼ばれるモジュールがあります。
これを使うことで他のプロジェクトと分離したパッケージの環境を作ることができます。&lt;/p>
&lt;h4 id="digdag-python37">digdag-python:3.7&lt;/h4>
&lt;p>digdag-python:3.7と同様の環境を作るには、次のgistからrequirements.txtとconstraints.txtをダウンロードします。
&lt;a href="https://gist.github.com/chezou/d0a0fc62007af4d808752e78b31ae694" target="_blank" rel="noopener">https://gist.github.com/chezou/d0a0fc62007af4d808752e78b31ae694&lt;/a>&lt;/p>
&lt;p>その後、以下のようにコマンドを実行することでDockerイメージと同様のPython環境が構築できます。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ python -m venv .venv
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">source&lt;/span> .venv/bin/activate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">(&lt;/span>.venv&lt;span class="o">)&lt;/span>$ pip install -r requirements.txt -c constraints.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="flex px-4 py-3 mb-6 rounded-md bg-primary-100 dark:bg-primary-900">
&lt;span class="pr-3 pt-1 text-primary-600 dark:text-primary-300">
&lt;svg height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m11.25 11.25l.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0a9 9 0 0 1 18 0m-9-3.75h.008v.008H12z"/>&lt;/svg>
&lt;/span>
&lt;span class="dark:text-neutral-300">この方法では、OSによる差異まで吸収ができない場合があります。
例えば、digdag-python:3.7はDebianベースなので、OSレベルのパッケージ管理は &lt;code>apt-get&lt;/code> を使いますが、それをスクリプト内に書いても実行ができません。&lt;/span>
&lt;/div>
&lt;h4 id="digdag-anaconda3201903">digdag-anaconda3:2019.03&lt;/h4>
&lt;p>anacondaイメージと同じ環境を作りたい場合は、gistからenvironment.ymlをダウンロードします。
&lt;a href="https://gist.github.com/chezou/d0a0fc62007af4d808752e78b31ae694#file-environment-yml" target="_blank" rel="noopener">https://gist.github.com/chezou/d0a0fc62007af4d808752e78b31ae694#file-environment-yml&lt;/a>&lt;/p>
&lt;p>その後、以下のコマンドを実行するとデフォルトの環境に依存するパッケージをインストールします。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">conda env update -n base -f environment.yml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>なお、新しい環境を構築したい場合は、environment.ymlの中の &lt;code>base&lt;/code> を &lt;code>my-env&lt;/code> など好きな名前に変更した後、以下のコマンドで新規仮想環境を作成してください。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">conda env create -f environment.yml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="py-operatorを含んだworkflowをローカルで実行する">py&amp;gt; operatorを含んだworkflowをローカルで実行する&lt;/h3>
&lt;p>もし、ローカル環境でworkflow全体を実行したい場合は、 &lt;del>&lt;a href="https://github.com/treasure-data/digdag/tree/v0_10" target="_blank" rel="noopener">digdagの v0_10ブランチ&lt;/a>を使うことで、
本番環境に近いdigdag環境が用意できます&lt;/del>。&lt;/p>
&lt;div class="flex px-4 py-3 mb-6 rounded-md bg-yellow-100 dark:bg-yellow-900">
&lt;span class="pr-3 pt-1 text-red-400">
&lt;svg height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0zM12 15.75h.007v.008H12z"/>&lt;/svg>
&lt;/span>
&lt;span class="dark:text-neutral-300">2019/12/23現在は、Treasure Dataではv0_10を使っていますが、将来これは変わる恐れがあります。&lt;/span>
&lt;/div>
&lt;div class="flex px-4 py-3 mb-6 rounded-md bg-yellow-100 dark:bg-yellow-900">
&lt;span class="pr-3 pt-1 text-red-400">
&lt;svg height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0zM12 15.75h.007v.008H12z"/>&lt;/svg>
&lt;/span>
&lt;span class="dark:text-neutral-300">2021/02/14現在、v0_11へと移行しました。今後は適宜適切なブランチを使うようにしてください。
&lt;a href="https://github.com/treasure-data/digdag/pull/1502" target="_blank" rel="noopener">https://github.com/treasure-data/digdag/pull/1502&lt;/a>
&lt;a href="https://github.com/treasure-data/digdag/pull/1504" target="_blank" rel="noopener">https://github.com/treasure-data/digdag/pull/1504&lt;/a>&lt;/span>
&lt;/div>
## py> operatorにパラメータを渡す
&lt;p>py&amp;gt; operatorにパラメータを渡すには2つの方法があります。&lt;/p>
&lt;ol>
&lt;li>digdagの引数を使う&lt;/li>
&lt;li>環境変数を使う&lt;/li>
&lt;li>digdagの変数でやりとりする&lt;/li>
&lt;/ol>
&lt;h3 id="1-digdagの引数を使う">1. digdagの引数を使う&lt;/h3>
&lt;p>以下のような &lt;code>py_scripts/examples.py&lt;/code> というスクリプトがあるとします。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">print_arg&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Message is &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>msg&lt;/code> という変数を &lt;code>print_arg&lt;/code> という関数に渡す場合、以下のようなdigdagのtaskになります。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">+simple_with_arg&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">py&amp;gt;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">py_scripts.examples.print_arg&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">msg&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Hello World&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">docker&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;digdag/digdag-python:3.7&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>もし、複数の引数をPythonの関数へ渡したい場合、digdagの引数を増やせばよいでしょう。&lt;/p>
&lt;p>ここで注意してほしいのが、digdagの変数はPythonにシームレスに渡されるということです。
つまり、 &lt;code>**kwargs&lt;/code> などで引数を受け取る場合、意図しないdigdagの変数がPythonに渡る可能性に注意してください。&lt;/p>
&lt;p>例えば、上の例では &lt;code>docker&lt;/code> という変数には &lt;code>{&amp;quot;image&amp;quot;: &amp;quot;digdag/digdag-python:3.7&amp;quot;}&lt;/code> という辞書型の変数が代入されています。
ですので、 &lt;code>**kwargs&lt;/code> で受けるのではなく、明示的な引数の指定を強くおすすめします。&lt;/p>
&lt;p>た、同様に意図しないdigdagの変数と py&amp;gt; operatorの引数の衝突が起こる場合があります。
以下のようなワークフローがあったとします。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">_export&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">td&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">database&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">my_db&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">+simple_with_arg2&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">py&amp;gt;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">py_scripts.examples.print_arg_td&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">msg&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Hello World&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">docker&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;digdag/digdag-python:3.7&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>このとき、Pythonのスクリプトは以下のような関数を持っているとします。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">print_arg_td&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">td&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;#39;msg&amp;#39; is &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> and &amp;#39;td&amp;#39; is &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">td&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>simple_with_arg2&lt;/code> タスク実行時に &lt;code>print_arg_td&lt;/code> 関数へ渡される引数 &lt;code>td&lt;/code> は何になるでしょうか？
通常のPythonの感覚では、デフォルト値である &lt;code>None&lt;/code> が期待されます。
しかし、 digdagのワークフローで &lt;code>td&lt;/code> という変数がすでにexportされているため、 引数 &lt;code>td&lt;/code> には &lt;code>{&amp;quot;database&amp;quot;: &amp;quot;my_db&amp;quot;}&lt;/code> という辞書が格納されてしまいます。
これにより、変数 &lt;code>td&lt;/code> の型(&lt;code>dict&lt;/code>)が期待されていたもの(&lt;code>str&lt;/code>)との不一致が起こりうるのです。&lt;/p>
&lt;p>このようなミスマッチを避けるために、digdagで予約されている変数やよく利用される変数を避けたほうが良いでしょう。
特に &lt;code>td&lt;/code> という変数や以下の変数は避けたほうが良いです。&lt;/p>
&lt;ul>
&lt;li>td.endpoint&lt;/li>
&lt;li>td.apikey&lt;/li>
&lt;li>td.use_ssl&lt;/li>
&lt;li>td.proxy.enabled&lt;/li>
&lt;li>td.proxy.host&lt;/li>
&lt;li>td.proxy.port&lt;/li>
&lt;li>td.proxy.password&lt;/li>
&lt;li>td.proxy.user&lt;/li>
&lt;/ul>
&lt;p>これらの既に利用されている変数は将来的に変更される可能性があります。&lt;/p>
&lt;p>digdagの組み込み変数は次のドキュメントをご参照ください。
&lt;a href="http://docs.digdag.io/workflow_definition.html#using-variables" target="_blank" rel="noopener">http://docs.digdag.io/workflow_definition.html#using-variables&lt;/a>&lt;/p>
&lt;p>また、digdagは &lt;code>int&lt;/code> から &lt;code>str&lt;/code> など意図しない型の変換を行う場合があります。そのため、型のチェックや明示的な変換をおすすめします。&lt;/p>
&lt;h3 id="2-環境変数を使う">2. 環境変数を使う&lt;/h3>
&lt;p>環境変数は、py&amp;gt; operatorに変数を渡すためのもう一つの方法です。&lt;/p>
&lt;p>TDのAPI keyやAWSのsecret keyなどセキュアな情報はこの形式でやりとりすることが多いです。&lt;/p>
&lt;p>例えば、以下のような &lt;code>simple_with_env&lt;/code> というタスクがあったとします。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">+simple_with_env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">py&amp;gt;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">py_scripts.examples.print_env&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">_env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MY_ENV_VAR&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;hello&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">docker&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;digdag/digdag-python:3.7&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>このとき、 &lt;code>MY_ENV_VAR&lt;/code> は以下のようにPythonから取得できます。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">print_env&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;Env var is &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;MY_ENV_VAR&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Treasure Workflowではセキュアな情報を扱うためにdigdagの &lt;code>secrets&lt;/code> を格納するデータベースを提供しています。&lt;/p>
&lt;figure>&lt;img src="https://b94feae1.chezo-uno.pages.dev/post/2019-12-24-python-custom-scripting/digdag_secrets.png">&lt;figcaption>
&lt;h4>Digdag Secretsの設定フロー&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;ol>
&lt;li>Projectを &lt;code>td workflow push&lt;/code> でプッシュする&lt;/li>
&lt;li>secretsを &lt;code>td workflow secrets&lt;/code> でデータベースに格納する&lt;/li>
&lt;li>secretsを &lt;code>{secret:my_secret}&lt;/code> という形式でworkflowに書き、環境変数経由でPythonにわたす&lt;/li>
&lt;/ol>
&lt;p>環境変数での受け渡しをしないとsecretsはPythonに渡されません。
例えば、以下のようなワークフローとPythonスクリプトでは正しくsecretsに格納された &lt;code>td.apikey&lt;/code> という情報が渡されます。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">+simple_with_env2&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">py&amp;gt;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">py_scripts.examples.access_td&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">_env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">TD_API_KEY&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${secret:td.apikey}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">docker&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;digdag/digdag-python:3.7&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">access_td&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Able to fetch API key like &amp;#34;1234/XXXX&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">apikey&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;TD_API_KEY&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Do awesome execution&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>しかし、以下のようにdigdagの引数として渡した場合はsecretsが評価されません。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">+simple_with_env_ng&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">py&amp;gt;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">py_scripts.examples.access_td_ng&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">apikey&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${secret:td.apikey}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">docker&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;digdag/digdag-python:3.7&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">access_td_ng&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">apikey&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Always shows &amp;#34;${secret:td.apikey}&amp;#34; instead of actual API key like &amp;#34;1234/XXXX&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">apikey&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>secretsに関する詳細は、&lt;a href="https://docs.digdag.io/command_reference.html#secrets" target="_blank" rel="noopener">digdagのドキュメント&lt;/a>をご参照ください。&lt;/p>
&lt;h3 id="3-digdagの変数を使う">3. digdagの変数を使う&lt;/h3>
&lt;p>digdagのpy&amp;gt; operatorとして実行するPythonスクリプトでは、 &lt;code>import digdag&lt;/code> とすることでdigdagモジュールを利用することが出来ます。&lt;/p>
&lt;p>以下のように、 &lt;code>digdag.env.params&lt;/code> と呼ばれる変数に辞書のようにアクセスすることでdigdagの変数を直接読むことが出来ます。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">read_workflow_env&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">digdag&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">digdag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">env&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;my_msg&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>このとき、実行するworkflowは例えば以下のようなものになります。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">_export&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">my_msg&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;awesome message&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">+simple_with_arg2&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">py&amp;gt;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">py_scripts.examples.read_workflow_env&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">docker&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;digdag/digdag-python:3.7&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>なお、読み込みだけではなく&lt;code>digdag.env.store&lt;/code>を使うことでdigdagの変数の書き込みもできます。
ただし、あまり大きな変数の受け渡しはしないほうが良いでしょう。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">store_workflow_env&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">digdag&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">digdag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">env&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">store&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;my_msg&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">msg&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>このdigdagモジュールは、digdagがpy&amp;gt; operator実行時に動的に生成しているため、ローカルでは実行できないことに注意してください。
ローカルの実行を考慮する場合は、 &lt;code>try-expect&lt;/code> を利用するのが良いでしょう。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">digdag&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">digdag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">env&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">store&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;feature_query&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">feature_query&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">except&lt;/span> &lt;span class="ne">ImportError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="pythonやosのパッケージのインストール方法">PythonやOSのパッケージのインストール方法&lt;/h2>
&lt;p>Treasure WorkflowではDocker imageに入っていないパッケージは &lt;code>os.system&lt;/code> や &lt;code>subprocess.run&lt;/code>を使い実行します。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">system&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">executable&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> -m pip install --upgrade pytd==1.4.3&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">subprocess&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># arguments should be passed by list&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">executable&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-m&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;pip&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;install&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--upgrade&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;pytd==1.4.3&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>この際、パッケージのバージョンを指定しましょう。&lt;/p>
&lt;p>OSのパッケージのインストールの場合も同様です。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">system&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;apt-get update&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># Need to run before doing apt-get install&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">system&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;apt-get install -y wkhtmltopdf&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="py-operatorを含むディレクトリ構成">py&amp;gt; operatorを含むディレクトリ構成&lt;/h2>
&lt;p>一つのプロジェクトでは、以下のようなディレクトリ構成をおすすめしています。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-txt" data-lang="txt">&lt;span class="line">&lt;span class="cl">my_project
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── README.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── config
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── params.test.yml &amp;lt;- Configuration file for run through test. Mirror params.yml except for `td.database`
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── params.yml &amp;lt;- Configuration file for production
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── awesome_workflow.dig &amp;lt;- Main workflow to be executed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── ingest.dig &amp;lt;- Data ingestion workflow
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── py_scripts &amp;lt;- Python scripts directory
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── data.py &amp;lt;- Script to upload data to Arm Treasure Data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── my_script.py &amp;lt;- Main script to execute e.g. Data enrichment, ML training
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── queries &amp;lt;- SQL directory
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── example.sql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── run_test.sh &amp;lt;- Test shell script for local run through test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── test.dig &amp;lt;- Test workflow for local run through test
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>この構成に従ったdigdagプロジェクトをcookiecutter-digdagを使うことで簡単にテンプレートから生成できます。
&lt;a href="https://github.com/chezou/cookiecutter-digdag" target="_blank" rel="noopener">https://github.com/chezou/cookiecutter-digdag&lt;/a>&lt;/p>
&lt;h2 id="実行時のエラーを通知する">実行時のエラーを通知する&lt;/h2>
&lt;p>Pythonのスクリプト実行時に発生したエラーをSlackなどで通知したい事があると思います。
digdagは &lt;code>_error:&lt;/code> でworkflowが失敗した際の処理をできますが、そのとき&lt;code>${error.message}&lt;/code>の中にPythonの例外情報が入っています。&lt;/p>
&lt;p>以下のようなworkflowとPython scriptがあったとします。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">+simple_raise_error&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">py&amp;gt;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">py_scripts.examples.error_sample&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">docker&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;digdag/digdag-python:3.7&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">_error&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">echo&amp;gt;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${error.message}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">error_sample&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;a1234&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># raises ValueError&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>このとき、以下のようなログが得られます。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-txt" data-lang="txt">&lt;span class="line">&lt;span class="cl">2019-12-24 23:06:32 +0900 [INFO] (0039@[0:python]+simple^error): echo&amp;gt;: Python command failed with code 1: invalid literal for int() with base 10: &amp;#39;a1234&amp;#39; (ValueError)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> from Traceback (most recent call last):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> from File &amp;#34;.digdag/tmp/digdag-py-2-1815457087076518360/runner.py&amp;#34;, line 165, in &amp;lt;module&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> result = callable_type(**args)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> from File &amp;#34;/private/var/folders/y9/bnjb3krn39s22rmg_wvlnf7m0000gp/T/digdag-tempdir2111531196420040503/workspace/1_simple_1_2_2945225080250994454/py_scripts/examples.py&amp;#34;, line 5, in print_arg
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int(&amp;#34;a1234&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> from ValueError: invalid literal for int() with base 10: &amp;#39;a1234&amp;#39; (runtime)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>この例では、&lt;code>echo&amp;gt;&lt;/code> operatorでエラーを出力しているだけですが、Slack等に例外を送ることで定期実行しているPythonタスクの通知が簡単に行なえます。&lt;/p>
&lt;h2 id="まとめ">まとめ&lt;/h2>
&lt;p>このように、様々なポイントをおさえることでTreasure Workflowの開発をしやすくなるかと思います。
また、digdagでもいくらかのポイントは使うことができるかと思います。&lt;/p></description></item><item><title>tabula-py</title><link>https://b94feae1.chezo-uno.pages.dev/project/tabula-py/</link><pubDate>Sun, 01 Dec 2019 19:00:00 +0900</pubDate><guid>https://b94feae1.chezo-uno.pages.dev/project/tabula-py/</guid><description/></item></channel></rss>