<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Packaging |</title><link>https://kristianeschenburg.netlify.app/tag/packaging/</link><atom:link href="https://kristianeschenburg.netlify.app/tag/packaging/index.xml" rel="self" type="application/rss+xml"/><description>Packaging</description><generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><lastBuildDate>Tue, 02 May 2023 02:14:14 -0700</lastBuildDate><image><url>https://kristianeschenburg.netlify.app/img/Bayes.jpg</url><title>Packaging</title><link>https://kristianeschenburg.netlify.app/tag/packaging/</link></image><item><title>CI/CD Part 1: Gitlab Pipelines and Package Registries</title><link>https://kristianeschenburg.netlify.app/post/cicd-1-pipelines-and-packages/</link><pubDate>Tue, 02 May 2023 02:14:14 -0700</pubDate><guid>https://kristianeschenburg.netlify.app/post/cicd-1-pipelines-and-packages/</guid><description>&lt;p>I recently developed a template workflow to help our team adopt a CI/CD-based development strategy. Many of our web applications and tools were based on simple repository structures. With growing datasets and ever-increasing use by outside teams, we found ourselves needing to add new features more frequently to many of these tools and believed that continuous integration and deployment could help us not just develop more quickly, but also more intelligently. Since we use Gitlab to store our code, we decided to use the Gitlab CI/CD tools.&lt;/p>
&lt;p>Documentation on much of this process was scattered and/or sparse, so I decided to put what I learned and implemented into a more coherent set of notes. This post covers the anatomy of a &lt;code>.gitlab-ci.yml&lt;/code> file, how jobs and stages fit together, how to make a job conditional, and how to authenticate against a package registry so a pipeline can build a Python package and push it there.&lt;/p>
&lt;p>The
&lt;a href="https://kristianeschenburg.netlify.app/post/cicd-2-images-and-registries/">second post&lt;/a> covers the other half: writing a multi-stage Dockerfile to build an image from that package, and pushing the image to a container registry.&lt;/p>
&lt;h2 id="setup">Setup&lt;/h2>
&lt;p>I did all of my testing using my personal Gitlab account. To separate things out, I created a new Project called &amp;ldquo;Package Registry”, as well as a test repository that was used for building a local Python project called &amp;ldquo;TemplateCI&amp;rdquo;.&lt;/p>
&lt;p>The &amp;ldquo;Package Registry&amp;rdquo; Project serves as just that &amp;ndash; an all-inclusive location for any software packages your CI/CD pipelines build. You can find the built packages by clicking &lt;strong>${Project Name} &amp;gt; Deploy &amp;gt; Package Registry&lt;/strong>. Every &amp;ldquo;Project&amp;rdquo; in Gitlab has the ability to store packages in its own registry, but I felt it cleaner to store everything in one repo. Similarly, the actual code that I&amp;rsquo;ll be packaging will be stored in the &amp;ldquo;TemplateCI&amp;rdquo; repo.&lt;/p>
&lt;h2 id="authentication-pypirc-and-netrc">Authentication: .pypirc and .netrc&lt;/h2>
&lt;p>In order to build packages and push them to a remote package registry, we use the &lt;code>build&lt;/code> and &lt;code>twine&lt;/code> packages. &lt;code>build&lt;/code> generates a package, and &lt;code>twine&lt;/code> pushes this package to a registry (or &amp;ldquo;index&amp;rdquo;). &lt;code>twine&lt;/code> requires access to authentication usernames, passwords, and a registry URL in order to do so. &lt;code>twine&lt;/code> can access these tokens from a &lt;code>.pypirc&lt;/code> file &amp;ndash; the tokens are generated by the registry, and ensure that the submitting user has permissions to perform a certain action.&lt;/p>
&lt;p>Other processes, such as pulling or pushing code from a remote repository, often require additional usernames and passwords. In order to alleviate the need to consistently provide these variables at request time, we can save them in a &lt;code>.netrc&lt;/code> file.&lt;/p>
&lt;p>These are straightforward to set up locally. But we also need to set these up to ensure a properly functional CI/CD workflow. I&amp;rsquo;ve put together a basic script, called &lt;code>setup_tokens.sh&lt;/code> that does just that:&lt;/p>
&lt;h3 id="setup_tokenssh">setup_tokens.sh&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># generate .pypirc file&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;[distutils]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">index-servers =
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> personal
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">[personal]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">repository = https://gitlab.com/api/v4/projects/&lt;/span>&lt;span class="nv">$PACKAGE_REGISTRY_ID&lt;/span>&lt;span class="s2">/packages/pypi
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">username = &lt;/span>&lt;span class="nv">$CI_DEPLOY_USER&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">password = &lt;/span>&lt;span class="nv">$CI_DEPLOY_PASSWORD&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &amp;gt; ~/.pypirc
&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="c1"># generate .netrc file&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;machine gitlab.com
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">login gitlab-ci-token
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">password &lt;/span>&lt;span class="nv">$CI_JOB_TOKEN&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &amp;gt; ~/.netrc&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>.pypirc&lt;/code> refers to your Project Registry via a previously generated authentication token and password, and allows your to build and upload Python packages to that registry.&lt;/p>
&lt;p>The &lt;code>.netrc&lt;/code> file enables you to pull private packages from that same registry. In the context of our work, we&amp;rsquo;ll want to build and push packages to the registry first so that they are available for pulling. For example, in the &lt;code>Pipfile&lt;/code> for this template project, we have the following:&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">source&lt;/span>&lt;span class="p">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">url&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://pypi.org/simple&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">verify_ssl&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 class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;pypi&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">source&lt;/span>&lt;span class="p">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">url&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://${CI_DEPLOY_USER}:${CI_DEPLOY_PASSWORD}@gitlab.com/api/v4/projects/${$PACKAGE_REGISTRY_ID}/packages/pypi/simple&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">verify_ssl&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 class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;personal&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We see the same user authentication happening, along with the reference to the Package Registry ID variable. For local installation of your package, and in order to make sure that your &lt;code>Pipfile&lt;/code> and &lt;code>Pipfile.lock&lt;/code> are in sync, you&amp;rsquo;ll need to define the following local environment variables:&lt;/p>
&lt;ul>
&lt;li>&lt;code>CI_DEPLOY_USER&lt;/code>: generated user token&lt;/li>
&lt;li>&lt;code>CI_DEPLOY_PASSWORD&lt;/code>: generated token password&lt;/li>
&lt;li>&lt;code>PACKAGE_REGISTRY_ID&lt;/code>: the ID of the repository that you created that will store your packages&lt;/li>
&lt;/ul>
&lt;h2 id="basic-jobs">Basic jobs&lt;/h2>
&lt;p>The basis of a Gitlab CI/CD pipeline is the &lt;code>.gitlab-ci.yml&lt;/code> file, which is composed of a set of explicitly-defined and temporally-ordered “stages”. A stage is composed of a set of “jobs”. Jobs are the workhorses of the CI/CD pipeline, and define explicit tasks that a CI/CD pipeline runs. By default, all jobs from one stage run in parallel, unless specified otherwise (using the &lt;code>needs&lt;/code> keyword as an attribute of a job induces a temporal directed acyclic graph – jobs can be made “dependent” on the successful completion of other jobs within a stage). In this example, I’ve defined three stages:&lt;/p>
&lt;ol>
&lt;li>run-unit-tests&lt;/li>
&lt;li>build-package&lt;/li>
&lt;li>build-image&lt;/li>
&lt;/ol>
&lt;p>Any job associated with the &lt;code>run-unit-tests&lt;/code> stage will run to completion (or failure) PRIOR TO ANY job in the stages &lt;code>build-package&lt;/code> and &lt;code>build-image&lt;/code> starting. If all jobs in the &lt;code>run-unit-tests&lt;/code> stage complete successfully, then the next stage (&lt;code>build-package&lt;/code>) will begin. We define individual jobs, and give them a stage attribute. Stages define the rough ordering of jobs. Each job runs in the context of an “image” or environment. We can set a global &lt;code>image&lt;/code> and define stages as&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="c"># global CI/CD image&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="l">python:3.9-slim&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="c"># stages of this example pipeline&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">stages&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="l">run-unit-tests&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="l">build-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="l">build-image&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">variables&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">LC_ALL&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">C.UTF-8&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">LANG&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">C.UTF-8&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>or define the image as an attribute of a job. Gitlab CI/CD by default uses Docker images in which to run jobs. Setting the image is analogous to using the &lt;code>FROM&lt;/code> command in a Dockerfile. We&amp;rsquo;ve also set some global variables, here the &lt;code>LC_ALL&lt;/code> and &lt;code>LANG&lt;/code> variables.&lt;/p>
&lt;p>To “run” Gitlab pipelines for the purpose of CI/CD, we use “runners”, which are build instances installed on a server. Gitlab offers “shared” runners (use of these is free if you use
&lt;a href="https://www.gitlab.com" target="_blank" rel="noopener">www.gitlab.com&lt;/a>, but you need to register a credit card to prevent abuse of Gitlab resources). You can also register your own device(s) to act as a Gitlab runner.&lt;/p>
&lt;p>Below are examples of two jobs in the &lt;code>run-unit-tests&lt;/code> stage. These two jobs are effectively the same code, apart from the unique unit tests that they run. However, we&amp;rsquo;ve made the job &lt;code>unit-tests-2&lt;/code> dependent on the output of the job &lt;code>unit-tests-1&lt;/code> (see the &lt;code>needs&lt;/code> keyword of &lt;code>unit-tests-2&lt;/code>). Both jobs use the global &lt;code>python:3.9-slim&lt;/code> image. We can run some “setup” stuff (&lt;code>before_script&lt;/code>), run an actual script (&lt;code>script&lt;/code>), and run clean up (&lt;code>after_script&lt;/code>, not shown) &amp;ndash; these delineations (before, during, after) are for organizational purposes, and not due to any explicit functional differences in the delineations.&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="c"># setup_tokens.sh is the script from the previous section.&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># example job #1&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">unit-tests-1&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">stage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">run-unit-tests&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">before_script&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="l">chmod +x ./setup_tokens.sh; ./setup_tokens.sh&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="l">python3 -m pip install pipenv&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="l">apt-get update&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="l">apt-get install --yes --no-install-recommends gcc g++ libffi-dev&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="l">python3 -m pipenv install --deploy --dev&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="c"># run first set of unit tests&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">script&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="l">python3 -m pipenv run pytest -k &amp;#39;test_examples1.py&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>&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="c"># example job #2&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">unit-tests-2&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">stage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">run-unit-tests&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">before_script&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="l">chmod +x ./setup_tokens.sh; ./setup_tokens.sh&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="l">python3 -m pip install pipenv&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="l">apt-get update&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="l">apt-get install --yes --no-install-recommends gcc g++ libffi-dev&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="l">python3 -m pipenv install --deploy --dev&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="c"># run second set of unit tests&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">script&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="l">python3 -m pipenv run pytest -k &amp;#39;test_examples2.py&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="c"># wait for job 1 to finish&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">needs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">unit-tests-1]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conditional-pipeline-jobs">Conditional pipeline jobs&lt;/h2>
&lt;p>The above jobs are relatively simple and will run every time you push a repository to Gitlab. However, sometimes, we might only want to run a job if certain conditions are met. For example, we might only want to build a package from the &lt;code>main&lt;/code> branch, or only after a merge request is made. To this end, we can add “rules” to a job that restrict when it is actually run.&lt;/p>
&lt;p>Below is a more complicated job example. The overarching goal of this job is to build a Docker image from a local Python project and push the image to the Gitlab Container Registry. There’s a lot going on here, so I’ll break it up into pieces, but here is the whole job:&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="c"># Conditional job&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="c"># Building a docker image and pushing this container to a container registry&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="c"># Link to main image: https://github.com/bentolor/docker-dind-awscli&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="c"># Conditions:&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="c"># --- merge request events&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="c"># --- target branch of merge request is &amp;#34;main&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="c"># job name&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">build-image-glcr&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="c"># stage of pipeline&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">stage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build-image&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="c"># image that job is based on&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="l">docker:20.10.16&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="c"># sub-services of job&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">services&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="l">docker:20.10.16-dind&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="c"># variables available to the job&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">variables&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">DOCKER_TLS_CERTDIR&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;/certs&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">IMAGE_TAG&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG&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="c"># some setup scripts -- here, just making sure docker is available&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">before_script&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="l">docker info&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="c"># meat of the job -- authentication, building, run, push&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">script&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="l">docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY&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="l">docker build &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="l">build-arg CI_DEPLOY_USER=$CI_DEPLOY_USER &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="l">build-arg CI_DEPLOY_PASSWORD=$CI_DEPLOY_PASSWORD &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="l">build-arg CI_JOB_TOKEN=$CI_JOB_TOKEN&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="l">t $IMAGE_TAG .&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="l">docker run $IMAGE_TAG&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="l">docker push $IMAGE_TAG&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="c"># job conditions&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">rules&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">if&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">$CI_PIPELINE_SOURCE == &amp;#39;merge_request_event&amp;#39; &amp;amp;&amp;amp; $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == &amp;#34;main&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This job is associated with a new stage called &lt;code>build-image&lt;/code> that will run as the last stage of this example CI/CD pipeline. Without going into the specifics of the
&lt;a href="https://kristianeschenburg.netlify.app/post/cicd-2-images-and-registries/">Dockerfile&lt;/a> just yet, this stage builds and pushes a Docker image to a remote repository. We defined the job image as &lt;code>docker:20.10.16&lt;/code> and an additional “service” attribute as &lt;code>docker:20.10.16-dind&lt;/code> where “dind” means “Docker-in-Docker”. The Docker-in-Docker feature allows an image to run Docker itself (pretty meta, huh). The idea here is to instantiate a job from a specific Docker container (“image”) which itself has Docker installed (“dind”), which will allow the docker:20.10.16 image to build &lt;em>another&lt;/em> Docker container. We’ve also defined some variables:&lt;/p>
&lt;ul>
&lt;li>&lt;code>DOCKER_TLS_CERTDIR&lt;/code>: needed to allow the larger scale image to communicate with a service (honestly, I don’t quite understand this and documentation on CI/CD &amp;ldquo;services&amp;rdquo; is sparse)&lt;/li>
&lt;li>&lt;code>IMAGE_TAG&lt;/code>: refers to the container destination and the image “name” &amp;ndash; this just makes our lives easier by turning into a variable what would otherwise be a really long string&lt;/li>
&lt;/ul>
&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="c">#### Build Docker image and push to Gitlab Container Registry&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">build-image-glcr&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">stage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build-image&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="l">docker:20.10.16&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">services&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="l">docker:20.10.16-dind&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">variables&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">DOCKER_TLS_CERTDIR&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;/certs&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">IMAGE_TAG&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next up, we have all the script stuff. You’ll probably recognize most of the &lt;code>docker ${command}&lt;/code> commands. We first “authenticate” the current job with the Gitlab container registry (apparently there are a variety of ways to “authenticate” with Docker using one set of variables or another – the way below is the way I was able to get working, but there are
&lt;a href="https://stackoverflow.com/questions/61251622/how-to-authenticate-to-gitlabs-container-registry-before-building-a-docker-imag" target="_blank" rel="noopener">other solutions&lt;/a>). We then build the Docker image, run the image, and push the image to the container registry.&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">script&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="l">docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY&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="l">docker build &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="l">build-arg CI_DEPLOY_USER=$CI_DEPLOY_USER &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="l">build-arg CI_DEPLOY_PASSWORD=$CI_DEPLOY_PASSWORD &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="l">build-arg CI_JOB_TOKEN=$CI_JOB_TOKEN&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="l">t $IMAGE_TAG .&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="l">docker run $IMAGE_TAG&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="l">docker push $IMAGE_TAG&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You’ll notice a bunch of variables that I didn&amp;rsquo;t explicitly define anywhere:&lt;/p>
&lt;ul>
&lt;li>&lt;code>CI_REGISTRY_USER&lt;/code>: username for project (I think we can also use &lt;code>CI_DEPLOY_USER&lt;/code>, though it looks like there might be some things to figure out with branch / variable protections / non-protections)&lt;/li>
&lt;li>&lt;code>CI_REGISTRY_PASSWORD&lt;/code>: defaults to &lt;code>CI_JOB_TOKEN&lt;/code> value (this value is ephemeral e.g. valid only for one job at a time I think? I think we can also use the &lt;code>CI_DEPLOY_PASSWORD&lt;/code> for a longer-lived alternative, though it looks like there might be some things to figure out with branch / variable protections / non-protections)&lt;/li>
&lt;li>&lt;code>CI_REGISTRY&lt;/code>: defaults to &lt;code>https://gitlab.com/${group}/${project-name}/container_registry&lt;/code>&lt;/li>
&lt;li>&lt;code>CI_DEPLOY_USER&lt;/code>: generated user token&lt;/li>
&lt;li>&lt;code>CI_DEPLOY_PASSWORD&lt;/code>: generated token password&lt;/li>
&lt;li>&lt;code>CI_JOB_TOKEN&lt;/code>: see documentation
&lt;a href="https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html" target="_blank" rel="noopener">here&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>These are all
&lt;a href="https://docs.gitlab.com/ee/ci/variables/predefined_variables.html" target="_blank" rel="noopener">“predefined” variables&lt;/a>, meaning they already exist in the Gitlab CI/CD context as part of having a
&lt;a href="https://www.gitlab.com" target="_blank" rel="noopener">www.gitlab.com&lt;/a> account, without you explicitly defining them. However, I did run into some issues using &lt;code>CI_DEPLOY_USER&lt;/code> and &lt;code>CI_DEPLOY_PASSWORD&lt;/code>. In addition to having predefined variables provided by Gitlab CI/CD, we can also
&lt;a href="./docs/SettingEnvVariables.md">&lt;em>manually&lt;/em> predefine variables&lt;/a> for a whole Gitlab Project or for a whole Group. Go to the page for your &lt;strong>Project/Group &amp;gt; Settings &amp;gt; CI/CD &amp;gt; Variables &amp;gt; Expand&lt;/strong>. For example, I defined the &lt;code>CI_DEPLOY_USER&lt;/code> and &lt;code>CI_DEPLOY_PASSWORD&lt;/code> variables for my Group. These variables are the Group-level authentication tokens and are now made accessible to all Gitlab CI/CD jobs running under this Group. Additionally, although not shown here (because it&amp;rsquo;s used within the &lt;code>setup_tokens.sh&lt;/code> script), we&amp;rsquo;ve also defined an environment variable called &lt;code>PACKAGE_REGISTRY_ID&lt;/code> that tells the CI/CD pipeline where to build and push packages.&lt;/p>
&lt;p>And finally, what we’ve been waiting for, conditional pipeline jobs:&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">rules&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">if&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">$CI_PIPELINE_SOURCE == &amp;#39;merge_request_event&amp;#39; &amp;amp;&amp;amp; $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == &amp;#34;main&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As part of this job, I&amp;rsquo;ve defined a rule that indicates that this job should &lt;strong>only&lt;/strong> be run 1) upon a merge request event (i.e. if you click “Create Merge Request” in the console), and 2) if the name of the target branch of that merge request event is &lt;code>main&lt;/code>. For example, if I create a new branch called &lt;code>dev&lt;/code> off of &lt;code>main&lt;/code> and create a merge request in the Gitlab console, this job will run. However, it’s important to note that, if you push a commit to the &lt;code>dev&lt;/code> branch &lt;em>after&lt;/em> creating the merge request, this job will &lt;em>still&lt;/em> run, even if you haven’t created a &lt;em>new&lt;/em> merge request e.g. &lt;code>CI_PIPELINE_SOURCE&lt;/code> == &amp;ldquo;merge_request_event&amp;rdquo; will always default to &lt;code>TRUE&lt;/code> after the first merge request event, as long as the source branch is still actively being developed. Additionally, the job itself for this &lt;strong>will run the source branch code, not the target branch code&lt;/strong>. For Gitlab Premium users, there is an additional criterion called a “merged_result_event”, which would run the target branch (&lt;code>main&lt;/code>) code after merging the source branch (&lt;code>dev&lt;/code>) into the target branch.&lt;/p>
&lt;h2 id="building-and-pushing-a-package">Building and pushing a package&lt;/h2>
&lt;p>Building a package locally is straightforward, but doing so within a Gitlab CI/CD pipeline is a little more complicated. But, we can imagine adding this type of task to a CI/CD pipeline, and conditioning it on a merge request (or something of that kind).&lt;/p>
&lt;p>I&amp;rsquo;ve called this job &lt;code>build-package&lt;/code> and it belongs to a stage also called &lt;code>build-package&lt;/code>. We first set up the &lt;code>.pypirc&lt;/code> and &lt;code>.netrc&lt;/code> files in image running the job, and then install the &lt;code>build&lt;/code> and &lt;code>twine&lt;/code> libraries in the &lt;code>before_script&lt;/code> attribute. Then, using the &lt;code>script&lt;/code> attribute, we build our package, and push it to our package registry (we&amp;rsquo;ve defined the registry in our &lt;code>.pypirc&lt;/code> file &amp;ndash; here, it&amp;rsquo;s referred to as the &amp;ldquo;personal&amp;rdquo; registry.)&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">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">python:3.9-slim&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">variables&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">PACKAGE_REGISTRY_NAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;personal&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">stages&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="l">build-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="c">#### BUILDING PACKAGE AND PUSHING TO GITLAB PACKAGE REGISTRY&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">build-package&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">stage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build-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">before_script&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="l">chmod +x ./setup_tokens.sh; ./setup_tokens.sh&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="l">apt-get update&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="l">apt-get install --yes --no-install-recommends gcc g++ libffi-dev&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="l">python3 -m pip install build twine&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">script&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="l">python3 -m build&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="l">python3 -m twine upload --repository ${PACKAGE_REGISTRY_NAME} dist/* --verbose&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Some of the (many) external links I used in this process:&lt;/p>
&lt;ul>
&lt;li>
&lt;a href="https://docs.gitlab.com/ee/ci/variables/predefined_variables.html" target="_blank" rel="noopener">Predefined&lt;/a> CI/CD variables&lt;/li>
&lt;li>
&lt;a href="https://gitlab.com/gitlab-org/gitlab/-/issues/214014" target="_blank" rel="noopener">Adding&lt;/a> variables to the Gitlab CI/CD context&lt;/li>
&lt;li>
&lt;a href="https://gitlab.com/gitlab-org/gitlab/-/issues/350582" target="_blank" rel="noopener">Setting&lt;/a> up a ~/.netrc file with Gitlab credentials&lt;/li>
&lt;li>
&lt;a href="https://stackoverflow.com/questions/72789599/gitlab-ci-cd-execute-script-file-that-exist-in-the-repository" target="_blank" rel="noopener">Executing&lt;/a> a bash scripting within a Gitlab CI/CD pipeline&lt;/li>
&lt;li>
&lt;a href="https://stackoverflow.com/questions/58939500/how-to-pass-gitlab-ci-file-variable-to-dockerfile-and-docker-container" target="_blank" rel="noopener">Passing&lt;/a> variables to Docker image at build time&lt;/li>
&lt;li>
&lt;a href="https://www.shellhacks.com/gitlab-ci-cd-build-docker-image-push-to-registry/" target="_blank" rel="noopener">Building&lt;/a> Docker image and pushing to registry&lt;/li>
&lt;li>
&lt;a href="https://docs.gitlab.com/ee/user/packages/container_registry/authenticate_with_container_registry.html" target="_blank" rel="noopener">Authenticating&lt;/a> container registries&lt;/li>
&lt;li>
&lt;a href="https://docs.gitlab.com/ee/user/packages/pypi_repository/#authenticate-with-the-package-registry" target="_blank" rel="noopener">Setting&lt;/a> up a ~/.pypirc files with Gitlab credentials&lt;/li>
&lt;/ul></description></item><item><title>Lab Meeting: pip and the Python Packaging Index</title><link>https://kristianeschenburg.netlify.app/post/pyni-packages/</link><pubDate>Sun, 07 Jun 2020 10:20:09 -0700</pubDate><guid>https://kristianeschenburg.netlify.app/post/pyni-packages/</guid><description>&lt;p>What follows are the contents of part of a lab meeting presentation I gave recently. The topic of the meeting was &amp;ldquo;Python for Neuroimaging&amp;rdquo;, where I covered basic software development tools that brain imaging scientists might be interested in.&lt;/p>
&lt;h1 id="creating-python-packages">Creating Python Packages&lt;/h1>
&lt;p>In this lesson, I&amp;rsquo;ll show you how to build your own Python package that you can then install locally or upload to the
&lt;a href="https://pip.pypa.io/en/stable/" target="_blank" rel="noopener">Python Packaging Index&lt;/a> (for those of you familiar with
&lt;a href="https://www.r-project.org/about.html" target="_blank" rel="noopener">R&lt;/a>, think
&lt;a href="https://cran.r-project.org/" target="_blank" rel="noopener">CRAN&lt;/a>, but for Python).&lt;/p>
&lt;p>I&amp;rsquo;m going to be basing a lot of the material off of this
&lt;a href="https://packaging.python.org/tutorials/packaging-projects/" target="_blank" rel="noopener">documentation&lt;/a>, but will also show a real example using some of my own personal code.&lt;/p>
&lt;h2 id="what-are-packages">What are packages?&lt;/h2>
&lt;p>I&amp;rsquo;m sure most of you are familiar with packages and libraries already, either from Matlab, R, or Python. Packages are basically bundles of various snippets of code, i.e. &lt;strong>methods&lt;/strong>, &lt;strong>classes&lt;/strong>, &lt;strong>scripts&lt;/strong>, &lt;strong>tests&lt;/strong> etc. that are bundled together to perform some function. Generally (hopefully), there is coherence to what these snippets of code do &amp;ndash; they should interact together in some way or relate to some overarching computational goal.&lt;/p>
&lt;p>Within a package, you can have different groupings of code, where each grouping does some unique or discrete computing. These groupings are called &lt;strong>submodules&lt;/strong>. A common submodule in many packages is an &lt;strong>Input / Output (io)&lt;/strong> module that will read and write data that this package interacts with or produces. Another common submodule is often related to &lt;strong>plotting&lt;/strong> the outputs of your code. And then almost always, there are submodules that perform the brunt of the algorithmic work. So inside modules, you&amp;rsquo;ll find snippets of code that relate to the goal or concept of the module.&lt;/p>
&lt;p>Think of a package as a &lt;em>toolbox&lt;/em> with a bunch of drawers, each with a label: &lt;em>wood-working&lt;/em>, &lt;em>welding&lt;/em>, &lt;em>gardening&lt;/em>, &lt;em>flooring&lt;/em>, etc. These drawers are submodules. You can tell by their names that they each cover certain topics. Each drawer contains a set of tools: &lt;em>wood-working&lt;/em> might contain &lt;em>saw&lt;/em>, &lt;em>nail&lt;/em>, &lt;em>sandpaper&lt;/em>, &lt;em>wood glue&lt;/em>, while &lt;em>welding&lt;/em> might contain &lt;em>solder&lt;/em>, &lt;em>flux&lt;/em>, &lt;em>oxygen&lt;/em>, &lt;em>glove&lt;/em>. These tools are the functions, classes, and scripts that relate to that submodule.&lt;/p>
&lt;p>Overall, this toolbox performs some stuff related to construction, homebuilding, repair, and has discrete bundles of code useful for a variety of those tasks.&lt;/p>
&lt;h3 id="directory-structure-for-a-python-package">Directory structure for a Python package&lt;/h3>
&lt;p>Here we examine the skeleton of a package. All packages follow this basic structure.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pkg_name
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>-- __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>-- LICENSE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>-- pkg_name/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- submodule_a/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- a_1.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- submodule_b/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- b_1.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- b_2.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>-- README.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>-- setup.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>-- test/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- __init__.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>__init__.py&lt;/code> is a required file that allows your package to be imported. The only &lt;code>__init__.py&lt;/code> file that needs to contain anything is the highest-level file. The others can be empty, but they must exist. Here are the contents of the highest-level &lt;code>__init__.py&lt;/code> file:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">__all__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;a_1&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;b_1&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;b_2&amp;#39;&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">.submodule_a&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">a_1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">.submodule_b&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b_1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b_2&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>LICENSE&lt;/code> tells other users / individuals in what capacity they are allowed to use your code.&lt;/p>
&lt;p>&lt;code>README.md&lt;/code> describes how to use your code, and often contains examples. This is a &lt;strong>markdown&lt;/strong> file, but can generally be any type of &lt;strong>markup&lt;/strong> language.&lt;/p>
&lt;p>&lt;code>test/&lt;/code> is a directory in which you would want to write
&lt;a href="http://softwaretestingfundamentals.com/unit-testing/" target="_blank" rel="noopener">unit tests&lt;/a> for your code.&lt;/p>
&lt;p>&lt;code>setup.py&lt;/code> is what allows you to install your package. It&amp;rsquo;s a set of instructions that get supplied to
&lt;a href="https://setuptools.readthedocs.io/en/latest/" target="_blank" rel="noopener">setuptools&lt;/a> package.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">setuptools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">setup&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">find_packages&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">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;README.md&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">fh&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&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">fh&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">setup&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;pkg_name&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">version&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;0.1.0&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">author&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Kristian M. Eschenburg&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">author_email&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;keschenb@uw.edu&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">packages&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">find_packages&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scripts&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://github.com/kristianeschenburg/pkg_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">license&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;LICENSE.txt&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">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;An awesome package that does something&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">long_description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">long_description&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">install_requires&lt;/span>&lt;span class="o">=&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;numpy&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;pytest&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="s2">&amp;#34;matplotlib&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="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;h3 id="compiling-installing-and-uploading-your-package">Compiling, installing, and uploading your package&lt;/h3>
&lt;p>&lt;strong>1. Register on PyPi&lt;/strong>&lt;/p>
&lt;p>Once we&amp;rsquo;ve done all this, we&amp;rsquo;re just about ready to create our Python package and upload it to
&lt;a href="https://pypi.org/" target="_blank" rel="noopener">pypi.org&lt;/a>. But first, we need to create an account. For testing purposes, we&amp;rsquo;ll create a test account
&lt;a href="https://test.pypi.org/" target="_blank" rel="noopener">here&lt;/a>, but the process is the same.&lt;/p>
&lt;p>After you create your account, we need to create an API token, that will allow us to upload files to either
&lt;a href="https://test.pypi.org/" target="_blank" rel="noopener">Test PyPi&lt;/a> or
&lt;a href="https://pypi.org/" target="_blank" rel="noopener">PyPi&lt;/a> (depending on what we&amp;rsquo;re doing) &amp;ndash; the following steps are the same, regardless.&lt;/p>
&lt;p>Under your Test PyPi account, click your username in the top right, go to &lt;code>Account Settings&lt;/code>:&lt;/p>
&lt;figure >
&lt;a data-fancybox="" href="notebook_figures/packages/PyPi_Account.png" >
&lt;img src="notebook_figures/packages/PyPi_Account.png" alt="" >
&lt;/a>
&lt;/figure>
&lt;p>Scroll down and click &lt;code>Add API Token&lt;/code>:&lt;/p>
&lt;figure >
&lt;a data-fancybox="" href="notebook_figures/packages/PyPi_Token.png" >
&lt;img src="notebook_figures/packages/PyPi_Token.png" alt="" >
&lt;/a>
&lt;/figure>
&lt;p>Follow the instructions there, making sure to select &amp;ldquo;Entire Account&amp;rdquo; option under the &lt;code>Scope&lt;/code> tab.&lt;/p>
&lt;p>&lt;strong>DO NOT CLOSE THIS WINDOW WHEN THIS IS COMPLETE&lt;/strong>&lt;/p>
&lt;p>Next, type&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> &lt;span class="nv">$HOME&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">touch .pypirc&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and using your favorite text editor, enter the following:&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">[testpypi]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> repository: https://test.pypi.org/legacy/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> username = __token__
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> password = pypi-***&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If we were creating a token for PyPi, we&amp;rsquo;d type:&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">[pypi]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> repository: https://pypi.org/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> username = __token__
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> password = pypi-***&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Close your current terminal, and open a new window to refresh your settings. Now, when we go to upload our package to PyPi, we&amp;rsquo;ll be able to type the commands without needed to supply a username and password directly.&lt;/p>
&lt;p>&lt;strong>2. Compile your package&lt;/strong>&lt;/p>
&lt;p>First, we need to make sure that a few Python packages are installed. Namely, we need to install
&lt;a href="https://pip.pypa.io/en/stable/" target="_blank" rel="noopener">pip&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">python get-pip.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install -U pip&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then we can install the following packages:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install --upgrade pip setuptools wheel &lt;span class="c1"># for installing Python packages&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install tqdm &lt;span class="c1"># progress bar package&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install --user --upgrade twine &lt;span class="c1"># for publishing to PyPi&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then, we can compile our package:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">python setup.py bdist_wheel&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which creates the directories &lt;code>dist&lt;/code>, &lt;code>build&lt;/code>, and &lt;code>pkg_name.egg-info&lt;/code>. The &lt;code>*.egg-info&lt;/code> file is basically some zipped meta-data about your package, but we&amp;rsquo;re really only interested in the &lt;code>*.whl&lt;/code> file in &lt;code>dist&lt;/code> &amp;ndash; &amp;ldquo;wheels&amp;rdquo; are a &amp;ldquo;distribution&amp;rdquo; format, newly designed to replace &amp;ldquo;eggs&amp;rdquo;. I won&amp;rsquo;t go into it here, but &lt;strong>eggs&lt;/strong> were sort an &lt;em>ad hoc&lt;/em> solution to packaging Python code &amp;ndash; &lt;strong>wheels&lt;/strong> were part of
&lt;a href="https://www.python.org/dev/peps/pep-0427/" target="_blank" rel="noopener">PEP427&lt;/a> i.e. is actually an &amp;ldquo;enhancement&amp;rdquo; to the Python language, and the formal way of packaging Python code.&lt;/p>
&lt;p>&lt;strong>3. Installing your code&lt;/strong>&lt;/p>
&lt;p>We can install our code locally with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install dist/pkg_name-0.0.0-py3-none-any.whl&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you want to install an &amp;ldquo;editable&amp;rdquo; version of your package, do this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install -e .&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will allow you to change your &lt;code>*.py&lt;/code> files and have these changes take effect immediately when importing your package, without needing to rebuild each time &amp;ndash; but this method installs from the &lt;strong>egg&lt;/strong> distribution, and generally produces larger build files, since the build needs to keep track of your actual source code.&lt;/p>
&lt;p>&lt;strong>4. Upload your code&lt;/strong>&lt;/p>
&lt;p>We can upload our code to PyPi now using the following command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">python3 -m twine upload --repository testpypi dist/*&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, if you click &amp;ldquo;Your Projects&amp;rdquo; under your account name on PyPi, you&amp;rsquo;ll see that you project has been uploaded.&lt;/p>
&lt;p>** I should note that, any time you want to upgrade your code and upload it to PyPi again, you need to remove all files from the &lt;code>dist&lt;/code> directory, increment the &lt;code>version&lt;/code> number in the &lt;code>setup.py&lt;/code> file &amp;ndash; i.e. 0.0.0 &amp;ndash;&amp;gt; 0.0.1 &amp;ndash; rebuild your package with&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">bash&lt;/span> &lt;span class="n">python&lt;/span> &lt;span class="n">setup&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">py&lt;/span> &lt;span class="n">bdist_wheel&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="example-with-personal-package">Example with personal package&lt;/h3>
&lt;p>I don&amp;rsquo;t generally upload my code to PyPi (probably scared bugs in the code, and people finding them, and then thinking I&amp;rsquo;m terrible at software development, and going down a long spiral of self-deprecation, but I digress&amp;hellip;) but I do upload it all to GitHub. In either case, here is a walk-through of packaging some software called &lt;code>pysurface&lt;/code> that I use for processing mesh-based data &amp;ndash; I use it for adjacency matrices, performing Laplacian smoothing on surfaces, sampling points from mesh triangle simplices, plotting on surfaces&amp;hellip; Just some stuff that I find myself doing a lot.&lt;/p>
&lt;p>Here is the directory containing all my code:&lt;/p>
&lt;figure >
&lt;a data-fancybox="" href="notebook_figures/packages/pysurface_code.png" >
&lt;img src="notebook_figures/packages/pysurface_code.png" alt="" >
&lt;/a>
&lt;/figure>
&lt;p>You&amp;rsquo;ll see 5 different modules: &lt;code>graphs&lt;/code>, &lt;code>operations&lt;/code>, &lt;code>plotting&lt;/code>, &lt;code>spectra&lt;/code>, and &lt;code>utilities&lt;/code>, and you&amp;rsquo;ll note that each module directory has a &lt;code>__init__.py&lt;/code> file.&lt;/p>
&lt;p>Here is my &lt;code>setup.py&lt;/code> file:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">os&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">setuptools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">setup&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">find_packages&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">sys&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="n">here&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">abspath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dirname&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&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">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">here&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;README.rst&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">readme_file&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">readme&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">readme_file&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">here&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;requirements.txt&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">requirements_file&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Parse requirements.txt, ignoring any commented-out lines.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">requirements&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">line&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">requirements_file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">splitlines&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;#&amp;#39;&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">setup&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;pysurface&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">version&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;0.0.4&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">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Python package for quickly processing surface meshes.&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">long_description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">readme&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">author&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Kristian Eschenburg&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">author_email&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;keschenb@uw.edu&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">url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;https://github.com/kristianeschenburg/pysurface&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">packages&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">find_packages&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">entry_points&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;console_scripts&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># &amp;#39;some.module:some_function&amp;#39;,&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;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">include_package_data&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">package_data&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;pysurface&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># When adding files here, remember to update MANIFEST.in as well,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># or else they will not be included in the distribution on PyPI!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># &amp;#39;path/to/data_file&amp;#39;,&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;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">install_requires&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">requirements&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">license&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;BSD (3-clause)&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">classifiers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Development Status :: 2 - Pre-Alpha&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="s1">&amp;#39;Natural Language :: English&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="s1">&amp;#39;Programming Language :: Python :: 3&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="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>You can see that I&amp;rsquo;ve run&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">python setup.py bdist_wheel&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>based off the &lt;code>dist&lt;/code>, &lt;code>build&lt;/code>, and &lt;code>pysurface.egg-info&lt;/code> directories. &lt;code>dist&lt;/code> contains a file called &lt;code>pysurface-0.0.4-py3-none.any.whl&lt;/code>, which is the actual distribution that can be used for installation. I&amp;rsquo;ve uploaded the code to Test PyPi, and this is what we see:&lt;/p>
&lt;figure >
&lt;a data-fancybox="" href="notebook_figures/packages/PyPi_Project.png" >
&lt;img src="notebook_figures/packages/PyPi_Project.png" alt="" >
&lt;/a>
&lt;/figure>
&lt;p>We can then install the package and all of it&amp;rsquo;s dependencies from TestPypi via&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install --index-url https://test.pypi.org/simple/ pysurface&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now I can do something like the following in a Python script:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">pysurface&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># or&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pysurface&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">graphs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">spectra&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># or&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pysurface.spectra&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">eigenspectrum&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item></channel></rss>