<?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/categories/python/</link><atom:link href="https://b94feae1.chezo-uno.pages.dev/categories/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, 26 Jan 2024 18:40: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/categories/python/</link></image><item><title>Scrape Notion and convert into PDF</title><link>https://b94feae1.chezo-uno.pages.dev/blog/2024-01-26_scrape-notion-to-pdf/</link><pubDate>Fri, 26 Jan 2024 18:40:00 -0700</pubDate><guid>https://b94feae1.chezo-uno.pages.dev/blog/2024-01-26_scrape-notion-to-pdf/</guid><description>&lt;p>I love &lt;a href="https://www.vangohan.com/" target="_blank" rel="noopener">VanGohan&lt;/a>, who is a Japanese meal kits provider in Vancouver. Their meal kits are really tasty and authentic Japanese foods. I can&amp;rsquo;t live without them. When I visited Japan last year, I wasn&amp;rsquo;t too eager to find nice Japanese restaurants because of them.&lt;/p>
&lt;h2 id="recipe-on-notion-is-good-if-its-printable">Recipe on Notion is good, if it&amp;rsquo;s printable&lt;/h2>
&lt;p>They provide a recipe on Notion. Seeing the recipes on it is great since they can fix recipes quite quickly.&lt;/p>
&lt;p>However, there&amp;rsquo;s one caveat of Notion. They don&amp;rsquo;t provide printable pages. It&amp;rsquo;s super annoying to copy and past the recipes to the memo app, and print it out. I asked Notion&amp;rsquo;s support team, but they answered it isn&amp;rsquo;t a prioritized item implicitly.&lt;/p>
&lt;p>Ok, it&amp;rsquo;s automation time!&lt;/p>
&lt;h2 id="scrape-notion-with-python">Scrape Notion with Python&lt;/h2>
&lt;p>As my handy tool, I have been using Python for this kind of automation for years. Originally, I used beautifulsoup, which is great package for web scraping, but I gave it up to use it. Contents of Notion is rendered by JavaScript dynamically.&lt;/p>
&lt;p>I chose &lt;a href="https://selenium-python.readthedocs.io/" target="_blank" rel="noopener">Selenium&lt;/a> and it works like a charm.&lt;/p>
&lt;p>Here is the GitHub repository:&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/vangohan-pdf" data-iframely-url="//iframely.net/cP0eFmn?card=small">&lt;/a>&lt;/div>&lt;/div>&lt;script async src="//iframely.net/embed.js">&lt;/script>
&lt;p>They key takeaways are:&lt;/p>
&lt;ul>
&lt;li>&lt;code>chromedriver-autoinstaller&lt;/code> package is useful to avoid extra efforts of Chrome driver installation.&lt;/li>
&lt;li>Selenium is easy enough to export PDF &lt;a href="https://github.com/chezou/vangohan-pdf/blob/004eaa83a45f2a238b5065a0474903a60838a94d/fetch_vangohan.py#L200-L212" target="_blank" rel="noopener">code&lt;/a>.&lt;/li>
&lt;li>Running the script on GitHub Actions is easy. Don&amp;rsquo;t forget to install fonts if it&amp;rsquo;s not English page.&lt;/li>
&lt;/ul>
&lt;p>Originally, I thought I had to prepare a Docker image, but I was aware it was not mandatory. Managing a Docker image for this kind of hobby script would be costly. So, I&amp;rsquo;m going to keep this approach and will look back if it is the right way.&lt;/p>
&lt;p>Currently, I scheduled the &lt;a href="https://github.com/chezou/vangohan-pdf/blob/main/.github/workflows/selenium_action.yaml" target="_blank" rel="noopener">GitHub Actions workflow&lt;/a>. It will update the PDFs on the repository automatically.&lt;/p>
&lt;p>&lt;a href="https://github.com/chezou/vangohan-pdf/tree/main/docs" target="_blank" rel="noopener">https://github.com/chezou/vangohan-pdf/tree/main/docs&lt;/a>&lt;/p>
&lt;p>Edit: Now I use Cloudflare Pages to host the PDFs. You can check at &lt;a href="https://vangohan.chezo.uno/" target="_blank" rel="noopener">https://vangohan.chezo.uno/&lt;/a>.&lt;/p>
&lt;p>No Python environment on a local machine is needed anymore.&lt;/p>
&lt;p>Yay, automation is completed! 😁&lt;/p></description></item><item><title>tabula-py 2.8.0 now uses jpype to launch JVM</title><link>https://b94feae1.chezo-uno.pages.dev/blog/2023-09-09-tabula-py-280/</link><pubDate>Sat, 09 Sep 2023 17:13:08 -0700</pubDate><guid>https://b94feae1.chezo-uno.pages.dev/blog/2023-09-09-tabula-py-280/</guid><description>&lt;p>Recently, I released tabula-py 2.8.0. It is a major release because it uses &lt;a href="https://jpype.readthedocs.io/en/latest/" target="_blank" rel="noopener">jpype&lt;/a> to launch JVM. This means that it reduces JVM launch time since jpype reuse JVM via JNI.&lt;/p>
&lt;h2 id="how-fast-is-it">How fast is it?&lt;/h2>
&lt;p>I measured &lt;code>read_pdf_with_template&lt;/code> function execution time, which repeatedly launches Java process in the previous version.&lt;/p>
&lt;p>The example template contains 4 rules, which means it calls tabula-java 4 times.&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">$ cat examples/data.tabula-template.json &lt;span class="p">|&lt;/span> jq
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;page&amp;#34;&lt;/span>: 1,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;extraction_method&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;guess&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;x1&amp;#34;&lt;/span>: 153.99985500000003,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;x2&amp;#34;&lt;/span>: 565.5698550000001,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;y1&amp;#34;&lt;/span>: 123.999615,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;y2&amp;#34;&lt;/span>: 531.7446150000001,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;width&amp;#34;&lt;/span>: 411.57,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;height&amp;#34;&lt;/span>: 407.745
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;page&amp;#34;&lt;/span>: 2,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;extraction_method&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;guess&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;x1&amp;#34;&lt;/span>: 153.99985500000003,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;x2&amp;#34;&lt;/span>: 453.879855,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;y1&amp;#34;&lt;/span>: 123.99884999999993,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;y2&amp;#34;&lt;/span>: 210.44384999999994,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;width&amp;#34;&lt;/span>: 299.88,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;height&amp;#34;&lt;/span>: 86.44500000000001
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;page&amp;#34;&lt;/span>: 2,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;extraction_method&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;guess&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;x1&amp;#34;&lt;/span>: 153.99985500000003,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;x2&amp;#34;&lt;/span>: 487.53985500000005,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;y1&amp;#34;&lt;/span>: 410.99625000000003,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;y2&amp;#34;&lt;/span>: 497.44125,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;width&amp;#34;&lt;/span>: 333.54,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;height&amp;#34;&lt;/span>: 86.44500000000001
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;page&amp;#34;&lt;/span>: 3,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;extraction_method&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;guess&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;x1&amp;#34;&lt;/span>: 153.99985500000003,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;x2&amp;#34;&lt;/span>: 235.85485500000001,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;y1&amp;#34;&lt;/span>: 123.99885000000012,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;y2&amp;#34;&lt;/span>: 322.8988500000001,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;width&amp;#34;&lt;/span>: 81.855,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;height&amp;#34;&lt;/span>: 198.9
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The result is as follows:&lt;/p>
&lt;p>v2.7.0:&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 timeit &lt;span class="s1">&amp;#39;import tabula; tabula.read_pdf_with_template(&amp;#34;examples/data.pdf&amp;#34;, &amp;#34;examples/data.tabula-template.json&amp;#34;)&amp;#39;&lt;/span> 2&amp;gt; /dev/null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">1&lt;/span> loop, best of 5: 1.31 sec per loop
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>v2.8.0:&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 timeit &lt;span class="s1">&amp;#39;import tabula; tabula.read_pdf_with_template(&amp;#34;examples/data.pdf&amp;#34;, &amp;#34;examples/data.tabula-template.json&amp;#34;)&amp;#39;&lt;/span> 2&amp;gt; /dev/null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">1&lt;/span> loop, best of 5: &lt;span class="m">75&lt;/span> msec per loop
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It is 17 times faster than the previous version!&lt;/p>
&lt;h2 id="caveats">Caveats&lt;/h2>
&lt;p>Since &lt;a href="https://jpype.readthedocs.io/en/latest/api.html#jpype.shutdownJVM" target="_blank" rel="noopener">jpype doesn&amp;rsquo;t allow to reboot JVM&lt;/a>, you can pass &lt;code>java_options&lt;/code> for the first time only. If you want to change &lt;code>java_options&lt;/code>, you need to restart Python process.&lt;/p>
&lt;h2 id="challenges-for-releasing-v280">Challenges for releasing v2.8.0&lt;/h2>
&lt;p>I had to solve several challenges to release this version.&lt;/p>
&lt;h3 id="the-test-issue-with-different-java_options">The test issue with different &lt;code>java_options&lt;/code>&lt;/h3>
&lt;p>As I mentioned, jpype doesn&amp;rsquo;t allow to reboot JVM. This causes unit test with different &lt;code>java_options&lt;/code> to fail. I solved this by separating run with nox session.&lt;/p>
&lt;p>See &lt;a href="https://github.com/chezou/tabula-py/pull/356/files#r1306600161" target="_blank" rel="noopener">https://github.com/chezou/tabula-py/pull/356/files#r1306600161&lt;/a> for details.&lt;/p>
&lt;p>This limitation is not a big deal for tabula-py users because tabula-py users don&amp;rsquo;t need to change &lt;code>java_options&lt;/code> frequently.&lt;/p>
&lt;h3 id="read-the-docs-default-behavior-change">Read the docs default behavior change&lt;/h3>
&lt;p>Read the docs changed the default installation packages for Sphinx. I didn&amp;rsquo;t declared the dependency for Sphinx, so it caused the build failure.&lt;/p>
&lt;p>The default behavior of RTD was just installing the latest version of Sphinx and sphinx-rtd-theme, however, now it installs very old version of them like: &lt;a href="https://github.com/readthedocs/readthedocs.org/issues/10670#issuecomment-1694761746" target="_blank" rel="noopener">https://github.com/readthedocs/readthedocs.org/issues/10670#issuecomment-1694761746&lt;/a>&lt;/p>
&lt;p>I solved this by pinning the versions of dependency for Sphinx and sphinx-rtd-theme.&lt;/p></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>py> operator development guide for Python users</title><link>https://b94feae1.chezo-uno.pages.dev/blog/2020-03-05_py-operator-development-guide-for-python-users/</link><pubDate>Thu, 05 Mar 2020 14:15:52 +0000</pubDate><guid>https://b94feae1.chezo-uno.pages.dev/blog/2020-03-05_py-operator-development-guide-for-python-users/</guid><description>&lt;p>
&lt;/p>
&lt;h1 id="how-to-build--test-custom-scripts-on-local-env-before-pushing">How to build &amp;amp; test custom scripts on local env before pushing&lt;/h1>
&lt;p>General strategy:&lt;/p>
&lt;ul>
&lt;li>Make a Python task reasonable granularity to run on local env&lt;/li>
&lt;/ul>
&lt;p>Since Treasure Workflow doesn&amp;rsquo;t have intermediate storage between tasks, a task can be huge. Considering container launch time, it would be better to create a single huge task, but it makes difficult for debugging. Starting from creating a reasonable size of function which is able to debug easily. Then, you can create a function that calls those minimal functions at once.&lt;/p>
&lt;p>There are few options to develop py&amp;gt; operator on the local environment.&lt;/p>
&lt;ol>
&lt;li>Use TD docker image&lt;/li>
&lt;li>Create a Python virtual environment on local env&lt;/li>
&lt;/ol>
&lt;h2 id="1-use-td-docker-image">1. Use TD docker image&lt;/h2>
&lt;p>To develop a single py&amp;gt; operator task, you can use the official docker image to run python tasks locally. Like ordinal Python script, you can add the main guard like:&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>As of Mar. 5, 2020, our latest official images are shown as the following:&lt;/p>
&lt;ul>
&lt;li>digdag/digdag-python:3.7
&lt;/li>
&lt;li>digdag/digdag-anaconda3:2019.03
&lt;/li>
&lt;/ul>
&lt;p>If you want to run a debugger toward Docker container, we recommend using PyCharm to run a remote debugger. See also
.&lt;/p>
&lt;h2 id="2-create-a-python-virtual-environment-on-local-env">2. Create a Python virtual environment on local env&lt;/h2>
&lt;p>Python provides venv to create virtual environments, you can create the same environment by using pip.&lt;/p>
&lt;p>Download requirements.txt and constraints.txt from
and you can install dependencies as same environment with digdag-python:3.7 as:&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 class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Using this virtual environment, you can develop by using the same packages on the local environment.&lt;/p>
&lt;p>Note that this approach can&amp;rsquo;t ensure OS differences, which means the production environment is running on Debian but the development environment might be Windows/macOS X. This causes errors when executing OS-dependent commands like apt-get.&lt;/p>
&lt;p>If you want to create the same environment with anaconda image, you can download environment.yml from
, and run:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&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>Now you have the same Python packages with digdag/digdag-anaconda3:2019.03&lt;/p>
&lt;p>Note that this command will overwrite existing conda environment, we highly recommend to modify name in environment.yml from base to your environment name like my-env, and run:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&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;h2 id="test-a-workflow-including-python">Test a workflow including Python&lt;/h2>
&lt;p>If you want to run an entire workflow on the local environment, &lt;del>you can use
&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">As of Mar 5, 2020, Treasure Data uses digdag v0_10 branch, but it may change in the near future.&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">As of Feb 14, 2021, Treasure Data moved to v0_11 branch. You may use the latest release branch.
&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>
&lt;h1 id="passing-parameters-to-py-operator">Passing Parameters to py&amp;gt; operator&lt;/h1>
&lt;p>There are two ways to pass parameters into py&amp;gt; operator:&lt;/p>
&lt;ol>
&lt;li>ordinal digdag argument&lt;/li>
&lt;li>environment variable&lt;/li>
&lt;li>digdag variable&lt;/li>
&lt;/ol>
&lt;h2 id="1-digdag-argument">1. digdag argument&lt;/h2>
&lt;p>Assuming we have a Python script named py_scripts/examples.py like:&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>Passing msg argument from simple_with_arg task can be like:&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>If you want to pass multiple arguments, you can add arguments in your function, then add them into digdag arguments as well.&lt;/p>
&lt;p>Note that digdag arguments can be passed into Python seamlessly so that you might face unintended variables passed by using keyword arguments **kwargs.&lt;/p>
&lt;p>For example, in this case, docker variable can be passed as a dictionary {&amp;ldquo;image&amp;rdquo;: &amp;ldquo;digdag/digdag-python:3.7&amp;rdquo;}. We recommend having implicit arguments on a Python function.&lt;/p>
&lt;p>Note that there might be unintended conflicts between digdag and py&amp;gt; operator. Assuming you set some digdag variables like the following:&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>having python function print_arg_td with td argument like the following:&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="n">td&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>In this case, td variable never can be None since exported td variable, i.e., {&amp;ldquo;database&amp;rdquo;: &amp;ldquo;my_db&amp;rdquo;} always should be passed. This may cause type mismatches like dictionary and string. We recommend avoiding to use preserved arguments for digdag, like td variables like:&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>Note that these variables might be changed in the future. There are build-in digdag variables. See digdag build-in variables at
&lt;/p>
&lt;p>Also, digdag might converts unintended type e.g., an integer from a string, so we recommend to evaluate or explicitly convert type on a Python function.&lt;/p>
&lt;h2 id="2-environment-variable">2. environment variable&lt;/h2>
&lt;p>Environment variables can be another option to pass parameters to py&amp;gt; operator. An environment variable is reasonable for passing secure information or secrets.&lt;/p>
&lt;p>For example, if we have a task simple_with_env&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>This MY_ENV_VAR can be accessed by using os.environ like:&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>&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>Using an environment variable should be important especially when you need to use secrets information e.g. Treasure Data API key or AWS secrets key, etc.&lt;/p>
&lt;p>digdag has a feature to store secrets information. Secrets are stored on digdag (or Treasure Workflow) database when executing td workflow secrets subcommand.&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img src="https://chezo.uno/post/2019-12-24-python-custom-scripting/digdag_secrets.png" alt="" loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;p>Assuming you&amp;rsquo;ve set a secret named td.apikey. This secret can be passed to py&amp;gt; operator like:&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: 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>from py_scripts/examples.py like:&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>&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="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>If you try to pass secrets from ordinal digdag arguments, secrets will never be fetched from secrets DB. For example, if you have a task like the following:&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: 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>by using the following script like:&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">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="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;span class="line">&lt;span class="cl"> &lt;span class="c1"># Always shows &amp;#34;${secret:td.apikey}&amp;#34; insted of actual API key like &amp;#34;1234/XXXX&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="3-digdag-variable">3. digdag variable&lt;/h2>
&lt;p>If you want to read digdag variable in a Python script, you can use digdag.env.params as the following:&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 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="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>Note that import digdag can be run only when the script is run as a digdag py&amp;gt; operator task. If you want to avoid import error, you should write try-except syntax like:&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;h1 id="directory-structures">Directory structures&lt;/h1>
&lt;p>I recommend having the following directory structure.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="n">my_project&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">├──&lt;/span> &lt;span class="n">README&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">md&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">├──&lt;/span> &lt;span class="n">config&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">├──&lt;/span> &lt;span class="n">params&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">test&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">yml&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="n">Configuration&lt;/span> &lt;span class="n">file&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">run&lt;/span> &lt;span class="n">through&lt;/span> &lt;span class="n">test&lt;/span>&lt;span class="o">.&lt;/span> &lt;span class="n">Mirror&lt;/span> &lt;span class="n">params&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">yml&lt;/span> &lt;span class="n">except&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="err">`&lt;/span>&lt;span class="n">td&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">database&lt;/span>&lt;span class="err">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">└──&lt;/span> &lt;span class="n">params&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">yml&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="n">Configuration&lt;/span> &lt;span class="n">file&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">production&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">├──&lt;/span> &lt;span class="n">awesome_workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dig&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="n">Main&lt;/span> &lt;span class="n">workflow&lt;/span> &lt;span class="n">to&lt;/span> &lt;span class="n">be&lt;/span> &lt;span class="n">executed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">├──&lt;/span> &lt;span class="n">ingest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dig&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="n">Data&lt;/span> &lt;span class="n">ingestion&lt;/span> &lt;span class="n">workflow&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">├──&lt;/span> &lt;span class="n">py_scripts&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="n">Python&lt;/span> &lt;span class="n">scripts&lt;/span> &lt;span class="n">directory&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">├──&lt;/span> &lt;span class="n">__init__&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">├──&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">py&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="ne">Script&lt;/span> &lt;span class="n">to&lt;/span> &lt;span class="n">upload&lt;/span> &lt;span class="n">data&lt;/span> &lt;span class="n">to&lt;/span> &lt;span class="n">Arm&lt;/span> &lt;span class="n">Treasure&lt;/span> &lt;span class="n">Data&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">└──&lt;/span> &lt;span class="n">my_script&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">py&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="n">Main&lt;/span> &lt;span class="n">script&lt;/span> &lt;span class="n">to&lt;/span> &lt;span class="n">execute&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">g&lt;/span>&lt;span class="o">.&lt;/span> &lt;span class="n">Data&lt;/span> &lt;span class="n">enrichment&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ML&lt;/span> &lt;span class="n">training&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">├──&lt;/span> &lt;span class="n">queries&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="n">SQL&lt;/span> &lt;span class="n">directory&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">└──&lt;/span> &lt;span class="n">example&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sql&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">├──&lt;/span> &lt;span class="n">run_test&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sh&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="n">Test&lt;/span> &lt;span class="n">shell&lt;/span> &lt;span class="n">script&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">local&lt;/span> &lt;span class="n">run&lt;/span> &lt;span class="n">through&lt;/span> &lt;span class="n">test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">└──&lt;/span> &lt;span class="n">test&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dig&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="n">Test&lt;/span> &lt;span class="n">workflow&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">local&lt;/span> &lt;span class="n">run&lt;/span> &lt;span class="n">through&lt;/span> &lt;span class="n">test&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can generate this structure from a template by using cookiecutter-digdag.&lt;/p>
&lt;p>
&lt;/p>
&lt;h1 id="how-to-install-python-packages--os-packages">How to install Python packages / OS packages&lt;/h1>
&lt;p>For installation of Python packages, you can use os.syste or subprocess.run like:&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>Ensure you set the version number of Python package.&lt;/p>
&lt;p>To install OS packages, you can execute like the following:&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;h1 id="how-to-readwrite-tiny-variables-between-digdag-tasks">How to read/write tiny variables between digdag tasks&lt;/h1>
&lt;p>To read a digdag variable, you can use digdag.env.params as mentioned above.&lt;/p>
&lt;p>To pass variables to another Python task, you can use import 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>This example code sets my_msg variable which is able to use the following tasks like:&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">+store_msg&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.store_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">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;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">+restore_msg&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">${my_msg}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="error-notification-with-python-stack-trace">Error notification with Python stack trace&lt;/h1>
&lt;p>digdag has _error: syntax to send a notification for an error message. You can access ${error.message} digdag variable to send the notification for Slack or Email.&lt;/p>
&lt;p>Assuming that if we have the following 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">+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;p>with this Python script:&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">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>This script always raises ValueError and the workflow log shows stack trace of Python as the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="mi">2019&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">12&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">24&lt;/span> &lt;span class="mi">23&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">06&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">32&lt;/span> &lt;span class="o">+&lt;/span>&lt;span class="mi">0900&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">INFO&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">0039&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="n">python&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="n">simple&lt;/span>&lt;span class="o">^&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">):&lt;/span> &lt;span class="n">echo&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Python&lt;/span> &lt;span class="n">command&lt;/span> &lt;span class="n">failed&lt;/span> &lt;span class="n">with&lt;/span> &lt;span class="n">code&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">invalid&lt;/span> &lt;span class="n">literal&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="ne">int&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="n">with&lt;/span> &lt;span class="n">base&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;a1234&amp;#39;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ValueError&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">from&lt;/span> &lt;span class="n">Traceback&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">most&lt;/span> &lt;span class="n">recent&lt;/span> &lt;span class="n">call&lt;/span> &lt;span class="n">last&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">from&lt;/span> &lt;span class="ne">File&lt;/span> &lt;span class="s2">&amp;#34;.digdag/tmp/digdag-py-2-1815457087076518360/runner.py&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="mi">165&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">module&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">callable_type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">from&lt;/span> &lt;span class="ne">File&lt;/span> &lt;span class="s2">&amp;#34;/private/var/folders/y9/bnjb3krn39s22rmg_wvlnf7m0000gp/T/digdag-tempdir2111531196420040503/workspace/1_simple_1_2_2945225080250994454/py_scripts/examples.py&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">print_arg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ne">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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">from&lt;/span> &lt;span class="n">ValueError&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">invalid&lt;/span> &lt;span class="n">literal&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="ne">int&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="n">with&lt;/span> &lt;span class="n">base&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;a1234&amp;#39;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">runtime&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In this example, we use echo&amp;gt; operator to show the error message, but you can use mail&amp;gt; operator for sending Email or http&amp;gt; operator to send a Slack message.&lt;/p></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>How to release Python package from GitHub Actions</title><link>https://b94feae1.chezo-uno.pages.dev/blog/2019-11-26_how-to-release-python-package-from-github-actions-d5a1d8edba6e/</link><pubDate>Tue, 26 Nov 2019 01:42:11 +0900</pubDate><guid>https://b94feae1.chezo-uno.pages.dev/blog/2019-11-26_how-to-release-python-package-from-github-actions-d5a1d8edba6e/</guid><description>&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img src="./0__hOksODxf9TX1BkS0.jpg" alt="" loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
Photo by
on 
&lt;/p>
&lt;p>Recently, I changed my CI from Travis to GitHub Actions. GitHub Actions is handy and useful for testing, publishing Python packages.&lt;/p>
&lt;h3 id="testing-python-code-on-githubactions">Testing Python code on GitHub Actions&lt;/h3>
&lt;p>Migration from Travis is super easy, just writing a simple workflow like:&lt;/p>
&lt;p>
&lt;/p>
&lt;p>The benefits of GitHub Actions for Python are:&lt;/p>
&lt;ul>
&lt;li>We can use build matrix (e.g., OS and Python versions) like Travis&lt;/li>
&lt;li>Launch time of GitHub is faster than Travis&lt;/li>
&lt;li>Easy for additional dependency installation by using &lt;code>uses&lt;/code> syntax, which uses another workflow&lt;/li>
&lt;/ul>
&lt;p>For example, installing JDK can be written as:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">- uses: actions/setup-java@v1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> with:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> java-version: &amp;#39;12&amp;#39;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> java-package: jdk
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> architecture: x64
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The downside of GitHub Actions are:&lt;/p>
&lt;ul>
&lt;li>Unable to
&lt;/li>
&lt;li>Hard to find the resources for debugging on the internet and unable to ssh to the instance&lt;/li>
&lt;/ul>
&lt;h3 id="releasing-python-package-from-github-actions-topypi">Releasing Python package from GitHub Actions to PyPI&lt;/h3>
&lt;p>I created the workflow like the following sequence:&lt;/p>
&lt;ol>
&lt;li>Push a tag from local, or create a tag on GitHub. Using
enables you to make a new version from Git tag&lt;/li>
&lt;li>GitHub Actions creates GitHub release from the tag&lt;/li>
&lt;li>GitHub Actions publishes wheel to PyPI by using PyPI API Token&lt;/li>
&lt;/ol>
&lt;p>You can see the actual workflow on GitHub:&lt;/p>
&lt;p>
&lt;/p>
&lt;p>The key points are:&lt;/p>
&lt;ol>
&lt;li>Triggering the workflow from Git tag&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">on:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> push:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> tags:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &amp;#39;v\*&amp;#39;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>2. Adding dependency for deploy task&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">deploy:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> needs: release
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>needs&lt;/code> syntax supports to write dependency. In this case, I describe &lt;code>release&lt;/code> job for creating GitHub release, and then &lt;code>deploy&lt;/code> job publishes the package to PyPI.&lt;/p>
&lt;p>3. Preparation secrets for PyPI&lt;/p>
&lt;p>Recently, PyPI provides API tokens for package publishments so that you can get an API token for the specific project. See details on the official document since it is under beta, and spec might change.&lt;/p>
&lt;p>
&lt;/p>
&lt;p>After getting API Token from PyPI, you can set secrets on GitHub by clicking “Settings” -&amp;gt; “Secrets” on the project page. Using my example workflow, you should set &lt;code>__token__&lt;/code> for &lt;code>PYPI_USERS&lt;/code> , and a token starting with &lt;code>pypi-&lt;/code> got on PyPI configuration for &lt;code>PYPI_PASSWORD&lt;/code> .&lt;/p>
&lt;p>Now, you can publish Python package to PyPI by just tagging on GitHub.&lt;/p></description></item><item><title>Use Markdown document on brand new PyPI</title><link>https://b94feae1.chezo-uno.pages.dev/blog/2018-04-17_use-markdown-document-on-brand-new-pypi-9723024f09c2/</link><pubDate>Tue, 17 Apr 2018 13:21:33 +0900</pubDate><guid>https://b94feae1.chezo-uno.pages.dev/blog/2018-04-17_use-markdown-document-on-brand-new-pypi-9723024f09c2/</guid><description>&lt;p>Yesterday, PyPI was renewed to the next-generation site. It is modern and stylish one.&lt;/p>
&lt;p>&lt;a href="http://twitter.com/aodag" title="Twitter profile for @aodag" target="_blank" rel="noopener">@aodag&lt;/a> told me that &lt;a href="https://www.python.org/dev/peps/pep-0566/#description-content-type-optional" target="_blank" rel="noopener">PEP 566&lt;/a>, which was accepted Feb. 2018, allows us for a document on PyPI to use not only reStructuredText but also other formats such as Markdown.&lt;/p>
&lt;p>So I enabled my Markdown document on brand-new PyPI.&lt;/p>
&lt;h3 id="upgrade-python-packages-if-necessary">Upgrade Python packages (if necessary)&lt;/h3>
&lt;p>We can use Markdown with setuptools &lt;a href="http://setuptools.readthedocs.io/en/latest/history.html#v38-6-0" target="_blank" rel="noopener">as of v.38.6.0&lt;/a>. Let’s upgrade you python packages if needed. Without that, Markdown description will not be rendered appropriately.&lt;/p>
&lt;p>$ python -m pip install --upgrade pip
$ pip install &amp;ndash;upgrade wheel&lt;br>
$ pip --version
pip 10.0.0 from c:\\users\\chezo\\documents\\source\\tabula-py\\venv\\lib\\site-packages\\pip (python 3.6)
$ pip list&lt;br>
Package Version Location&lt;br>
-&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;- &amp;mdash;&amp;mdash;&amp;mdash;&amp;ndash; &amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;ndash;&lt;br>
(&amp;hellip;snip&amp;hellip;)&lt;br>
setuptools 38.1.0&lt;br>
(&amp;hellip;snip&amp;hellip;)&lt;br>
wheel 0.31.0&lt;/p>
&lt;h3 id="modify-setuppy">Modify setup.py&lt;/h3>
&lt;p>If you’ve already used README.md as a long description on PyPI, all you have to do is to add &lt;code>long_description_content_type&lt;/code> to setup.py as follows:&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="n">long_description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;README.md&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">long_description_content_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;text/markdown&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can see the full description of the PR :&lt;/p>
&lt;p>&lt;a href="https://github.com/chezou/tabula-py/pull/85" title="https://github.com/chezou/tabula-py/pull/85" target="_blank" rel="noopener">&lt;strong>Handle markdown long description for Pypi by chezou · Pull Request #85 · chezou/tabula-py&lt;/strong>&lt;br>
_Thanks for PEP 566, as of setuptools v38.6.0, PyPI can render long description written in markdown. This PR allows…_github.com&lt;/a>&lt;a href="https://github.com/chezou/tabula-py/pull/85" target="_blank" rel="noopener">&lt;/a>&lt;/p>
&lt;h3 id="build-a-wheel-and-upload-withtwine">Build a wheel and upload with twine&lt;/h3>
&lt;p>Now, you can build a wheel and upload with twine.&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 setup.py bdist_wheel
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ twine upload dist/*
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;figure>&lt;img src="https://b94feae1.chezo-uno.pages.dev/blog/2018-04-17_use-markdown-document-on-brand-new-pypi-9723024f09c2/1__TsTQiTt6wOa5zxTxQzpTsQ.png">&lt;figcaption>
&lt;h4>The Markdown document was rendered!&lt;/h4>
&lt;/figcaption>
&lt;/figure>
The Markdown document was rendered!&lt;/p>
&lt;p>CAVEAT: I didn’t upgrade PyPI because it is too much to bump up for just rendering Markdown. I &lt;a href="https://test.pypi.org/project/tabula-py/1.0.0/" target="_blank" rel="noopener">tested on test.pypi.org&lt;/a>.&lt;/p>
&lt;h3 id="references">References&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://www.python.org/dev/peps/pep-0566/#description-content-type-optional" target="_blank" rel="noopener">PEP 566&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/pypa/readme_renderer" target="_blank" rel="noopener">https://github.com/pypa/readme_renderer&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/pypa/sampleproject/pull/66" target="_blank" rel="noopener">https://github.com/pypa/sampleproject/pull/66&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>新しいPyPIでMarkdownのドキュメントを使う</title><link>https://b94feae1.chezo-uno.pages.dev/post/2018-04-17_---pypi-markdown-----------14e40d90ff3f/</link><pubDate>Tue, 17 Apr 2018 12:55:15 +0900</pubDate><guid>https://b94feae1.chezo-uno.pages.dev/post/2018-04-17_---pypi-markdown-----------14e40d90ff3f/</guid><description>&lt;p>長い間、Pythonでパッケージを作った場合、構造化されたドキュメントを使う場合は、&lt;a href="http://www.sphinx-doc.org/ja/master/usage/restructuredtext/basics.html" target="_blank" rel="noopener">reStructuredText&lt;/a> (reST)でドキュメントを書く必要がありました。&lt;/p>
&lt;p>ところが、&lt;a href="https://twitter.com/aodag" target="_blank" rel="noopener">aodag さん&lt;/a>に教えてもらったのですが2018年2月にアクセプトされた&lt;a href="https://www.python.org/dev/peps/pep-0566/#description-content-type-optional" target="_blank" rel="noopener">PEP 566&lt;/a>のおかげで、PyPIのドキュメントがtxtかreSTでなければいけないという制約が外れました。そこで、昨日新しくなったばかりのPyPIでMarkdownがレンダーできる方法を試してみました。&lt;/p>
&lt;h3 id="必要に応じてライブラリをupgradeする">(必要に応じて)ライブラリをupgradeする&lt;/h3>
&lt;p>Markdownが扱えるようになったのは、&lt;a href="http://setuptools.readthedocs.io/en/latest/history.html#v38-6-0" target="_blank" rel="noopener">setuptoolsのv38.6.0&lt;/a>からです。古いバージョンだとMarkdownがレンダーされないので、合わせてpipとwheelをupgradeしておきました。&lt;/p>
&lt;p>$ python -m pip install --upgrade pip
$ pip install &amp;ndash;upgrade wheel&lt;br>
$ pip --version
pip 10.0.0 from c:\\users\\chezo\\documents\\source\\tabula-py\\venv\\lib\\site-packages\\pip (python 3.6)
$ pip list&lt;br>
Package Version Location&lt;br>
-&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;- &amp;mdash;&amp;mdash;&amp;mdash;&amp;ndash; &amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;ndash;&lt;br>
(&amp;hellip;snip&amp;hellip;)&lt;br>
setuptools 38.1.0&lt;br>
(&amp;hellip;snip&amp;hellip;)&lt;br>
wheel 0.31.0&lt;/p>
&lt;h3 id="setuppyを編集する">setup.pyを編集する&lt;/h3>
&lt;p>&lt;code>long_description_content_type&lt;/code>という項目をsetup.pyに追加することで、reST以外の形式を使えるようになります。Markdownであれば例えば以下のようになると思います。&lt;/p>
&lt;p>long_description=open(&amp;lsquo;README.md&amp;rsquo;).read(),&lt;br>
long_description_content_type=”text/markdown”,&lt;/p>
&lt;p>元々README.mdを読むようにしていたので、今回は &lt;code>long_description_content_type&lt;/code> の行を追加するだけで済みました。&lt;/p>
&lt;p>実際のPRはこちらです。&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/tabula-py/pull/85" data-iframely-url="//cdn.iframe.ly/HWuHFUO?card=small">&lt;/a>&lt;/div>&lt;/div>&lt;script async src="//cdn.iframe.ly/embed.js" charset="utf-8">&lt;/script>
&lt;h3 id="wheelを作ってtwineでアップロードする">wheelを作ってtwineでアップロードする&lt;/h3>
&lt;p>後はいつもどおりwheelを作ってtwineでPyPIにアップロードしました。なお、&lt;/p>
&lt;p>$ python setup.py bdist\_wheel
$ twine upload dist/*&lt;/p>
&lt;figure>&lt;img src="https://b94feae1.chezo-uno.pages.dev/post/2018-04-17_---pypi-markdown-----------14e40d90ff3f/1_dskznp-UI9jw4D9L6gAQLA.png">&lt;figcaption>
&lt;h4>test.pypi.orgのMarkdownのドキュメントの様子&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>今回はこれだけのためにpatch versionを上げるのもなぁということで、test.pypi.orgにあげています。&lt;/p>
&lt;div class="iframely-embed">&lt;div class="iframely-responsive" style="height: 140px; padding-bottom: 0;">&lt;a href="https://test.pypi.org/project/tabula-py/" data-iframely-url="//cdn.iframe.ly/NCS5gmm">&lt;/a>&lt;/div>&lt;/div>&lt;script async src="//cdn.iframe.ly/embed.js" charset="utf-8">&lt;/script>
&lt;h3 id="参考文献">参考文献&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://www.python.org/dev/peps/pep-0566/#description-content-type-optional" target="_blank" rel="noopener">PEP 566&lt;/a>の関連箇所&lt;/li>
&lt;li>&lt;a href="https://github.com/pypa/readme_renderer" target="_blank" rel="noopener">https://github.com/pypa/readme_renderer&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/pypa/sampleproject/pull/66" target="_blank" rel="noopener">https://github.com/pypa/sampleproject/pull/66&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Python basics: package management</title><link>https://b94feae1.chezo-uno.pages.dev/blog/2017-08-30_python-basics--package-management-462918458f96/</link><pubDate>Wed, 30 Aug 2017 11:31:15 +0900</pubDate><guid>https://b94feae1.chezo-uno.pages.dev/blog/2017-08-30_python-basics--package-management-462918458f96/</guid><description>&lt;p>Python is a very famous programming language for machine learning. In this article, I will introduce basic Python environment.&lt;/p>
&lt;h3 id="glossary">Glossary&lt;/h3>
&lt;p>I will introduce basic terms about Python package management.&lt;/p>
&lt;ul>
&lt;li>pip: A tool for package installation. It retrieves Python packages from &lt;a href="http://pypi.python.org/" target="_blank" rel="noopener">PyPI&lt;/a>. pip is gem command of Ruby.&lt;/li>
&lt;li>virtualenv: Package isolation tool for Python. It has similar function with bundler of Ruby, but it also has the function to change Python versions over 2.x and 3.x.&lt;/li>
&lt;li>venv: It is an official tool for package isolation introduced from Python 3.3. But, if you want to use Python 2.x or you are Debian/Ubuntu user, I recommend you to use virtualenv.&lt;/li>
&lt;/ul>
&lt;p>venv switches with a command like &lt;code>python3.5 -m venv some-awesome-env&lt;/code>, so it can’t handle over Python 2 and 3. venv installed by Debian/Ubuntu installs useless dependencies for other OSs, so I’m an Ubuntu user so I don’t use venv.&lt;/p>
&lt;p>These are common tool sets for many Pythonistas. They are &lt;a href="https://packaging.python.org/guides/tool-recommendations/" target="_blank" rel="noopener">recommended tools&lt;/a> of &lt;a href="https://www.pypa.io/en/latest/" target="_blank" rel="noopener">PyPA&lt;/a>, a working group that maintains many of the relevant projects in Python packaging.&lt;/p>
&lt;p>There is one more tool that is for the specific purpose.&lt;/p>
&lt;ul>
&lt;li>conda: conda is a tool for package management for scientific computation developed by &lt;a href="https://www.anaconda.com/" target="_blank" rel="noopener">Anaconda&lt;/a>, Inc. It can manage not only Python but also R. PyData community loves conda.&lt;/li>
&lt;/ul>
&lt;p>I use &lt;a href="http://blog.cloudera.com/blog/2017/05/create-conda-recipe-to-use-c-extended-python-library-on-pyspark-cluster-with-cloudera-data-science-workbench/" target="_blank" rel="noopener">conda for my work&lt;/a>, but I recommend you to know the pros/cons of conda and virtualenv/venv and chose write tool for your purpose.&lt;/p>
&lt;h3 id="installation-ofpython">Installation of Python&lt;/h3>
&lt;p>Since it is 2017, Python beginners should use the latest version of Python 3. However, there are some cases to use Python 2.x for some painful reasons.&lt;/p>
&lt;p>If you need to install Python 2 and 3, you can install multiple Python with package management tools like &lt;code>apt&lt;/code> or &lt;code>yum&lt;/code>. In Ubuntu, you can install Python 2.7 with &lt;code>apt install python-dev&lt;/code>, and you can install Python 3.6 via &lt;code>apt install python3-dev&lt;/code>.&lt;/p>
&lt;p>After installation, you can see the Pythons under &lt;code>/usr/bin&lt;/code>:&lt;/p>
&lt;p>/usr/bin/python #&amp;lt;- 2.7&lt;br>
/python2 #&amp;lt;- 2.7&lt;br>
/python2.7 #&amp;lt;- 2.7&lt;br>
/python3 #&amp;lt;- 3.6&lt;br>
/python3.6 #&amp;lt;- 3.6&lt;/p>
&lt;p>If you’re macOS user, you can install both Python 2 and 3 via &lt;code>brew install&lt;/code> or &lt;code>port install&lt;/code>.&lt;/p>
&lt;p>For Windows users, you can install Python 2 and 3 using official installer or Chocolatey. From Python 3.6 for Windows, there is &lt;code>py&lt;/code> command that switches Python version.&lt;/p>
&lt;p>&lt;strong>Caution&lt;/strong>: Never try to keep using System Python. System Python is often old, and it depends on system critical system such as yum. If you run &lt;code>sudo pip install&lt;/code> carelessly, there is a risk of destroying the environment of the OS itself.&lt;/p>
&lt;h3 id="package-management">Package management&lt;/h3>
&lt;p>As I mentioned, you should not do &lt;code>sudo pip install awesome-package&lt;/code>. Hence, Many important systems depend on system Python, don’t use &lt;code>sudo pip&lt;/code>.&lt;/p>
&lt;p>If you’re a venv user, this tutorial will help you.&lt;br>
&lt;a href="https://docs.python.jp/3/tutorial/venv.html" target="_blank" rel="noopener">https://docs.python.jp/3/tutorial/venv.html&lt;/a>&lt;/p>
&lt;p>For virtualenv users, I will write a tutorial of virtualenv. It is a translation of the document written by aodag.&lt;br>
&lt;a href="https://gist.github.com/aodag/bea141d255e22d204a2140fba658ebf2" target="_blank" rel="noopener">https://gist.github.com/aodag/bea141d255e22d204a2140fba658ebf2&lt;/a>&lt;/p>
&lt;h4 id="why-should-we-use-virtualenvvenv">Why should we use virtualenv/venv?&lt;/h4>
&lt;p>virtualenv avoids:&lt;br>
- Conflicting Python packages with system Python&lt;br>
- Conflicting packages between projects&lt;br>
- Losing sight of which project depends on those packages&lt;/p>
&lt;h4 id="install-virtualenv">Install virtualenv&lt;/h4>
&lt;p>First, you can install &lt;code>virtualenv&lt;/code> under user home directory.&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">$ wget https://bootstrap.pypa.io/get-pip.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">export&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>”~/.local/bin/:&lt;span class="nv">$PATH&lt;/span>”
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ python get-pip.py --user
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ pip install virtualenv --user
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">\#&lt;/span> Windows user can isntall just via &lt;span class="se">\`&lt;/span>pip install&lt;span class="se">\`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">\&amp;gt;&lt;/span> pip install virtualenv
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With &lt;code>--user&lt;/code> option, you can install packages under user directory.&lt;/p>
&lt;p>&lt;code>virtualenv&lt;/code> can create a Python virtual environment. Creating the environment under the project root is common.&lt;/p>
&lt;p>Run &lt;code>virtualenv&lt;/code> as follows:&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">$ virtualenv venv -p python3.6
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>then, you can get virtual environment.&lt;/p>
&lt;p>Since Python packages will be installed under the &lt;code>venv&lt;/code> directory, don’t forget to add venv directory into &lt;code>.gitignore&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">$ &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> $
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">\#&lt;/span> For Windows
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">\&amp;gt;&lt;/span> . venv/Script/activate
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="install-python-packages-viapip">Install Python packages via pip&lt;/h4>
&lt;p>You can install packages via &lt;code>pip&lt;/code>. After activating virtualenv/venv, pip will install packages under &lt;code>venv&lt;/code> directory.&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">&lt;span class="o">(&lt;/span>venv&lt;span class="o">)&lt;/span> $ pip install pyramid
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you want to install the specific version of the package, you can set version number:&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">&lt;span class="o">(&lt;/span>venv&lt;span class="o">)&lt;/span> $ pip install &lt;span class="nv">pyramid&lt;/span>&lt;span class="o">==&lt;/span>1.8.1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Without version number, &lt;code>pip&lt;/code> will install latest stable version.&lt;br>
&lt;a href="https://www.python.org/dev/peps/pep-0440/" target="_blank" rel="noopener">https://www.python.org/dev/peps/pep-0440/&lt;/a>&lt;/p>
&lt;p>You can list installed packages with &lt;code>pip list&lt;/code> command.&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">&lt;span class="o">(&lt;/span>venv&lt;span class="o">)&lt;/span> $ pip list
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">numpy &lt;span class="o">(&lt;/span>1.13.1&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pandas &lt;span class="o">(&lt;/span>0.20.3&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip &lt;span class="o">(&lt;/span>9.0.1&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pkginfo &lt;span class="o">(&lt;/span>1.4.1&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pytest &lt;span class="o">(&lt;/span>3.2.0&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">python-dateutil &lt;span class="o">(&lt;/span>2.6.1&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pytz &lt;span class="o">(&lt;/span>2017.2&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">wheel &lt;span class="o">(&lt;/span>0.29.0&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="managing-packageversion">Managing package version&lt;/h4>
&lt;p>From pip 7.1, we can fix version of packages with &lt;code>constraints.txt&lt;/code>. Using &lt;code>pip freeze&lt;/code> command, you can list packages with a version number.&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">&lt;span class="o">(&lt;/span>venv&lt;span class="o">)&lt;/span>$ pip freeze -l
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">numpy&lt;/span>&lt;span class="o">==&lt;/span>1.13.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">pandas&lt;/span>&lt;span class="o">==&lt;/span>0.20.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">pkginfo&lt;/span>&lt;span class="o">==&lt;/span>1.4.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">pytest&lt;/span>&lt;span class="o">==&lt;/span>3.2.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">python-dateutil&lt;span class="o">==&lt;/span>2.6.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">pytz&lt;/span>&lt;span class="o">==&lt;/span>2017.2
&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 freeze -l &amp;gt; constraints.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You should list your required packages into &lt;code>requirements.txt&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">&lt;span class="o">(&lt;/span>venv&lt;span class="o">)&lt;/span>$ cat requirements.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pandas
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">numpy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then you can install required packages as follows:&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">&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;h4 id="levelaging-wheelhouse">Levelaging wheelhouse&lt;/h4>
&lt;p>Modern Python package is distributed by wheel format, which is the binary type format. There is another format, sdist, which is the source type format and it requires compile from source if it depends on native codes. I highly recommend using wheel format, because it is faster installation than sdist without compilation and even if you have an offline environment which unable to connect PyPI you can deploy the project easily.&lt;/p>
&lt;p>Put all dependent &lt;code>.whl&lt;/code> format package files under &lt;code>wheelhouse&lt;/code> directory, you can install as follows:&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">$ pip install -r requirements.txt -c constraints.txt -f wheelhouse — no-index
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>-w&lt;/code> or &lt;code>--wheel-dir&lt;/code> option allows you to set wheel directory. &lt;code>-f&lt;/code> or&lt;code>--find-links&lt;/code> option uses wheelhouse directory primary.&lt;code>--no-index&lt;/code> option prevent to connect PyPI.&lt;/p>
&lt;p>If you want to export all the dependencies into &lt;code>wheelhouse&lt;/code> directory, you can use &lt;code>pip wheel&lt;/code> command.&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">$ pip wheel -r requirements.txt -c constraints.txt -w wheelhouse
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="should-i-useconda">Should I use conda?&lt;/h3>
&lt;p>Anaconda is a Python distribution for scientific computing such as machine learning. Anaconda suit consists of Anaconda, which includes the recommended package and Miniconda, which is the minimum environment for conda and you can install only necessary packages yourself. Anaconda sometimes includes heavy packages. It used to include Django, so check the default package and use it properly.&lt;/p>
&lt;p>Unlike virtualenv, Anaconda can create its original virtual environment. Characteristically, using the &lt;code>--copy&lt;/code> option makes it possible to copy system level libraries, .so, etc. without creating symbolic links. If you archive a set of virtual environments with zip or tar, you can use it on other machines.&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 create -n myenv --copy &lt;span class="nv">python&lt;/span>&lt;span class="o">=&lt;/span>3.6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ conda activate myenv
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In other words, libraries, which are managed by OS level package management tools such as &lt;code>apt&lt;/code>, are also managed by conda. Conda has its own package repository different from PyPI and upload binaries for each OS on it. Since the same package, such as OpenCV, is registered in the repository by multiple users, you should care which package is the best one.&lt;/p>
&lt;p>In many machine learning books, it is often written that conda can be used, but I think that it is better not to use it much outside Windows.&lt;/p>
&lt;p>The reasons are as follows:&lt;/p>
&lt;ul>
&lt;li>In 2017, wheel is de facto for the binary package format, so conda’s original purpose, handling scientific packages like numpy, or Scipy, can be done without conda.&lt;/li>
&lt;li>conda will replace commands such as openssl/curl/python in macOS / Linux System (strictly speaking, conda will pass PATH first) \[[issue](https://github.com/ContinuumIO/anaconda-issues/issues/1119)\]&lt;/li>
&lt;li>Package developers are often not conda users, and they seem to be asked for support in an environment that they do not normally use, such as JRuby or Rubyinius (or Windows specific trouble).&lt;/li>
&lt;li>In the conda world, it is difficult to pass information that should be included in a build of a native extension (such as Cython dependence)&lt;/li>
&lt;/ul>
&lt;p>So I recommend using conda for Windows users or people do not develop heavily but want to experience machine learning. Or, put Miniconda under pyenv control. I use conda under Docker environment.&lt;/p>
&lt;p>However, we can not install the package like Scipy on Windows via &lt;code>pip install&lt;/code>, you need to download wheel on your own. I think that this point is better for honest conda.&lt;/p>
&lt;p>Historical details are detailed in &lt;a href="https://translate.google.com/translate?sl=ja&amp;amp;tl=en&amp;amp;js=y&amp;amp;prev=_t&amp;amp;hl=ja&amp;amp;ie=UTF-8&amp;amp;u=http:%2F%2Fymotongpoo.hatenablog.com%2Fentry%2F2017%2F02%2F02%2F182647&amp;amp;edit-text=" target="_blank" rel="noopener">this article&lt;/a>. In short, because old binary format egg was not good, conda was created.&lt;/p>
&lt;h3 id="conclusion">Conclusion&lt;/h3>
&lt;p>I introduced installation of Python and how to manage Python packages. I think we can manage Python packages via virtualenv/venv well without conda, but there is good case for conda to pack some environment with system libraries.&lt;/p>
&lt;h3 id="references">References&lt;/h3>
&lt;p>Original Japanese document:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://chezo.uno/post/2017-08-26_python-dc8d8f2fe989/" target="_blank" rel="noopener">Pythonの環境構築を自分なりに整理してみる&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://pip.pypa.io/en/stable/" target="_blank" rel="noopener">pip - pip 9.0.1 documentation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://chezo.uno/blog/2017-07-24_simple-way-to-distribute-your-private-python-packages-within-your-organization-fb7af5dbd4c9/" target="_blank" rel="noopener">Simple way to distribute your private Python packages within your organization&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Pythonの環境構築を自分なりに整理してみる</title><link>https://b94feae1.chezo-uno.pages.dev/post/2017-08-26_python------------------dc8d8f2fe989/</link><pubDate>Sat, 26 Aug 2017 13:58:07 +0900</pubDate><guid>https://b94feae1.chezo-uno.pages.dev/post/2017-08-26_python------------------dc8d8f2fe989/</guid><description>&lt;p>先日、 kawasaki.rb の懇親会で「AWS LambdaのためにPythonはじめたいんです」とか「機械学習系の本を読み始めたので、そこに書いてあったcondaではじめました」とかいう話を聞きました。&lt;/p>
&lt;p>コレ読んでおけ系の記事は既に色々とあるんですが、今回は自分で触ってきて使っている環境を中心に説明をしようと思います。想定読者としては、Rubyを使っていたけど最近Pythonにも手を出すようになってきたという人です。&lt;/p>
&lt;p>書き始めてから思ったんですが、ぶっちゃけPythonコミッターのmethaneさんの以下の記事を読めば十分な気もしてきましたが、一例としてお読みいただければと思います。&lt;/p>
&lt;div class="iframely-embed">&lt;div class="iframely-responsive" style="height: 140px; padding-bottom: 0;">&lt;a href="https://qiita.com/methane/items/5afdabd513a18049c34f" data-iframely-url="//cdn.iframe.ly/7whUOpn?card=small">&lt;/a>&lt;/div>&lt;/div>&lt;script async src="//cdn.iframe.ly/embed.js" charset="utf-8">&lt;/script>
&lt;h3 id="私の立ち位置">私の立ち位置&lt;/h3>
&lt;p>もともとは、数年前まで 年に一回Python3でNLTKやらscikit-learnやら機械学習や自然言語処理の環境を作ろうと試みては失敗してPython使うものかと思っていた人でした。それが、Anacondaで環境導入すると、面倒なことは何も考えずに済むということを知り、CookpadではAnacondaを前提に環境構築を勧めていました。&lt;/p>
&lt;p>今は、ymotongpooに「郷に入っては郷に従え」と言われたため、pip+virtualenv/venvで全てをこなしていますが、これで困ったことは特にありません。macOS, Windows, Linuxそれぞれでやっていますが、大きな苦労もなく大丈夫だなという印象です。&lt;/p>
&lt;p>先に言っておくと自分のPython力はそこまで高くないです。もともとはRailsで開発はしていましたが、Pythonはほぼ機械学習やデータマイニング系のコードを一人で書くということが多いです。いくつかPythonのライブラリは書いて入るのですが、チーム開発の経験はないためその観点は弱いということを認識いただければと思います。&lt;/p>
&lt;p>なお、仕事では
ので、適材適所だなと思ってこの話を書いています。単純にpyenvやcondaを使うな、というつもりはありません。リスクとベネフィットのトレードオフで選んでください。&lt;/p>
&lt;h3 id="環境構築のあれやこれや">環境構築のあれやこれや&lt;/h3>
&lt;p>以下では、macOS, Windows, Linuxで自分がどのように環境構築をしているかについて書いていこうと思います。&lt;/p>
&lt;h4 id="最低限の前提知識">最低限の前提知識&lt;/h4>
&lt;p>Pythonの実行環境の導入・パッケージ管理周りに出て来るキーワードについて簡単に説明します。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>pip&lt;/strong>: パッケージインストールのためのツール。Rubyでいうところのgem。パッケージはPyPIというRubyで言うところのrubygems.orgから取ってくる。&lt;/li>
&lt;li>&lt;strong>virtualenv&lt;/strong>: パッケージを切り分ける仮想環境を作る人。Rubyで言うところのbundlerに近いが、Pythonの利用するバージョンも指定するのでrbenvのruby-build抜きの機能を持っている感じ&lt;/li>
&lt;li>&lt;strong>venv&lt;/strong>: Python 3から公式として入ったvirtualenv。ただし、venvはPythonのバージョン指定ができないとか、Debian/Ubuntuでは余計な依存関係が発生するとかで私は使ってません。&lt;/li>
&lt;/ul>
\[2017/08/30 追記\]&lt;p>&lt;br>
venvはバージョンの指定が出来ないのではなく、2.7と3.6との切り替えができないという意味でした。 &lt;code>python3.5 -m venv some-great-env&lt;/code>で切り分けられます。&lt;br>
私自身パッケージ開発に2.7サポートをする場合があるのと、Ubuntu使いなのでvirtualenvを使っています。&lt;br>
&lt;/p>
\[/追記\]&lt;p>以上が、多くのPythonistaに使われている基本的なツールセットです。このあたりの話は、PyPAと呼ばれるパッケージ管理関係の標準と参照実装を決める目的で設立された団体によって
として扱われています。（後述のcondaもでてきますが）&lt;/p>
&lt;ul>
&lt;li>&lt;strong>conda&lt;/strong>: Continuumという会社が作っているPythonを中心とした科学計算のパッケージ管理をするためのプラットフォーム&lt;/li>
&lt;li>&lt;strong>pyenv&lt;/strong>: rbenvのPython版。Pythonのソースからのビルドと、Pythonの実行環境（バージョンなど）の切り替えをする&lt;/li>
&lt;/ul>
&lt;p>これらは、特定の目的のために使うことが多いものです。&lt;/p>
&lt;h4 id="実行環境の導入">実行環境の導入&lt;/h4>
&lt;p>2017年においては、Python 3系の最新版を入れておけばだいたい生きていけると思います。が、何かしらの辛い事情で2系も使わないといけない場合もあるかと思います。&lt;/p>
&lt;p>その場合は、自分はOSのパッケージ管理ツールでPythonを入れています。例えばUbuntuで &lt;code>apt install python-dev&lt;/code> とするとPython 2.7が &lt;code>apt install python3-dev&lt;/code> をすると今ならPython 3.6が入ります。同時に入れると&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">/usr/bin/python #&amp;lt;- 2.7
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> python2 #&amp;lt;- 2.7
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> python2.7 #&amp;lt;- 2.7
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> python3 #&amp;lt;- 3.6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> python3.6 #&amp;lt;- 3.6
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>という感じで入ります。 &lt;code>brew install&lt;/code>でも &lt;code>python&lt;/code> と &lt;code>python3&lt;/code> で2系と3系両方入れることができます。&lt;/p>
&lt;p>バージョンの切り替えは、後述のvirtualenvで実行するバージョンを切り替えます。逆に言うとpyenvでのバージョン切り替えは、ライブラリ開発者のように細かいバージョン切り替えが必要な人でなければ不要でしょう。&lt;/p>
&lt;p>なお、homebrewは彼らの方針で過去バージョンのPythonはpyenvを使ってくれ、と言っています。MacPortsは普通に過去バージョンが入りますし、Ubuntuは
を使えばこの問題は解決される話です。homebrewで切り替えたい人はpyenvを使うほうが楽でしょう。&lt;/p>
&lt;p>WindowsだとChocolateyで入れるか、オフィシャルのインストーラで入れれば良いです。（ Python 3.6からは &lt;code>py&lt;/code> というコマンドが用意されており、それを使うとバージョンを切り替えた実行もできます）&lt;/p>
&lt;p>なお、絶対にやってはいけないのはSystem Pythonを使い続けることです。System Pythonは往々にして古いことが多く、またyumなどシステム上重要なシステムに依存しています。うっかり、 &lt;code>sudo pip install&lt;/code> などしようものなら、環境を壊してしまう恐れがあります。&lt;/p>
&lt;p>なお、環境構築に関する話は、以下の記事が参考になるでしょう。&lt;/p>
&lt;p>
&lt;/p>
&lt;h4 id="パッケージ管理">パッケージ管理&lt;/h4>
&lt;p>まず、大前提として先程も書きましたが &lt;code>sudo pip install hogehoge&lt;/code> をするのは辞めましょう。システムPythonはOSで使っているため、これの環境を壊すことは危険です。&lt;/p>
&lt;p>プロジェクトごとにパッケージのバージョンを依存関係を切り替えるのには &lt;code>virtualenv&lt;/code>を使います。以下の aodagさんの資料を読んで下さると良いと思います。&lt;/p>
&lt;p>
&lt;/p>
&lt;p>また、Python 3に関しては以下の &lt;code>venv&lt;/code>のチュートリアルがわかりやすくまとまっています。Debian/Ubuntu以外の人はPython 3だけで良ければ &lt;code>venv&lt;/code> で十分かと思います。&lt;/p>
&lt;p>
&lt;/p>
&lt;p>ここに書いてあるけど大事な話としては、 &lt;code>virtualenv&lt;/code>でPythonの実行環境を切り替えられる話をしておきます。&lt;/p>
&lt;p>まずは、以下のようにしてユーザ領域に &lt;code>virtualenv&lt;/code>を入れます。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="o">$&lt;/span> &lt;span class="n">wget&lt;/span> &lt;span class="n">https&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="o">//&lt;/span>&lt;span class="n">bootstrap&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pypa&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">io&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">pip&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">$&lt;/span> &lt;span class="k">export&lt;/span> &lt;span class="n">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;~/.local/bin/:$PATH&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">$&lt;/span> &lt;span class="n">python&lt;/span> &lt;span class="n">get&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">pip&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">py&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">user&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">$&lt;/span> &lt;span class="n">pip&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="n">virtualenv&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">user&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-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># Windowsの場合は特に考えずにpip installで良い
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; pip install virtualenv
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>virtualenvでは、プロジェクトの下に仮想環境を作ります。プロジェクトルートで以下のコマンドを実行すると&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">$ virtualenv venv -p python3.6
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Python 3.6を実行環境とした、仮想環境ができます。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">$ echo venv &amp;gt;&amp;gt; .gitignore
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>で、 &lt;code>.gitignore&lt;/code> に追加しておくとよいでしょう。&lt;/p>
&lt;p>次に、 &lt;code>activate&lt;/code> して仮想環境を有効化します。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">$ source venv/bin/activate(venv) $
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">#Windows
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; . venv/Script/activate
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>この状態で、 &lt;code>pip install scikit-learn&lt;/code> とすると &lt;code>venv&lt;/code> 以下にパッケージが入ります。仮想環境の利用を止めたい場合は &lt;code>deactivate&lt;/code> コマンドを使いましょう。&lt;/p>
&lt;p>pip 7.1以降ではconstraints.txtというGemfile.lockのようなものが扱えるようになりました。 &lt;code>pip freeze&lt;/code> で現在インストールしているライブラリとバージョン一覧がでます。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="n">venv&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">$&lt;/span> &lt;span class="n">pip&lt;/span> &lt;span class="n">freeze&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">l&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pylint&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">1.7&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pyparsing&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">2.2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pytest&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">3.2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">requests&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">2.18&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">six&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">1.10&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">tox&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">2.7&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">urllib3&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">1.22&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">virtualenv&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">15.1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">0&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="n">venv&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">$&lt;/span> &lt;span class="n">pip&lt;/span> &lt;span class="n">freeze&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">l&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">constraints&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">txt&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="n">venv&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">$&lt;/span> &lt;span class="n">cat&lt;/span> &lt;span class="n">constraints&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">txt&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pylint&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">1.7&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pyparsing&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">2.2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pytest&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">3.2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">requests&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">2.18&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">six&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">1.10&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">tox&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">2.7&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">urllib3&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">1.22&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">virtualenv&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="mf">15.1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ここに、プロジェクトに必要な必要なライブラリをrequirements.txtに記述します。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">(venv)$ cat requirements.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">requests
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pyparsing
&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-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="n">venv&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">$&lt;/span> &lt;span class="n">pip&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">r&lt;/span> &lt;span class="n">requirements&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">txt&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">c&lt;/span> &lt;span class="n">constraints&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">txt&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="condaの話">condaの話&lt;/h4>
&lt;p>Anacondaは機械学習などの科学計算向けのPythonディストリビューションです。Anacondaは、推奨パッケージを含めて入れるAnacondaと、自分で必要なパッケージを入れるMinicondaが入ります。Anacondaは（今はないですが）昔はDjangoを入れているなど重いパッケージも入ってくることがあるので、きちんとデフォルトパッケージを調べてから使いましょう。&lt;/p>
&lt;p>なお、Pythonディストリビューションと書いてありますが、Rなど他の言語の環境を作ることもできます。&lt;/p>
&lt;p>Anacondaはvirtualenvと違い、独自の仮想環境を作ることができます。特徴的なのは、 &lt;code>--copy&lt;/code> というオプションを付けるとライブラリや.soなどをシンボリックリンクを作らず、コピーするということができるため、仮想環境一式をzipやtarで固めると、そのまま実行可能な形で使いまわせることです。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">$ conda create -n myenv --copy python**\=**3.6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ conda activate myenv
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>つまり、通常 &lt;code>apt&lt;/code> などのOSのパッケージ管理で入れるようなところも、condaのパッケージで管理をします。condaはPyPIとは異なる独自のパッケージリポジトリを持ち、そちらにOSごとにバイナリをアップロードします。ですので、OpenCVなど同じ名前の同じものを入れるためのパッケージが複数のユーザによってレポジトリに登録されていたりと結構雑多な印象があります。&lt;/p>
&lt;p>多くの機械学習の本では、condaを使えと書いて有ることが多いのですが、Windows以外ではあまり使わないほうが良いと私は考えています。&lt;/p>
&lt;p>理由としては、以下が上げられます。&lt;/p>
&lt;ul>
&lt;li>2017年においては、 wheel というバイナリパッケージのフォーマットがデファクトになっているので、condaの当初の目的の科学計算用のパッケージ（gfortanとかに依存していて辛い）を簡単に入れるという目的はcondaでなくてもできる。&lt;/li>
&lt;li>普通に入れると、macOS/Linuxの
(厳密には、 condaの方が先にPATHが通ってしまう) \[[issue](https://github.com/ContinuumIO/anaconda-issues/issues/1119)\]&lt;/li>
&lt;li>パッケージ開発者は、condaユーザーではないことが多く、彼らからするとJRubyやRubyinius（あるいはWindows固有の不具合）など通常使わない環境でのサポートを求められるように感じる。&lt;/li>
&lt;li>condaの世界で閉じていないNative extensionのビルド（Cython依存など）でincludeしないといけない情報などがうまく渡せず、はまると辛い&lt;/li>
&lt;li>Distributionごとの違いが発生する場合もあるのに、OSレベルの違いしか考慮できない&lt;/li>
&lt;/ul>
&lt;p>ですので、Windowsの人や、開発は通常しないけど機械学習を体験してみたいという人はcondaを使うと良いでしょう。あるいは、pyenvの管理下でMinicondaを入れるか、私自身はDockerの中でcondaを使っています。&lt;/p>
&lt;p>ただ、ScipyなどWindows用の一部は &lt;code>pip install&lt;/code>では入らないので
wheelをダウンロードする必要があります。この点は正直condaの方が良いかと思います。&lt;/p>
&lt;p>歴史的な詳細は下記が詳しいです。端的に言うと、wheel以前の古いバイナリフォーマットがいまいちだったため、condaを作ったという形になります。&lt;/p>
&lt;p>
&lt;/p>
&lt;h4 id="カルチャーの違い">カルチャーの違い&lt;/h4>
&lt;p>RubyとPythonでは色々と文化が違うのですが、一番大きな違いは個人的には以下の2点だと思っています。&lt;/p>
&lt;ul>
&lt;li>できるだけ依存するものは最低限の薄いものが良い。ブラックボックスは好まないという思想&lt;/li>
&lt;li>Rubyはいいものを皆が試して事実上のデファクトスタンダードになるのに対して、提案されたPEPという「標準」を合議して決めていくというスタイル&lt;/li>
&lt;/ul>
&lt;p>なので、Rubyはパッとデファクトが決まる印象がありますが、Pythonはじっくり議論の上で標準を決めていくという印象があります。（コレもあって、過渡期の実装が色々出てくる）&lt;/p>
&lt;p>また、こうした経緯からかPythonの人たちはきっちり例外まで補足したがる傾向にあると思います（例えるなら、Pythonは数学科の数式にきっちり向き合う感じと、Rubyは工学部の数式に対する雑な扱いのような感じ）。なので、昔からのPythonistaが書くドキュメントは長くなる傾向にあると思いますが、そういうものだと思って割り切るしかないと思います。&lt;/p>
&lt;h3 id="まとめ">まとめ&lt;/h3>
&lt;p>個人的には、普通に使う分にはvirtualenv/venvとOSのパッケージ管理ツールでPythonを入れるというので、私は良いと思って使っています。&lt;/p>
&lt;p>ただ、プロダクション要求などで必要なものもあると思うので、condaとpyenvは適材適所で用法用量を守って使いましょう。&lt;/p>
&lt;h3 id="参考文献">参考文献&lt;/h3>
&lt;p>
&lt;/p>
&lt;p>
&lt;/p>
&lt;p>
&lt;/p>
&lt;p>
&lt;/p>
&lt;p>
&lt;/p></description></item><item><title>Simple way to distribute your private Python packages within your organization</title><link>https://b94feae1.chezo-uno.pages.dev/blog/2017-07-24_simple-way-to-distribute-your-private-python-packages-within-your-organization-fb7af5dbd4c9/</link><pubDate>Mon, 24 Jul 2017 01:21:40 +0900</pubDate><guid>https://b94feae1.chezo-uno.pages.dev/blog/2017-07-24_simple-way-to-distribute-your-private-python-packages-within-your-organization-fb7af5dbd4c9/</guid><description>&lt;p>&lt;figure>&lt;img src="https://b94feae1.chezo-uno.pages.dev/blog/2017-07-24_simple-way-to-distribute-your-private-python-packages-within-your-organization-fb7af5dbd4c9/0_YSlLMz01REAp_q_y.png">&lt;figcaption>
&lt;h4>[https://www.irasutoya.com/2017/05/blog-post\_22.html](https://www.irasutoya.com/2017/05/blog-post_22.html)&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;a href="http://www.irasutoya.com/2017/05/blog-post_22.html" target="_blank" rel="noopener">https://www.irasutoya.com/2017/05/blog-post_22.html&lt;/a>&lt;/p>
&lt;p>&lt;em>This article is a translation of&lt;/em> &lt;a href="https://gist.github.com/aodag/9a8118d06998b674e2d9597c6d02a6db" target="_blank" rel="noopener">&lt;em>this article&lt;/em>&lt;/a>&lt;em>, originally written by&lt;/em> &lt;a href="https://github.com/aodag" target="_blank" rel="noopener">&lt;em>aodag&lt;/em>&lt;/a> &lt;em>in Japanese. I translated it with his permission. This article is aimed to know simple ways to prepare internal Python package host like a&lt;/em> &lt;a href="http://guides.rubygems.org/run-your-own-gem-server/" target="_blank" rel="noopener">&lt;em>local gem server on Ruby&lt;/em>&lt;/a>&lt;em>.&lt;/em>&lt;/p>
&lt;h3 id="methods">Methods&lt;/h3>
&lt;ul>
&lt;li>Include your packages in your git repository&lt;/li>
&lt;li>Publish a directory including your packages via HTTP server&lt;/li>
&lt;li>Build a local PyPI-equivalent server&lt;/li>
&lt;/ul>
&lt;p>It is a high-cost way to create a local PyPI-equivalent server &lt;em>(translator note: like&lt;/em> &lt;a href="http://doc.devpi.net/latest/" target="_blank" rel="noopener">&lt;em>devpi&lt;/em>&lt;/a>&lt;em>)&lt;/em>, and I don’t think there is no need to do so, I will describe first two options.&lt;/p>
&lt;h4 id="include-your-packages-in-your-git-repository">Include your packages in your Git repository&lt;/h4>
&lt;p>If your packages are required for a particular project, it is straightforward to contain them in the Git repository. You can put them in the directory named &lt;code>wheelhouse&lt;/code>, which comes from the name of the previous default directory created by &lt;code>pip wheel&lt;/code>. (&lt;em>translator note: this method is assumed you to know wheel. If not,&lt;/em> &lt;a href="http://wheel.readthedocs.io/en/latest/story.html" target="_blank" rel="noopener">&lt;em>this story&lt;/em>&lt;/a> &lt;em>and&lt;/em> &lt;a href="https://issues.apache.org/jira/browse/SPARK-16367" target="_blank" rel="noopener">&lt;em>this JIRA&lt;/em>&lt;/a> &lt;em>would be helpful.&lt;/em>)If you put the private package &lt;code>foo&lt;/code> in the &lt;code>wheelhouse&lt;/code>, you can install as follows:&lt;/p>
&lt;p>$ pip install foo -f wheelhouse&lt;/p>
&lt;p>Note that &lt;code>-f&lt;/code> is the short option for &lt;code>--find-links&lt;/code>, with that option, pip will search packages in the directory first, then fall back to &lt;code>pypi&lt;/code>.&lt;/p>
&lt;h4 id="publish-a-directory-including-your-packages-via-httpserver">Publish a directory including your packages via HTTP server&lt;/h4>
&lt;p>We can use&lt;code>--find-link&lt;/code> option to search not only local directory but also a remote server via &lt;code>http&lt;/code>. If you have a package used by multiple projects, this method will help you.&lt;/p>
&lt;p>The easiest way to distribute your packages with this method is executing &lt;code>python -m http.server&lt;/code> with Python 3.x (or &lt;code>python -m SimpleHTTPServer&lt;/code> with Python 2.7) on the &lt;code>wheelhouse&lt;/code> directory. This simple server provides directory listings so that we can just use&lt;code>--find-links&lt;/code> to use the directory. Make sure to open &lt;code>http://localhost:8000&lt;/code> that you can see the list of files under the &lt;code>wheelhouse&lt;/code> directory via a web browser.&lt;/p>
&lt;p>To install &lt;code>foo&lt;/code> package via HTTP server you launched, you can execute as follows:&lt;/p>
&lt;p>$ pip install foo -f http://localhost:8000&lt;/p>
&lt;p>Since this is a simple server, for production, it is good to put them in cloud storage such as AWS S3, you should check the way for directory listings, or you can use Apache with &lt;code>DirectoryIndex&lt;/code> enabled.&lt;/p>
&lt;h3 id="conclusion">Conclusion&lt;/h3>
&lt;p>I recommend these methods because they are simple and no need to prepare the dedicated application server.&lt;/p></description></item></channel></rss>