> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Manage Python dependencies

> See how to manage Python dependencies with different package managers.

export const DynamicCodeBlock = ({language = 'yaml', filename, icon, lines, wrap, expandable, highlight, focus, children}) => {
  const STORAGE_KEY = 'upsun_versions_cache';
  const COMPOSABLE_STORAGE_KEY = 'upsun_composable_cache';
  const CACHE_TTL = 5 * 60 * 1000;
  const API_URL = 'https://meta.upsun.com/images';
  const COMPOSABLE_API_URL = 'https://meta.upsun.com/composable';
  const DEBUG_PREFIX = '[DynamicCodeBlock cache]';
  const [versionData, setVersionData] = useState(null);
  const [versionError, setVersionError] = useState(false);
  const [composableData, setComposableData] = useState(null);
  const [composableError, setComposableError] = useState(false);
  useEffect(() => {
    const fetchData = async () => {
      let cachedData = null;
      let cachedEtag = null;
      if (typeof localStorage !== 'undefined') {
        try {
          const cached = localStorage.getItem(STORAGE_KEY);
          if (cached) {
            const parsed = JSON.parse(cached);
            cachedData = parsed?.data || null;
            cachedEtag = parsed?.etag || null;
            if (cachedData && Date.now() - parsed.timestamp < CACHE_TTL) {
              return cachedData;
            }
          }
        } catch (err) {
          console.error('Failed to load from cache:', err);
        }
      }
      const requestHeaders = cachedEtag ? {
        'If-None-Match': cachedEtag
      } : {};
      console.debug(`${DEBUG_PREFIX} revalidating`, {
        storageKey: STORAGE_KEY,
        hasCachedData: Boolean(cachedData),
        hasCachedEtag: Boolean(cachedEtag)
      });
      const response = await fetch(API_URL, {
        headers: requestHeaders
      });
      if (response.status === 304 && cachedData) {
        console.debug(`${DEBUG_PREFIX} revalidated (304)`, {
          storageKey: STORAGE_KEY
        });
        if (typeof localStorage !== 'undefined') {
          try {
            const etag = response.headers.get('etag') || cachedEtag;
            localStorage.setItem(STORAGE_KEY, JSON.stringify({
              data: cachedData,
              etag,
              timestamp: Date.now()
            }));
          } catch (err) {
            console.error('Failed to refresh cache metadata:', err);
          }
        }
        return cachedData;
      }
      if (!response.ok) throw new Error(`API request failed: ${response.statusText}`);
      const data = await response.json();
      const etag = response.headers.get('etag');
      console.debug(`${DEBUG_PREFIX} refreshed (200)`, {
        storageKey: STORAGE_KEY,
        etag
      });
      if (typeof localStorage !== 'undefined') {
        try {
          localStorage.setItem(STORAGE_KEY, JSON.stringify({
            data,
            etag,
            timestamp: Date.now()
          }));
        } catch (err) {
          console.error('Failed to cache data:', err);
        }
      }
      return data;
    };
    fetchData().then(data => setVersionData(data)).catch(err => console.error('Failed to fetch version data:', err));
  }, []);
  const findHighestVersion = versionsMap => {
    if (!versionsMap || Object.keys(versionsMap).length === 0) return null;
    const entries = Object.entries(versionsMap);
    const active = entries.filter(([, v]) => v.upsun && v.upsun.status === 'supported' || v.upsun && v.upsun.status === 'deprecated');
    const candidates = active.length > 0 ? active : entries;
    let [highestName] = candidates[0];
    for (let i = 1; i < candidates.length; i++) {
      const [currentName] = candidates[i];
      const cp = currentName.split('.').map(Number);
      const hp = highestName.split('.').map(Number);
      for (let j = 0; j < Math.max(cp.length, hp.length); j++) {
        if ((cp[j] || 0) > (hp[j] || 0)) {
          highestName = currentName;
          break;
        } else if ((cp[j] || 0) < (hp[j] || 0)) {
          break;
        }
      }
    }
    return highestName;
  };
  const getVersion = (lang, requestedVersion = 'latest') => {
    if (lang === 'composable') {
      if (!composableData || !composableData.versions || Object.keys(composableData.versions).length === 0) return null;
      if (requestedVersion && requestedVersion !== 'latest') {
        return (requestedVersion in composableData.versions) ? requestedVersion : null;
      }
      return findHighestVersion(composableData.versions);
    }
    if (!versionData) return null;
    const imageData = versionData[lang];
    if (!imageData || !imageData.versions || Object.keys(imageData.versions).length === 0) {
      return null;
    }
    if (requestedVersion && requestedVersion !== 'latest') {
      return (requestedVersion in imageData.versions) ? requestedVersion : null;
    }
    return findHighestVersion(imageData.versions);
  };
  let code = typeof children === 'string' ? children : String(children || '');
  const codeLines = code.split('\n');
  while (codeLines.length > 0 && codeLines[0].trim() === '') codeLines.shift();
  while (codeLines.length > 0 && codeLines[codeLines.length - 1].trim() === '') codeLines.pop();
  if (codeLines.length > 0) {
    const indents = codeLines.filter(line => line.trim().length > 0).map(line => line.match(/^[ \t]*/)[0].length);
    const minIndent = Math.min(...indents);
    code = codeLines.map(line => line.slice(minIndent)).join('\n');
  }
  code = code.replace(/\{\{version:(.*?)\}\}/g, (match, params) => {
    const parts = params.split(':');
    const lang = parts[0];
    const ver = parts[1] || 'latest';
    const isComposable = lang === 'composable';
    const hasError = isComposable ? composableError : versionError;
    const dataReady = isComposable ? composableData !== null : versionData !== null;
    if (hasError) return '(unavailable)';
    if (dataReady) {
      const resolvedVersion = getVersion(lang, ver);
      return resolvedVersion || match;
    }
    return '...';
  });
  const codeBlockProps = {
    language,
    ...filename && ({
      filename
    }),
    ...icon && ({
      icon
    }),
    ...lines !== undefined && ({
      lines
    }),
    ...wrap !== undefined && ({
      wrap
    }),
    ...expandable !== undefined && ({
      expandable
    }),
    ...highlight && ({
      highlight
    }),
    ...focus && ({
      focus
    })
  };
  return <CodeBlock {...codeBlockProps}>{code}</CodeBlock>;
};

