Simplify setup.py and tests (#2336)

This commit is contained in:
Cuong Duong 2023-01-19 21:52:19 +11:00 committed by GitHub
parent 8b3d09caf7
commit 2f84620a28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 123 additions and 159 deletions

View file

@ -9,37 +9,40 @@ on:
jobs:
build-and-test-python:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [3.7, 3.8]
python-version: ["3.8"]
os: ["macos-latest", "ubuntu-latest", "windows-latest"]
fail-fast: false
steps:
- name: "Get OS version (Linux)"
if: startsWith(runner.os, 'Linux')
- name: "Set environment variables (Windows)"
if: startsWith(runner.os, 'Windows')
shell: pwsh
run: |
echo "OS_VERSION=`lsb_release -sr`" >> $GITHUB_ENV
echo "PIP_DEFAULT_CACHE=$HOME/.cache/pip" >> $GITHUB_ENV
echo "DEFAULT_HOME=$HOME" >> $GITHUB_ENV
- uses: actions/checkout@v2
(Get-ItemProperty "HKLM:System\CurrentControlSet\Control\FileSystem").LongPathsEnabled
$os_version = (Get-CimInstance Win32_OperatingSystem).version
Echo "OS_VERSION=$os_version" >> $env:GITHUB_ENV
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: "Restore pip cache"
id: cache-pip
uses: actions/cache@v2
cache: pip
cache-dependency-path: "**/python/pyproject.toml"
- name: "Restore RTools40"
if: startsWith(runner.os, 'Windows')
id: cache-rtools
uses: actions/cache@v3
with:
path: ${{ env.PIP_DEFAULT_CACHE }}
key: ${{ runner.os }}-pip-${{ hashFiles('**/python/requirements.txt') }}-v1
restore-keys: |
${{ runner.os }}-pip-
path: C:/rtools40
key: ${{ runner.os }}-${{ env.OS_VERSION }}-rtools-v1
- name: Install and test
run: |
pip install -U -r python/requirements.txt dask[dataframe] distributed
cd python
python setup.py develop test
python -m pip install -U --editable ".[dev,parallel]"
python -m pytest prophet/tests/
build-and-test-r:
@ -55,7 +58,7 @@ jobs:
RSPM: ${{ matrix.config.rspm }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up R
uses: r-lib/actions/setup-r@v2
with:
@ -71,7 +74,7 @@ jobs:
shell: Rscript {0}
- name: Restore R package cache
if: runner.os != 'Windows'
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ${{ env.R_LIBS_USER }}
key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }}

View file

@ -2,17 +2,12 @@ FROM python:3.7-stretch
RUN apt-get -y install libc-dev
RUN pip install pip==19.1.1
COPY python/requirements.txt .
RUN pip install -r requirements.txt
RUN pip install ipython==7.5.0
RUN pip install pip==22.3.1
COPY . .
WORKDIR python
RUN python setup.py install
RUN python -m pip install -e \".[dev, parallel]\"
WORKDIR /

View file

@ -88,14 +88,13 @@ To get the latest code changes as they are merged, you can clone this repo and b
```bash
git clone https://github.com/facebook/prophet.git
cd prophet/python
python -m pip install -r requirements.txt
python setup.py develop
python -m pip install -e .
```
By default, Prophet will use a fixed version of `cmdstan` (downloading and installing it if necessary) to compile the model executables. If this is undesired and you would like to use your own existing `cmdstan` installation, you can set the environment variable `PROPHET_REPACKAGE_CMDSTAN` to `False`:
```bash
export PROPHET_REPACKAGE_CMDSTAN=False; python setup.py develop
export PROPHET_REPACKAGE_CMDSTAN=False; python -m pip install -e .
```
### Linux

View file

@ -46,12 +46,10 @@ $ cd python
# with Anaconda
$ conda create -n prophet
$ conda activate prophet
$ pip install -r requirements.txt
# with venv
$ python3 -m venv prophet
$ source prophet/bin/activate
$ pip install -r requirements.txt
```
### R
@ -86,7 +84,7 @@ The next step is to build and install the development version of prophet in the
### Python
```bash
$ python setup.py develop
$ python -m pip install -e ".[dev,parallel]"
```
You should be able to import *prophet* from your locally built version:
@ -95,7 +93,7 @@ You should be able to import *prophet* from your locally built version:
$ python # start an interpreter
>>> import prophet
>>> prophet.__version__
'1.0' # whatever the current github version is
'1.1.2' # whatever the current github version is
```
This will create the new environment, and not touch any of your existing environments,
@ -154,11 +152,11 @@ Adding tests is one of the most common requests after code is pushed to prophet.
### Python
Prophet uses the ``unittest`` package for running tests in Python and ``testthat`` package for testing in R. All tests should go into the tests subdirectory in either the Python or R folders.
Prophet uses the ``pytest`` package for running tests in Python and ``testthat`` package for testing in R. All tests should go into the tests subdirectory in either the Python or R folders.
The entire test suite can be run by typing:
```bash
$ python setup.py test
$ python -m pytest prophet/tests/
```
### R

