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:

Run this generator@aws/nx-plugin:py#project

pnpm nx g @aws/nx-plugin:py#project
Build your command6

Required

Required

Generator Options6 options
nameRequiredstring

The name of the Python project

typeRequiredenumDefault: application

Whether the project is an application or library

applicationlibrary
directorystringDefault: packages

Parent directory where the project is placed.

subDirectorystring

The sub directory the project is placed in. By default this is the project name.

moduleNamestring

Python module name

preferInstallDependenciesbooleanDefault: true

Whether 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. The py#lambda-function, py#api and py#mcp-server generators add this for you. To add one by hand, add targets such as the following to your project.json file, matching the shape those generators vend:

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

Install Python dependencies with the following command:

Terminal window
uv sync

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

Your project also has an assemble target, which produces whatever your project contributes to a deployment (for example its compiled or bundled output), without running the lint, test or type-check gates.

Terminal window
pnpm nx assemble <project-name>

The deploy targets depend on assemble, so deploying builds only what it is about to deploy.

pytest is configured for testing your project.

Tests should be written in the tests 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'

Your project’s tests directory is excluded from the production named input in nx.json.

Targets whose output cannot contain a test file - such as compile and any bundle targets - read production rather than default, so editing a test does not invalidate them or any task in a project which depends on yours.

The exclusion is deliberately limited to the tests directory. A test_*.py file inside your package directory is treated as production code, since it is packaged into your built distribution, and so still invalidates the build.

The test, lint, format and typecheck targets read default and so still re-run when you edit a test. Note that typecheck type checks your tests too, so a type error in a test is still reported.

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.