export const DisclaimerNix = () => <Tip>
    You can now use composable image to install runtimes and tools in your application container. To find out more, see the <a href="/docs/configure-apps/app-reference/composable-image">Composable image</a> topic.
  </Tip>;

<DisclaimerNix />

You can manage Python packages in different ways.
Python images come with pip installed,
but they're flexible enough so you can choose what package manager you want.
This article describes how to configure major package management tools.

This package management is different from global dependencies (packages available as commands),
which you can add in your [app configuration](/docs/configure-apps).
See more about [managing global dependencies](/docs/languages/python#package-management).

## Pip

[pip](https://pip.pypa.io/en/stable/) is the primary package installer for Python
and comes installed on every Python container.
You can use it to install packages from the Python Package Index and other locations.

To manage packages with pip,
commit a `requirements.txt` file with all of the dependencies needed for your app.
Then install the packages in your [`build` hook](/docs/configure-apps/hooks),
such as by running the following command: `pip install -r requirements.txt`.

The following sections present ideas to keep in mind to ensure repeatable deployments on Upsun.

### pip version

The version of pip on Python containers gets updated regularly.
But it isn't guaranteed to be the latest version or the version that matches your local environment.
You might want to define a specific version of pip in your deployments to further enforce repeatable builds.

To do so, modify your [app configuration](/docs/configure-apps), as in the following examples:

<Tabs>
  <Tab title="Latest version">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  type: 'python:{{version:python:latest}}'
                  hooks:
                    build: |
                      # Fail the build if any errors occur
                      set -eu
                      # Download the latest version of pip
                      python{{version:python:latest}} -m pip install --upgrade pip
                      # Install dependencies
                      pip install -r requirements.txt`
          }
    </DynamicCodeBlock>
  </Tab>

  <Tab title="Specific version">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  type: 'python:{{version:python:latest}}'
                  variables:
                    env:
                      PIP_VERSION: '22.3.1'
                  hooks:
                    build: |
                      # Fail the build if any errors occur
                      set -eu
                      # Download a specific version of pip
                      python{{version:python:latest}} -m pip install pip==$PIP_VERSION
                      # Install dependencies
                      pip install -r requirements.txt`
          }
    </DynamicCodeBlock>
  </Tab>
</Tabs>

### pip freeze

You can write `requirements.txt` files in various ways.
You can specify anything from the latest major to a specific patch version in a [requirement specifier](https://pip.pypa.io/en/stable/reference/requirement-specifiers/).
Use `pip freeze` before committing your requirements to pin specific package versions.
This ensures repeatable builds on Upsun with the same packages.

## Pipenv

[Pipenv](https://pipenv.pypa.io/en/latest/) is a package manager for Python
that creates and manages a virtual environment for Python projects.
Dependencies are tracked and defined within a `Pipfile`.
It also generates a `Pipfile.lock` file to produce repeatable installs.

You can specify the latest or a specific version of Pipenv
in your deployments to ensure repeatable builds.
Because Pipenv depends on pip, you might want to also specify the pip version.

<Tabs>
  <Tab title="Latest version">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  type: 'python:{{version:python:latest}}'
                  dependencies:
                    python3:
                      pipenv: '*'
                  hooks:
                    build: |
                      # Fail the build if any errors occur
                      set -eu
                      # Download the latest version of pip
                      python{{version:python:latest}} -m pip install --upgrade pip
                      # Install dependencies
                      # Include \`--deploy\` to fail the build if \`Pipfile.lock\` isn't up to date
                      pipenv install --deploy`
          }
    </DynamicCodeBlock>
  </Tab>

  <Tab title="Specific version">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  type: 'python:{{version:python:latest}}'
                  variables:
                    env:
                      PIP_VERSION: '22.3.1'
                  dependencies:
                    python3:
                      pipenv: '2024.4.1'
                  hooks:
                    build: |
                      # Fail the build if any errors occur
                      set -eu
                      # Download a specific version of pip
                      python{{version:python:latest}} -m pip install pip==$PIP_VERSION
                      # Install dependencies
                      # Include \`--deploy\` to fail the build if \`Pipfile.lock\` isn't up to date
                      pipenv install --deploy`
          }
    </DynamicCodeBlock>
  </Tab>
</Tabs>

## UV

[uv](https://docs.astral.sh/uv/) is an extremely fast Python package and project
manager, written in Rust.

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        # The app's name, which must be unique within the project.
        myapp:
          type: 'python:{{version:python:latest}}'
          dependencies:
            python3:
              uv: "*"`
  }
</DynamicCodeBlock>

## Poetry

[Poetry](https://python-poetry.org/docs/) is a tool for dependency management and packaging in Python.
It allows you to declare the libraries your project depends on and manages them for you.
Poetry offers a lock file to ensure repeatable installs and can build your project for distribution.
It also creates and manages virtual environments to keep project work isolated from the rest of your system.

To set up Poetry on Upsun, follow these steps:

1. Configure your virtual environment by setting two variables in your [app configuration](/docs/configure-apps).

   * [`POETRY_VIRTUALENVS_IN_PROJECT`](https://python-poetry.org/docs/configuration/#virtualenvsin-project):
     Setting this to `true` places the virtual environment at the root of the app container: `/app/.venv`.
   * [`POETRY_VIRTUALENVS_CREATE`](https://python-poetry.org/docs/configuration/#virtualenvscreate):
     Setting this to `true` ensures that the same virtual environment created during the build hook is reused in subsequent steps.

   Set the variables as follows:

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        # The app's name, which must be unique within the project.
        myapp:
          type: 'python:{{version:python:latest}}'
          variables:
            env:
              POETRY_VIRTUALENVS_IN_PROJECT: true
              POETRY_VIRTUALENVS_CREATE: true`
  }
</DynamicCodeBlock>

2. Install Poetry.
   You can specify the latest or a specific version of Poetry in your deployments to ensure repeatable builds.

<Tabs>
  <Tab title="Latest version">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                  # The location of the application's code.
                  source:
                    root: "myapp"
                  type: 'python:{{version:python:latest}}'
                  dependencies:
                    python3:
                      poetry: '*'
                  variables:
                    env:
                      POETRY_VIRTUALENVS_IN_PROJECT: true
                      POETRY_VIRTUALENVS_CREATE: true
                  hooks:
                    build: |
                      # Fail the build if any errors occur
                      set -eu
                      # Download the latest version of pip
                      python{{version:python:latest}} -m pip install --upgrade pip
                      # Install dependencies
                      poetry install`
          }
    </DynamicCodeBlock>
  </Tab>

  <Tab title="Specific version">
    <DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
      {`
              applications:
                # The name of the app container. Must be unique within a project.
                myapp:
                   # The location of the application's code.
                   source:
                     root: "myapp"
                   type: 'python:{{version:python:latest}}'
                   dependencies:
                     python3:
                       poetry: '>=1.8'
                     variables:
                       env:
                         POETRY_VIRTUALENVS_IN_PROJECT: true
                         POETRY_VIRTUALENVS_CREATE: true

                      hooks:
                        build: |
                          # Fail the build if any errors occur
                          set -eu
                          # Download the latest version of pip
                          python{{version:python:latest}} -m pip install --upgrade pip
                          # Install dependencies
                          poetry install`
          }
    </DynamicCodeBlock>
  </Tab>
</Tabs>

3. Make Poetry available outside the build hook.
   Although step 2 updated the `PATH` to make Poetry available during the build hook,
   it isn't enough to make it available at subsequent stages.

   To use Poetry in a start command, a deploy hook, or during SSH sessions,
   update the `PATH` in a [`.environment` file](/docs/development/variables/set-variables#set-variables-via-script).

   ```text .environment theme={null}
   # Updates PATH when Poetry is used, making it available during deploys, start commands, and SSH.
   if [ -n "$POETRY_VERSION" ]; then
     export PATH="/app/.local/bin:$PATH"
   fi
   ```
