Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4900890
Migrate development workflow to uv lockfile
wmcv Aug 4, 2026
c1f8601
Merge branch 'master' into issue-2015-dev-workflow
liopeer Aug 5, 2026
364261d
Merge branch 'master' into issue-2015-dev-workflow
liopeer Aug 6, 2026
cba91dd
Address dependency workflow review feedback
wmcv Aug 6, 2026
4937100
update lockfile
liopeer Aug 7, 2026
85be3fe
pin python version for extras-no-video
liopeer Aug 7, 2026
81ec3cf
Merge branch 'master' into issue-2015-dev-workflow
liopeer Aug 10, 2026
fcc6c8a
rework the make targets and workflows
liopeer Aug 10, 2026
a1fa5c4
move to v9.0.0 instead of v9 for astral uv setup
liopeer Aug 10, 2026
adecc85
lower bounds for dependency groups
liopeer Aug 10, 2026
5b7b75f
fix: raise minimal setuptools floor to 56 for py3.8 test collection
gabrielfruet Aug 28, 2026
7ff65b9
Merge branch 'master' into issue-2015-dev-workflow
gabrielfruet Aug 28, 2026
8c35e07
fix: floor numpy to 1.21 in minimal group for numpy.typing.NDArray
gabrielfruet Aug 28, 2026
b7eb847
fix: drop removed lightly-download from test-cli smoke test
gabrielfruet Aug 28, 2026
760361e
fix: cap minimal setuptools at <59.6 for torch 1.10 distutils.version
gabrielfruet Aug 28, 2026
c052116
fix: run lock-check before install-maximal so CI catches a stale lock
gabrielfruet Aug 28, 2026
eafd904
chore: drop dead API-only targets and fix stale dev-workflow docs
gabrielfruet Aug 28, 2026
0507b57
build: skip PyAV system deps outside CI and run mypy hook via uv
gabrielfruet Aug 31, 2026
1a88007
ci: run tests and type-check on 3.8/3.12, drop dead server env
gabrielfruet Aug 31, 2026
ef0ed67
build: drop redundant av python marker and refine dep comments
gabrielfruet Aug 31, 2026
aa00fa7
ci: run maximal test and type-check on 3.12 only
gabrielfruet Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ Main entry points:
Package manager is `uv`.

```bash
uv venv && source .venv/bin/activate
make install-dev # installs all extras + pre-commit hooks
make install-dev
```

This synchronizes the project environment from the lockfile and installs the pre-commit hooks. Commands should be run through the Makefile or `uv run --frozen`; manual virtual environment activation is not required.

| Command | Purpose |
|---|---|
| `make format` | Auto-fix imports/formatting with ruff |
Expand All @@ -50,6 +51,9 @@ make install-dev # installs all extras + pre-commit hooks

If `make format` reports changes, re-run it before `make all-checks`.

Development commands should be executed through the Makefile or `uv run --frozen`
to ensure they use the locked dependency versions.

## Code style (see `CONTRIBUTING.md` for full detail)

- Google + PyTorch styleguide. Docstrings use triple double quotes and the
Expand Down
26 changes: 19 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,21 @@ Follow these steps to start contributing:

**do not** work on the `master` branch.

