Skip to content

Python Projects

The Python project generator can be used to create a modern Python library or application configured with best practices, managed with UV, a single lockfile and virtual environment in an UV workspace, pytest for running tests, Ruff for static analysis, and ty for type checking.

You can generate a new Python project in two ways:

Terminal window
pnpm nx g @aws/nx-plugin:py#project
You can also perform a dry-run to see what files would be changed
Terminal window
pnpm nx g @aws/nx-plugin:py#project --dry-run
ParameterTypeDefaultDescription
name Requiredstring-The name of the Python project
directory stringpackagesParent directory where the project is placed.
subDirectory string-The sub directory the project is placed in. By default this is the project name.
type Requiredapplication | libraryapplicationWhether the project is an application or library
moduleName string-Python module name
preferInstallDependencies booleantrueWhether to prefer installing dependencies after the generator runs. Set to false to defer installing when batching multiple generators (an install still runs if needed so subsequent generators can compute the Nx project graph); install once at the end.

The generator will create the following project structure in the <directory>/<name> directory:

  • Directory<module-name>
    • __init__.py Module initialisation
  • Directorytests
    • __init__.py Module initialisation
    • conftest.py Test configuration
    • test_noop.py Placeholder test
  • project.json Project configuration and build targets
  • pyproject.toml Packaging configuration file used by UV
  • .python-version Contains the project’s Python version

You may also notice the following files created/updated in the root of your workspace:

  • pyproject.toml Workspace level packaging configuration for UV
  • .python-version Contains the workspace Python version
  • uv.lock Lockfile for Python dependencies

Add your Python source code in the <module-name> directory.

Importing your Library Code in Other Projects

Section titled “Importing your Library Code in Other Projects”

Use the add target to add a dependency to a Python project.

Suppose we have created two python projects, my_app and my_lib. These will have fully qualified project names of my_scope.my_app and my_scope.my_lib, and by default will each have module names of my_scope_my_app and my_scope_my_lib.

For my_app to depend on my_lib, we can run the following command:

Terminal window
pnpm nx run my_scope.my_app:add my_scope.my_lib

You can then import your library code:

packages/my_app/my_scope_my_app/main.py
from my_scope_my_lib.my_module import my_function

Above, my_scope_my_lib is the module name for the lib, my_module corresponds to a Python source file my_module.py, and my_function is a method defined in that file.

To add dependencies to your project, you can run the add target in your Python project, for example:

Terminal window
pnpm nx run my_scope.my_library:add some-pip-package

This will add the dependency to your project’s pyproject.toml file, and update the root uv.lock.

When you use your Python project as runtime code (for example as the handler for an AWS lambda function), you will need to create a bundle of the source code and all its dependencies. You can achieve this by adding a target such as the following to your project.json file:

project.json
{
...
"targets": {
...
"bundle": {
"cache": true,
"executor": "nx:run-commands",
"outputs": ["{workspaceRoot}/dist/packages/my_library/bundle"],
"options": {
"commands": [
"uv export --frozen --no-dev --no-editable --project packages/my_library --package my_scope.my_library -o dist/packages/my_library/bundle/requirements.txt",
"uv pip install -n --no-deps --no-installer-metadata --no-compile-bytecode --python-platform x86_64-manylinux_2_28 --python `uv python pin` --target dist/packages/my_library/bundle -r dist/packages/my_library/bundle/requirements.txt"
],
"parallel": false
},
"dependsOn": ["compile"]
},
},
}

Your Python project is configured with a build target (defined in project.json), which you can run via:

Terminal window
pnpm nx build <project-name>

Where <project-name> is the fully qualified name of your project.

The build target will compile, lint, test and type check your project.

Build output can be found in the root dist folder in your workspace, inside a directory for your package and target, for example dist/packages/<my-library>/build

To build all of the projects in your workspace, run:

Terminal window
pnpm nx run-many --target build

Or use the shorthand command:

Terminal window
pnpm build

pytest is configured for testing your project.

Tests should be written in the test directory within your project, in python files prefixed with test_, for example:

  • Directorymy_library
    • my_module.py
  • Directorytests
    • test_my_module.py Tests for my_module.py

Tests are methods which begin with test_ and make assertions to verify expectations, for example:

tests/test_my_module.py
from my_library.my_module import say_hello
def test_say_hello():
assert say_hello("Darth Vader") == "Hello, Darth Vader!"

For more details about how to write tests, please refer to the pytest documentation.

Tests will run as part of the build target for your project, but you can also run them separately by running the test target:

Terminal window
pnpm nx test <project-name>

You can run an individual test or suite of tests using the -k flag, specifying either the name of the test file or method:

Terminal window
pnpm nx test <project-name> -k 'test_say_hello'

Python projects use ty for type checking.

Type checking runs as part of the build target for your project, but you can also run it separately via the typecheck target:

Terminal window
pnpm nx run <project-name>:typecheck

To suppress a specific diagnostic for a single line, add a # ty: ignore[<rule>] comment at the end of the line, for example:

value: int = "not an int" # ty: ignore[invalid-assignment]

To configure type checking behaviour across your project, add a [tool.ty] section to your project’s pyproject.toml. Refer to the ty configuration reference for available options.

Python projects use Ruff for linting.

To invoke the linter to check your project, you can run the lint target.

Terminal window
pnpm nx lint <project-name>

The majority of linting or formatting issues can be fixed automatically. You can tell Ruff to fix lint issues by running with the --configuration=fix argument.

Terminal window
pnpm nx lint <project-name> --configuration=fix

Similarly if you would like to fix all lint issues in all packages in your workspace, you can run:

Terminal window
pnpm nx run-many --target lint --all --configuration=fix

To avoid linting issues slowing you down during development (particularly if you have non auto-fixable issues in your project), you can run a build with the skip-lint configuration:

Terminal window
pnpm nx run-many --target build --configuration=skip-lint

This will still run Ruff as part of the build, but the lint target will always be considered successful.