diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bfa87a4..b6fcefa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,6 +61,52 @@ We follow scientific Python coding standards to maintain consistency: - Use snake_case for functions/variables, CamelCase for classes - Use double quotes for strings +## Dependency Management + +EthoPy pins dependencies in two layers, and they do different jobs. + +### `pyproject.toml` + +Declares the core dependencies with loose ranges. Upper bounds are added only where a package has a track record of breaking releases, currently `datajoint`, `setuptools`, `numpy`, `pandas` and `scipy`. Do not add a cap to every dependency. Blanket caps cause resolution conflicts for anyone installing EthoPy alongside other packages, and each cap needs a release to lift. + +When adding a new core dependency, add it without an upper bound unless you have evidence of a breakage, and refresh the lock file in the same pull request. + +### `requirements-lock.txt` + +One pinned version per package for the entire tree, including transitive dependencies. It is a single cross-platform file: environment markers cover the few packages that differ by operating system or Python version, so there is no per-OS variant to keep in sync. + +Versions are chosen as the newest one actually running on a verified EthoPy machine that still supports the whole declared Python range, with the resolver enforcing mutual consistency. The point is to record what actually ran, not what a resolver believes should work. + +Hardware and analysis packages are deliberately excluded. Every such import in EthoPy is lazy, so the core runs without them, and they are installed per machine. + +To refresh it: + +1. On each machine you care about, build a fresh virtual environment, install EthoPy without + the lock file, run the test suite and a real experimental session, then capture + `pip freeze --exclude-editable`. Do not skip the session; some breakages only appear at + runtime with hardware attached. +2. Regenerate the cross-platform pin set, capped at the versions you just verified: + ```bash + uv pip compile pyproject.toml --universal --python-version \ + --constraint -o requirements-lock.txt + ``` +3. Confirm it still resolves on every target before committing: + ```bash + uv pip compile requirements-lock.txt --python-version 3.9.2 \ + --python-platform aarch64-unknown-linux-gnu + ``` + Repeat for each Python version and architecture you support. Any version drift means the + pins are inconsistent. +4. Update the header comment with the source machines, Python versions and date. + +### If nobody refreshes this + +The lock file will age. Installs will keep working, reproducing an increasingly old +environment, and EthoPy will fall behind the ecosystem. This is the intended failure mode and +it is preferred to the alternative. An install that reproduces a two-year-old working +environment is still a working install; an install that silently picks up an untested major +release is not. + ## Testing Guidelines Ethopy has specific testing requirements due to its database connections: diff --git a/README.md b/README.md index 84feff2..fc98b7a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ EthoPy provides a flexible, state-based system for designing and running behavio ### Requirements -- Python 3.8 or higher +- Python 3.9 to 3.11 - Maria DB Database - EthoPy requires database setup before running experiments. Follow our step-by-step guide: **[Getting Started Guide](https://ef-lab.github.io/ethopy_package/getting_started/)** for a complete setup from installation to first experiment diff --git a/docs/contributing.md b/docs/contributing.md index 145c133..07f21eb 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -97,6 +97,42 @@ Ready to contribute? Here's how to set up Ethopy for local development: 9. Submit a pull request through the GitHub website. +## Dependency Management + +EthoPy constrains dependencies in two layers, and they do different jobs. + +### `pyproject.toml` + +Declares the core dependencies with loose ranges. Upper bounds are added only where a package has a track record of breaking releases, currently `datajoint`, `setuptools`, `numpy`, `pandas` and `scipy`. Do not cap every dependency. Blanket caps cause resolution conflicts for anyone installing EthoPy alongside other packages, and each one needs a release to lift. + +When adding a new core dependency, add it without an upper bound unless you have evidence of a breakage, and refresh the lock file in the same pull request. + +### `requirements-lock.txt` + +One pinned version per package for the entire tree, including transitive dependencies, and what [Installation](installation.md) points every machine at. It is a single cross-platform file: environment markers cover the few packages that differ by operating system or Python version, so there is no per-OS variant to keep in sync. + +Versions are chosen as the newest one actually running on a verified EthoPy machine that still supports the whole declared Python range, with the resolver enforcing mutual consistency. Hardware and analysis packages are excluded, since every such import in EthoPy is lazy and they are installed per machine. + +To refresh it: + +1. On each machine you care about, build a fresh virtual environment, install EthoPy without the lock file, run the test suite and a real experimental session, then capture `pip freeze --exclude-editable`. Do not skip the session, some breakages only appear at runtime with hardware attached. +2. Regenerate the cross-platform pin set, capped at the versions you just verified: + ```bash + uv pip compile pyproject.toml --universal --python-version \ + --constraint -o requirements-lock.txt + ``` +3. Confirm it still resolves on every target before committing: + ```bash + uv pip compile requirements-lock.txt --python-version 3.9.2 \ + --python-platform aarch64-unknown-linux-gnu + ``` + Repeat for each Python version and architecture you support. Any version drift means the pins are inconsistent. +4. Update the header comment with the source machines, Python versions and date. + +### If nobody refreshes this + +The lock file will age. Installs keep working, reproducing an increasingly old environment, and EthoPy falls behind the ecosystem. This is the intended failure mode and it is preferred to the alternative. An install that reproduces a two-year-old working environment is still a working install. An install that silently picks up an untested major release is not. + ## Pull Request Guidelines Before you submit a pull request, check that it meets these guidelines: @@ -105,6 +141,5 @@ Before you submit a pull request, check that it meets these guidelines: 2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.md -3. The pull request should work for Python 3.8 and later, and - for PyPy. Check and make sure that the tests pass for all - supported Python versions. +3. The pull request should work for Python 3.9 through 3.11, the range declared by + `requires-python`. Check and make sure that the tests pass for all supported Python versions. diff --git a/docs/getting_started.md b/docs/getting_started.md index c0c1f96..bdba5aa 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -6,7 +6,7 @@ This guide will walk you through the process of setting up and running your firs Before starting, ensure you have: -- Python 3.8 or higher (but less than 3.12) installed +- Python 3.9 or higher (but less than 3.12) installed - MariaDB database !!! tip Database setup We recommend using [Docker](https://www.docker.com/blog/getting-started-with-docker-desktop/) for setting up a new database. diff --git a/docs/index.md b/docs/index.md index 5cf90cb..721db49 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,7 +13,7 @@ EthoPy provides a flexible, state-based system for designing and running behavio ### Requirements -- Python 3.8 or higher +- Python 3.9 to 3.11 - Maria DB Database - EthoPy requires database setup before running experiments. Follow our step-by-step guide **[Getting Started](https://ef-lab.github.io/ethopy_package/getting_started/)** for a complete setup from installation to first experiment diff --git a/docs/installation.md b/docs/installation.md index be2ac41..634b86d 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -2,7 +2,7 @@ ## Requirements -- Python >=3.8,<3.12 +- Python >=3.9,<3.12 - Docker (for database setup) - Dependencies: numpy, pandas, datajoint, pygame, pillow, and more (automatically installed) @@ -16,16 +16,38 @@ To install EthoPy with basic features, run: pip install ethopy ``` -This is the preferred method as it will install the most recent stable release. +This resolves the most recent versions allowed by EthoPy's declared dependency ranges. Those versions may be newer than anything that has been tested on a rig. + +### Reproducible Installation (recommended for rigs) + +`requirements-lock.txt` in the repository records the exact versions of every package, including transitive dependencies, from a Raspberry Pi rig that is known to run correctly. Install that environment first, then install EthoPy without letting it re-resolve: + +```bash +pip install -r https://raw.githubusercontent.com/ef-lab/ethopy_package/main/requirements-lock.txt +pip install --no-deps ethopy +``` + +This works on every supported platform. The lock file uses environment markers rather than being tied to one operating system, and is verified to install from wheels on Python 3.9.2, 3.10 and 3.11, on both aarch64 (Raspberry Pi) and x86_64 (Linux, macOS, Windows). No pin requires a compiler. + +The locked versions are refreshed by hand, so they will lag behind the newest releases. That is intentional. See [Dependency Management](contributing.md#dependency-management) for the refresh procedure. + +#### Per-machine packages + +The lock file covers only EthoPy's core dependencies. Hardware and analysis packages are excluded, because every one of them is a lazy import and the core runs without them. Install them on top of the lock file, only where they are needed: + +```bash +# Raspberry Pi rigs +pip install RPi.GPIO pigpio pyserial picamera2 opencv-python + +# DeepLabCut setups, such as the openfield workstation +pip install deeplabcut-live scikit-video +``` ### Optional Features For additional functionality: ```bash -# For 3D object support -pip install "ethopy[obj]" - # For development pip install "ethopy[dev]" diff --git a/docs/raspberry_pi.md b/docs/raspberry_pi.md index 4c3b82f..08a218a 100644 --- a/docs/raspberry_pi.md +++ b/docs/raspberry_pi.md @@ -28,7 +28,7 @@ Once your Raspberry Pi is set up, you can connect to it from your computer's ter python --version ``` -EthoPy requires Python >=3.8, < 3.12 +EthoPy requires Python >=3.9, < 3.12 2. Setting Up a Virtual Environment diff --git a/pyproject.toml b/pyproject.toml index a38e69b..76ca5fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "ethopy" dynamic = ["version"] description = "State control system for automated, high-throughput behavioral training." readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9,<3.12" license = {text = "MIT License"} authors = [ {name = "Emmanouil Froudarakis", email = "frouman@imbb.forth.gr"}, @@ -14,7 +14,6 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -24,12 +23,12 @@ dependencies = [ "click>=8.1.0", "datajoint<2.0.0", "imageio", - "pandas>=2.0.0", + "pandas>=2.0.0,<4", "h5py", "pygame", "pygame-menu", - "numpy", - "scipy", + "numpy<3", + "scipy<2", "pyopengl", "imageio-ffmpeg", "pyfiglet", @@ -79,7 +78,7 @@ packages = ["src/ethopy"] only-include = ["src/ethopy"] [tool.mypy] -python_version = "3.8" +python_version = "3.9" disallow_untyped_defs = true ignore_missing_imports = true disable_error_code = ["attr-defined", "misc"] diff --git a/requirements-lock.txt b/requirements-lock.txt new file mode 100644 index 0000000..0720ea0 --- /dev/null +++ b/requirements-lock.txt @@ -0,0 +1,126 @@ +# EthoPy locked environment +# +# One pinned version per package, covering EthoPy's full dependency tree. +# Environment markers (the "; ..." clauses) handle the few packages that differ +# by operating system or Python version, so this stays a single file. +# +# VERIFIED TO RESOLVE AND INSTALL FROM WHEELS ON +# Python 3.9.2+, 3.10, 3.11 +# aarch64 (Raspberry Pi) and x86_64 (Linux, macOS, Windows) +# All six Linux combinations were checked with no version drift. Every pin has a +# wheel; nothing needs a compiler. +# +# HOW THESE VERSIONS WERE CHOSEN +# Each is the newest version actually running on a verified EthoPy machine that +# still supports the whole Python range, with the resolver enforcing mutual +# consistency. 80 of the 87 pins are running in the fleet today; the rest are +# marked below. +# +# sources : ef-rp163, ef-rp160 (Raspberry Pi 4, Python 3.9.2, aarch64) +# openfield (workstation, Python 3.10.12, x86_64) +# date : 2026-09-03 +# +# INSTALL +# pip install -r requirements-lock.txt +# pip install --no-deps ethopy +# +# PYTHON 3.9 IS THE BINDING CONSTRAINT +# click, h5py, imageio, matplotlib and scikit-learn are held below the versions +# on the openfield workstation because their newer releases require Python 3.10. +# If the Raspberry Pi rigs move off Python 3.9, this file can advance a long way. +# +# NOT INCLUDED: PER-MACHINE PACKAGES +# Every hardware and analysis import in EthoPy is lazy, so the core runs without +# these. Install them on top of this file, only on the machines that need them: +# +# Raspberry Pi : RPi.GPIO pigpio pyserial picamera2 opencv-python +# DeepLabCut rig : deeplabcut-live scikit-video +# +# Refreshing this file is a manual step. See CONTRIBUTING.md. +appdirs==1.4.4 +appnope==1.0.0 ; sys_platform == 'darwin' # not in the fleet; resolver choice +argon2-cffi==25.1.0 # runs on openfield +argon2-cffi-bindings==25.1.0 # runs on openfield +asttokens==3.0.1 # runs on openfield +attrs==26.1.0 +backcall==0.2.0 # runs on rp163, rp160 +blinker==1.9.0 # runs on openfield +certifi==2026.4.22 # runs on openfield +cffi==2.0.0 # runs on openfield +click==8.1.8 # runs on rp163, rp160 +colorama==0.4.6 ; sys_platform == 'win32' # runs on openfield +contourpy==1.1.0 # runs on rp163, rp160 +cryptography==48.0.0 # runs on openfield +cycler==0.12.1 # runs on openfield +datajoint==0.14.9 # runs on openfield +decorator==5.2.1 # runs on openfield +deepdiff==8.6.2 # not in the fleet; resolver choice +exceptiongroup==1.3.1 ; python_full_version < '3.11' # runs on openfield +executing==2.2.1 # runs on openfield +faker==37.12.0 # not in the fleet; resolver choice +flask==3.1.3 # runs on openfield +fonttools==4.42.1 # runs on rp163, rp160 +h5py==3.14.0 # runs on rp163 +hdmf==4.3.1 +imageio==2.31.3 # runs on rp163, rp160 +imageio-ffmpeg==0.6.0 # runs on openfield +importlib-metadata==8.4.0 ; python_full_version < '3.10' # not in the fleet; resolver choice +importlib-resources==6.0.1 ; python_full_version < '3.10' # runs on rp163, rp160 +ipython==8.15.0 # runs on rp163, rp160 +itsdangerous==2.2.0 # runs on openfield +jedi==0.18.0 # runs on rp163, rp160 +jinja2==3.1.6 # runs on openfield +joblib==1.5.3 # runs on openfield +jsonschema==4.25.1 # runs on rp163, rp160 +jsonschema-specifications==2025.9.1 +kiwisolver==1.4.5 # runs on rp163, rp160 +markupsafe==3.0.3 # runs on openfield +matplotlib==3.7.2 # runs on rp163, rp160 +matplotlib-inline==0.2.2 # runs on openfield +minio==7.2.20 # runs on openfield +networkx==2.6.3 # runs on rp163, rp160 +numpy==1.26.4 # runs on openfield +orderly-set==5.5.0 # runs on openfield +otumat==0.3.1 +packaging==26.2 # runs on openfield +pandas==2.3.3 # runs on openfield +parso==0.8.7 # runs on openfield +pexpect==4.9.0 ; sys_platform != 'win32' # runs on openfield +pickleshare==0.7.5 # runs on rp163, rp160 +pillow==10.0.0 # runs on rp163, rp160 +platformdirs==4.4.0 # runs on rp163, rp160 +prompt-toolkit==3.0.52 # runs on openfield +ptyprocess==0.7.0 ; sys_platform != 'win32' # runs on openfield +pure-eval==0.2.3 # runs on openfield +pycparser==2.22 ; implementation_name != 'PyPy' # runs on rp160 +pycryptodome==3.23.0 # runs on openfield +pydot==4.0.0 # not in the fleet; resolver choice +pyfiglet==1.0.4 # runs on rp163, openfield +pygame==2.6.1 # runs on openfield +pygame-menu==4.5.2 # runs on openfield +pygments==2.20.0 # runs on openfield +pymysql==1.1.3 # runs on openfield +pynwb==3.1.3 +pyopengl==3.1.10 # runs on openfield +pyparsing==3.0.9 # runs on rp163, rp160 +pyperclip==1.11.0 # runs on openfield +python-dateutil==2.9.0.post0 # runs on openfield +pytz==2026.2 # runs on openfield +referencing==0.36.2 # runs on rp163, rp160 +rpds-py==0.27.1 # runs on rp163, rp160 +ruamel-yaml==0.19.1 +scikit-learn==1.4.2 # runs on rp163, rp160 +scipy==1.13.1 # not in the fleet; resolver choice +setuptools==81.0.0 # not in the fleet; resolver choice +six==1.17.0 # runs on openfield +stack-data==0.6.3 # runs on openfield +threadpoolctl==3.6.0 # runs on openfield +tqdm==4.67.3 # runs on openfield +traitlets==5.15.0 # runs on openfield +typing-extensions==4.15.0 # runs on rp163, openfield +tzdata==2026.2 # runs on openfield +urllib3==1.26.5 # runs on rp163, rp160 +watchdog==6.0.0 # runs on openfield +wcwidth==0.7.0 # runs on openfield +werkzeug==3.1.8 # runs on openfield +zipp==3.16.2 ; python_full_version < '3.10' # runs on rp163, rp160