4. Set up a development environment. We use [uv](https://github.com/astral-sh/uv) for
development. Create and activate a virtual environment, then install the dev dependencies:
4. Set up the development environment. We use [uv](https://github.com/astral-sh/uv)
to manage dependencies and run project commands:

```bash
uv venv
source .venv/bin/activate
make install-dev
```

This synchronizes the project environment from the lockfile and installs the
pre-commit hooks. You do not need to activate the virtual environment manually.

5. Develop the features on your branch.

As you work on the features, you should make sure that the code is formatted and the
test suite passes:
As you work on the features, you should make sure that the code is formatted and
the test suite passes. The Makefile runs development tools through the locked `uv`
environment, so manual virtual environment activation is not required:
Comment thread
liopeer marked this conversation as resolved.

```bash
make format
Expand All @@ -104,6 +106,16 @@ Follow these steps to start contributing:
If you get a formatting error from ruff, please run `make format` again before
running `make all-checks`.

Run the full test suite with `make test`. To run a specific test directory,
module, class, or individual test through the locked environment, pass it to
`uv run --frozen pytest`, for example:

```bash
uv run --frozen pytest tests/models
uv run --frozen pytest tests/models/test_resnet.py
uv run --frozen pytest tests/models/test_resnet.py::TestClass::test_name
```

If you're modifying examples under `examples/`, make sure to update the corresponding notebooks by
running the following command:

Expand Down Expand Up @@ -292,4 +304,4 @@ class SampleClass:
from module.submodule import MyClass
```

#### This guide was inspired by the [Transformers guide to contributing](https://github.com/huggingface/transformers/blob/main/CONTRIBUTING.md) which was influenced by the [Scikit-learn guide to contributing](https://github.com/scikit-learn/scikit-learn/blob/main/CONTRIBUTING.md).
#### This guide was inspired by the [Transformers guide to contributing](https://github.com/huggingface/transformers/blob/main/CONTRIBUTING.md) which was influenced by the [Scikit-learn guide to contributing](https://github.com/scikit-learn/scikit-learn/blob/main/CONTRIBUTING.md).
76 changes: 38 additions & 38 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,36 +36,36 @@ PYTHON_DIRS = benchmarks docs examples lightly tests

# format code with ruff
format:
ruff check --fix --select I $(PYTHON_DIRS)
ruff format $(PYTHON_DIRS)
uv run --frozen ruff check --fix --select I $(PYTHON_DIRS)
uv run --frozen ruff format $(PYTHON_DIRS)

# check if code is formatted with ruff
format-check:
@echo "Checking code format..."
ruff check --select I $(PYTHON_DIRS)
ruff format --check $(PYTHON_DIRS)
uv run --frozen ruff check --select I $(PYTHON_DIRS)
uv run --frozen ruff format --check $(PYTHON_DIRS)

# lint code with ruff
lint: lint-lightly lint-tests

## lint lightly code with ruff
lint-lightly:
ruff check lightly
uv run --frozen ruff check lightly

## lint tests with ruff
lint-tests:
ruff check tests
uv run --frozen ruff check tests

## run tests
test:
pytest tests --runslow
uv run --frozen pytest tests --runslow

test-fast:
pytest tests
uv run --frozen pytest tests

## check typing
type-check:
mypy lightly tests
uv run --frozen mypy lightly tests

## run format checks
static-checks: format-check type-check
Expand All @@ -75,7 +75,7 @@ all-checks: static-checks test

## build source and wheel package
dist: clean
python -m build
uv build
Comment thread
gabrielfruet marked this conversation as resolved.
Outdated
ls -l dist


Expand All @@ -100,34 +100,41 @@ install-uv:

.PHONY: reset-venv
reset-venv:
deactivate || true
Comment thread
liopeer marked this conversation as resolved.
rm -rf .venv
uv venv .venv
uv venv


### Dependencies

# When running these commands locally, it is recommended to first reset the environment
# with: `make reset-venv && source .venv/bin/activate`
# Otherwise old dependencies might linger around.
# Project commands use uv and do not require manually activating the virtual environment.

# Set EDITABLE to -e to install the package in editable mode outside of CI. This is
# useful for local development.
# Install the package in non-editable mode in CI.
ifdef CI
EDITABLE=
Comment thread
gabrielfruet marked this conversation as resolved.
Outdated
NO_EDITABLE=--no-editable
else
EDITABLE=-e
NO_EDITABLE=
endif

# Date until which dependencies installed with --exclude-newer must have been released.
# Dependencies released after this date are ignored.
EXCLUDE_NEWER_DATE="2025-08-07"

# Min and max Python versions for dependency testing.
MINIMAL_PYTHON_VERSION=3.8
MAXIMAL_PYTHON_VERSION=3.12

# Update the lockfile using dependencies released before the cutoff date.
.PHONY: lock
lock:
uv lock --exclude-newer ${EXCLUDE_NEWER_DATE}

# Install package for local development.
.PHONY: install-dev
install-dev:
uv pip install ${EDITABLE} . --all-extras --requirement pyproject.toml
pre-commit install
uv sync --frozen --all-extras
uv run --frozen pre-commit install


# Install package with API dependencies only.
Expand All @@ -140,12 +147,9 @@ install-api-only:

# Install package with minimal dependencies.
#
# This command is split into multiple steps:
# 1. Install the dev dependencies to be able to run tests. We don't want to use
# the minimal versions for these dependencies.
# 2. Then we reinstall the package with minimal dependencies.
# 3. Finally we install setuptools<50. This is necessary for compatibility with old
# PyTorch Lightning versions that do not include the correct setuptools dependencies.
# The dev dependencies are installed together with the package's minimal dependencies.
# setuptools<50 is then installed for compatibility with old PyTorch Lightning versions
# that do not include the correct setuptools dependencies.
#
# Explanation of flags:
# --exclude-newer: We don't want to install dependencies released after that date to
Expand All @@ -155,21 +159,17 @@ install-api-only:
# Using --resolution=lowest would also download the latest versions for transitive
# dependencies which is not a realistic scenario and results in some extremely old
# dependencies being installed.
# --reinstall: Reinstall dependencies to make sure they satisfy the constraints.
.PHONY: install-minimal
install-minimal:
uv pip install --exclude-newer ${EXCLUDE_NEWER_DATE} ${EDITABLE} ".[dev]"
uv pip install --resolution=lowest-direct --exclude-newer ${EXCLUDE_NEWER_DATE} --reinstall ${EDITABLE} ".[minimal]"
uv pip install --exclude-newer ${EXCLUDE_NEWER_DATE} --reinstall "setuptools<50"
uv sync --python=${MINIMAL_PYTHON_VERSION} --resolution=lowest-direct --exclude-newer ${EXCLUDE_NEWER_DATE} ${NO_EDITABLE} --group dev --extra minimal --upgrade-group dev

# Install package with minimal dependencies including extras.
# See install-minimal for explanation of flags.
# We do not use --all-extras because it includes the dev dependencies for which we don't
# want to install the minimal versions.
# Install selected extras separately so their minimal versions can be tested.
# Development dependencies are installed from the dev dependency group.
.PHONY: install-minimal-extras
install-minimal-extras:
uv pip install --exclude-newer ${EXCLUDE_NEWER_DATE} ${EDITABLE} ".[dev]"
uv pip install --resolution=lowest-direct --exclude-newer ${EXCLUDE_NEWER_DATE} --reinstall ${EDITABLE} ".[matplotlib,minimal,timm,video]" --requirement pyproject.toml
uv sync --python=${MINIMAL_PYTHON_VERSION} --resolution=lowest-direct --exclude-newer ${EXCLUDE_NEWER_DATE} ${NO_EDITABLE} --group dev --extra matplotlib --extra minimal --extra timm --extra video --upgrade-group dev
Comment thread
gabrielfruet marked this conversation as resolved.
Outdated
uv pip install --exclude-newer ${EXCLUDE_NEWER_DATE} --reinstall "setuptools<50"

# Install package with dependencies pinned to the latest compatible version available at
Expand All @@ -189,7 +189,7 @@ install-pinned-extras:
# available at EXCLUDE_NEWER_DATE. This excludes video dependencies.
.PHONY: install-pinned-extras-no-video
install-pinned-extras-no-video:
uv pip install --exclude-newer ${EXCLUDE_NEWER_DATE} --reinstall ${EDITABLE} ".[dev,matplotlib,minimal,timm]" --requirement pyproject.toml
uv sync --python ${MAXIMAL_PYTHON_VERSION} --exclude-newer ${EXCLUDE_NEWER_DATE} ${NO_EDITABLE} --group dev --extra matplotlib --extra minimal --extra timm --upgrade-group dev

# Install package with pinned extras for notebook CI checks.
.PHONY: install-pinned-notebook
Expand All @@ -213,12 +213,12 @@ install-pinned-extras-3.12: install-av-system-deps install-pinned-extras
# Install package with the latest dependencies.
.PHONY: install-latest
install-latest:
uv pip install --upgrade --reinstall ${EDITABLE} . --all-extras --requirement pyproject.toml
uv sync --python=${MAXIMAL_PYTHON_VERSION} --upgrade --reinstall ${NO_EDITABLE} --group dev --all-extras


# Generate Notebooks from examples
.PHONY: generate-example-notebooks
generate-example-notebooks:
python examples/create_example_nbs.py examples/pytorch examples/notebooks/pytorch
python examples/create_example_nbs.py examples/pytorch_lightning examples/notebooks/pytorch_lightning
python examples/create_example_nbs.py examples/pytorch_lightning_distributed examples/notebooks/pytorch_lightning_distributed
uv run --frozen python examples/create_example_nbs.py examples/pytorch examples/notebooks/pytorch
uv run --frozen python examples/create_example_nbs.py examples/pytorch_lightning examples/notebooks/pytorch_lightning
uv run --frozen python examples/create_example_nbs.py examples/pytorch_lightning_distributed examples/notebooks/pytorch_lightning_distributed
4 changes: 3 additions & 1 deletion lightly/loss/ibot_loss.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from typing import cast

import torch
from torch import Tensor
from torch.nn import Module
Expand Down Expand Up @@ -105,7 +107,7 @@ def forward(

self.center.update(teacher_out)

return loss
return cast(Tensor, loss)


class IBOTPlusPlusPatchLoss(IBOTPatchLoss):
Expand Down
2 changes: 1 addition & 1 deletion lightly/models/modules/masked_vision_transformer_timm.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def forward(
attn_pool = getattr(self.vit, "attn_pool", None)
if attn_pool is not None:
x = attn_pool(x)
return x
return cast(Tensor, x)

global_pool = cast(str, getattr(self.vit, "global_pool", ""))
if global_pool == "avg":
Expand Down
4 changes: 2 additions & 2 deletions lightly/models/resnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# All Rights Reserved
from __future__ import annotations

from typing import List
from typing import List, cast

import torch.nn as nn
import torch.nn.functional as F
Expand Down Expand Up @@ -241,7 +241,7 @@ def forward(self, x: Tensor) -> Tensor:
out = F.avg_pool2d(out, 4)
out = out.view(out.size(0), -1)
out = self.linear(out)
return out
return cast(Tensor, out)


def ResNetGenerator(
Expand Down
10 changes: 7 additions & 3 deletions lightly/transforms/gaussian_blur.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) 2020. Lightly AG and its affiliates.
# All Rights Reserved

from typing import Optional, Tuple, Union
from typing import Optional, Tuple, Union, cast
from warnings import warn

import numpy as np
Expand Down Expand Up @@ -64,8 +64,12 @@ def __call__(self, sample: Union[Tensor, Image]) -> Union[Tensor, Image]:
prob = np.random.random_sample()

# Convert to PIL image if it's a tensor, otherwise use as is
is_input_tensor = isinstance(sample, Tensor)
sample_pil: Image = F.to_pil_image(sample) if is_input_tensor else sample
if isinstance(sample, Tensor):
is_input_tensor = True
sample_pil = cast(Image, F.to_pil_image(sample))
else:
is_input_tensor = False
sample_pil = sample

if prob < self.prob:
# choose randomized std for Gaussian filtering
Expand Down
55 changes: 32 additions & 23 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,35 @@ dependencies = [
"pydantic>=1.10.5",
"pytorch_lightning>=1.0.4",
"urllib3>=1.25.3",
"aenum>=3.1.11"
"aenum>=3.1.11",
]
dynamic = ["version", "readme"]

[project.optional-dependencies]
all = [
"lightly[dev,matplotlib,minimal,timm,video]"
"lightly[matplotlib,minimal,timm,video]"
]

# Minimal dependencies against which we test. Older versions might work depending on the
# functionality used.
minimal = [
"torch>=1.10.0",
"torchvision>=0.11.0",
"pytorch_lightning>=1.6",
]
openapi = [
"python_dateutil>=2.5.3",
"setuptools>=21.0.0",
"urllib3>=1.25.3",
"pydantic>=1.10.5",
"aenum>=3.1.11"
]
timm = ["timm>=0.9.9"]
video = ["av>=8.0.3; python_version>='3.8'"]
matplotlib = ["matplotlib>=3"]


[dependency-groups]
dev = [
"sphinx",
"pytest",
Expand All @@ -78,34 +99,22 @@ dev = [
"pandas",
"toml",
"torchmetrics",
# ruff and mypy should be the same version as defined in .pre-commit-config.yaml
"ruff==0.12.7", # frozen version to avoid differences between CI and local dev machines
"mypy==1.4.1", # frozen version to avoid differences between CI and local dev machines
# Ruff should match the version defined in .pre-commit-config.yaml.
"ruff==0.12.7",
"mypy>=1.4.1",
"types-python-dateutil",
"types-toml",
"types-requests",
"types-PyYAML",
"nbformat",
"jupytext"
]
# Minimal dependencies against which we test. Older versions might work depending on the
# functionality used.
minimal = [
"torch>=1.10.0",
"torchvision>=0.11.0",
"pytorch_lightning>=1.6",
"jupytext",
]
openapi = [
"python_dateutil>=2.5.3",
"setuptools>=21.0.0",
"urllib3>=1.25.3",
"pydantic>=1.10.5",
"aenum>=3.1.11"
]
timm = ["timm>=0.9.9"]
video = ["av>=8.0.3; python_version>='3.8'"]
matplotlib = ["matplotlib>=3"]

[tool.uv]
environments = ["python_full_version >= '3.8'"]

[tool.uv.dependency-groups]
dev = { requires-python = ">=3.8" }

[project.urls]
"Homepage" = "https://www.lightly.ai"
Expand Down
Loading