View file

@ -1,6 +1,6 @@
include stan/*.stan
include LICENSE
include requirements.txt
include pyproject.toml
# Ensure in-place built models do not get included in the source dist.
prune prophet/stan_model

View file

@ -30,7 +30,7 @@ See [Installation in Python - Development version](https://github.com/facebook/p
Simply type `make build` and if everything is fine you should be able to `make shell` or alternative jump directly to `make py-shell`.
To run the tests, inside the container `cd python/prophet` and then `python -m unittest`
To run the tests, inside the container `cd python/prophet` and then `python -m pytest prophet/tests/`
### Example usage

View file

@ -1,7 +1 @@
__title__ = "prophet"
__description__ = "Automatic Forecasting Procedure"
__url__ = "https://facebook.github.io/prophet/"
__version__ = "1.1.1"
__author__ = "Sean J. Taylor <sjtz@pm.me>, Ben Letham <bletham@fb.com>"
__author_email__ = "sjtz@pm.me"
__license__ = "MIT"
__version__ = "1.1.2"

View file

@ -0,0 +1,18 @@
import pytest
def pytest_configure(config):
config.addinivalue_line("markers", "slow: mark tests as slow (include in run with --test-slow)")
def pytest_addoption(parser):
parser.addoption("--test-slow", action="store_true", default=False, help="Run slow tests")
def pytest_collection_modifyitems(config, items):
if config.getoption("--test-slow"):
return
skip_slow = pytest.mark.skip(reason="Skipped due to the lack of '--test-slow' argument")
for item in items:
if "slow" in item.keywords:
item.add_marker(skip_slow)

View file

@ -9,11 +9,11 @@ from __future__ import print_function
from __future__ import unicode_literals
import os
import sys
from unittest import TestCase, skipUnless
from unittest import TestCase
import numpy as np
import pandas as pd
import pytest
from prophet import Prophet
from prophet.utilities import warm_start_params
@ -66,7 +66,7 @@ class TestProphet(TestCase):
res = self.rmse(future['yhat'], test['y'])
self.assertAlmostEqual(res, 23.44, places=2, msg="backend: {}".format(forecaster.stan_backend))
@skipUnless("--test-slow" in sys.argv, "Skipped due to the lack of '--test-slow' argument")
@pytest.mark.slow
def test_fit_sampling_predict(self):
days = 30
N = DATA.shape[0]
@ -106,7 +106,7 @@ class TestProphet(TestCase):
forecaster.fit(train)
forecaster.predict(future)
@skipUnless("--test-slow" in sys.argv, "Skipped due to the lack of '--test-slow' argument")
@pytest.mark.slow
def test_fit_predict_no_changepoints_mcmc(self):
N = DATA.shape[0]
train = DATA.head(N // 2)

View file

@ -1,7 +1,61 @@
[build-system]
requires = [
"setuptools>=42",
"wheel",
"cmdstanpy>=1.0.4",
"setuptools>=64",
"wheel",
"cmdstanpy>=1.0.4",
]
build-backend = "setuptools.build_meta"
[project]
name = "prophet"
dynamic = ["version"]
description = "Automatic Forecasting Procedure"
readme = "README.md"
requires-python = ">=3.7"
dependencies = [
"cmdstanpy>=1.0.4",
"numpy>=1.15.4",
"matplotlib>=2.0.0",
"pandas>=1.0.4",
"LunarCalendar>=0.0.9",
"convertdate>=2.1.2",
"holidays>=0.14.2",
"python-dateutil>=2.8.0",
"tqdm>=4.36.1",
]
authors = [
{name = "Sean J. Taylor", email = "sjtz@pm.me"},
{name = "Ben Letham", email = "bletham@fb.com"},
]
maintainers = [
{name = "Cuong Duong", email = "cuong.duong242@gmail.com"},
]
license = {text = "MIT"}
classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]
[project.optional-dependencies]
dev = [
"setuptools>=64",
"wheel",
"pytest",
"jupyterlab",
"nbconvert",
"plotly",
]
parallel = [
"dask[dataframe]",
"distributed",
]
[project.urls]
homepage = "https://facebook.github.io/prophet/"
documentation = "https://facebook.github.io/prophet/"
repository = "https://github.com/facebook/prophet"

View file

@ -1,12 +0,0 @@
cmdstanpy>=1.0.4
numpy>=1.15.4
pandas>=1.0.4
matplotlib>=2.0.0
LunarCalendar>=0.0.9
convertdate>=2.1.2
holidays>=0.14.2
setuptools>=42
setuptools-git>=1.2
python-dateutil>=2.8.0
tqdm>=4.36.1
wheel>=0.37.0

View file

@ -4,18 +4,15 @@
# LICENSE file in the root directory of this source tree.
import os
import sys
import platform
from pathlib import Path
from shutil import copy, copytree, rmtree
from typing import List
from pkg_resources import add_activation_listener, normalize_path, require, working_set
from setuptools import find_packages, setup, Extension
from setuptools.command.build_ext import build_ext
from setuptools.command.build_py import build_py
from setuptools.command.develop import develop
from setuptools.command.test import test as test_command
from setuptools.command.editable_wheel import editable_wheel
MODEL_DIR = "stan"
@ -52,6 +49,7 @@ def prune_cmdstan(cmdstan_dir: str) -> None:
rmtree(original_dir)
temp_dir.rename(original_dir)
def repackage_cmdstan():
return os.environ.get("PROPHET_REPACKAGE_CMDSTAN", "").lower() not in ["false", "0"]
@ -59,6 +57,7 @@ def repackage_cmdstan():
def maybe_install_cmdstan_toolchain():
"""Install C++ compilers required to build stan models on Windows machines."""
import cmdstanpy
try:
cmdstanpy.utils.cxx_toolchain_path()
except Exception:
@ -122,7 +121,6 @@ def build_cmdstan_model(target_dir):
prune_cmdstan(cmdstan_dir)
def get_backends_from_env() -> List[str]:
return os.environ.get("STAN_BACKEND", "CMDSTANPY").split(",")
@ -131,9 +129,10 @@ def build_models(target_dir):
print("Compiling cmdstanpy model")
build_cmdstan_model(target_dir)
if 'PYSTAN' in get_backends_from_env():
if "PYSTAN" in get_backends_from_env():
raise ValueError("PyStan backend is not supported for Prophet >= 1.1")
class BuildPyCommand(build_py):
"""Custom build command to pre-compile Stan models."""
@ -153,117 +152,33 @@ class BuildExtCommand(build_ext):
pass
class DevelopCommand(develop):
class EditableWheel(editable_wheel):
"""Custom develop command to pre-compile Stan models in-place."""
def run(self):
if not self.dry_run:
target_dir = os.path.join(self.setup_path, MODEL_TARGET_DIR)
target_dir = os.path.join(self.project_dir, MODEL_TARGET_DIR)
self.mkpath(target_dir)
build_models(target_dir)
develop.run(self)
editable_wheel.run(self)
class TestCommand(test_command):
user_options = [
("test-module=", "m", "Run 'test_suite' in specified module"),
(
"test-suite=",
"s",
"Run single test, case or suite (e.g. 'module.test_suite')",
),
("test-runner=", "r", "Test runner to use"),
("test-slow", "w", "Test slow suites (default off)"),
]
test_slow = None
def initialize_options(self):
super(TestCommand, self).initialize_options()
self.test_slow = False
def finalize_options(self):
super(TestCommand, self).finalize_options()
if self.test_slow is None:
self.test_slow = getattr(self.distribution, "test_slow", False)
"""We must run tests on the build directory, not source."""
def with_project_on_sys_path(self, func):
# Ensure metadata is up-to-date
self.reinitialize_command("build_py", inplace=0)
self.run_command("build_py")
bpy_cmd = self.get_finalized_command("build_py")
build_path = normalize_path(bpy_cmd.build_lib)
# Build extensions
self.reinitialize_command("egg_info", egg_base=build_path)
self.run_command("egg_info")
self.reinitialize_command("build_ext", inplace=0)
self.run_command("build_ext")
ei_cmd = self.get_finalized_command("egg_info")
old_path = sys.path[:]
old_modules = sys.modules.copy()
try:
sys.path.insert(0, normalize_path(ei_cmd.egg_base))
working_set.__init__()
add_activation_listener(lambda dist: dist.activate())
require("%s==%s" % (ei_cmd.egg_name, ei_cmd.egg_version))
func()
finally:
sys.path[:] = old_path
sys.modules.clear()
sys.modules.update(old_modules)
working_set.__init__()
with open("README.md", "r", encoding="utf-8") as f:
long_description = f.read()
with open("requirements.txt", "r") as f:
install_requires = f.read().splitlines()
about = {}
here = Path(__file__).parent.resolve()
with open(here / "prophet" / "__version__.py", "r") as f:
with open(here / "prophet" / "__version__.py", "r") as f:
exec(f.read(), about)
setup(
name=about["__title__"],
version=about["__version__"],
description=about["__description__"],
url=about["__url__"],
project_urls={
"Source": "https://github.com/facebook/prophet",
},
author=about["__author__"],
author_email=about["__author_email__"],
license=about["__license__"],
packages=find_packages(),
install_requires=install_requires,
python_requires=">=3.7",
zip_safe=False,
include_package_data=True,
ext_modules=[Extension("prophet.stan_model", [])],
cmdclass={
"build_ext": BuildExtCommand,
"build_py": BuildPyCommand,
"develop": DevelopCommand,
"test": TestCommand,
"editable_wheel": EditableWheel,
},
test_suite="prophet.tests",
classifiers=[
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
],
long_description=long_description,
long_description_content_type="text/markdown",
)