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

View file

@ -46,12 +46,10 @@ $ cd python
# with Anaconda # with Anaconda
$ conda create -n prophet $ conda create -n prophet
$ conda activate prophet $ conda activate prophet
$ pip install -r requirements.txt
# with venv # with venv
$ python3 -m venv prophet $ python3 -m venv prophet
$ source prophet/bin/activate $ source prophet/bin/activate
$ pip install -r requirements.txt
``` ```
### R ### R
@ -86,7 +84,7 @@ The next step is to build and install the development version of prophet in the
### Python ### Python
```bash ```bash
$ python setup.py develop $ python -m pip install -e ".[dev,parallel]"
``` ```
You should be able to import *prophet* from your locally built version: 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 $ python # start an interpreter
>>> import prophet >>> import prophet
>>> prophet.__version__ >>> 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, 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 ### 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: The entire test suite can be run by typing:
```bash ```bash
$ python setup.py test $ python -m pytest prophet/tests/
``` ```
### R ### R

View file

@ -1,6 +1,6 @@
include stan/*.stan include stan/*.stan
include LICENSE include LICENSE
include requirements.txt include pyproject.toml
# Ensure in-place built models do not get included in the source dist. # Ensure in-place built models do not get included in the source dist.
prune prophet/stan_model 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`. 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 ### Example usage

View file

@ -1,7 +1 @@
__title__ = "prophet" __version__ = "1.1.2"
__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"

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 from __future__ import unicode_literals
import os import os
import sys from unittest import TestCase
from unittest import TestCase, skipUnless
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import pytest
from prophet import Prophet from prophet import Prophet
from prophet.utilities import warm_start_params from prophet.utilities import warm_start_params
@ -66,7 +66,7 @@ class TestProphet(TestCase):
res = self.rmse(future['yhat'], test['y']) res = self.rmse(future['yhat'], test['y'])
self.assertAlmostEqual(res, 23.44, places=2, msg="backend: {}".format(forecaster.stan_backend)) 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): def test_fit_sampling_predict(self):
days = 30 days = 30
N = DATA.shape[0] N = DATA.shape[0]
@ -106,7 +106,7 @@ class TestProphet(TestCase):
forecaster.fit(train) forecaster.fit(train)
forecaster.predict(future) 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): def test_fit_predict_no_changepoints_mcmc(self):
N = DATA.shape[0] N = DATA.shape[0]
train = DATA.head(N // 2) train = DATA.head(N // 2)

View file

@ -1,7 +1,61 @@
[build-system] [build-system]
requires = [ requires = [
"setuptools>=42", "setuptools>=64",
"wheel", "wheel",
"cmdstanpy>=1.0.4", "cmdstanpy>=1.0.4",
] ]
build-backend = "setuptools.build_meta" 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. # LICENSE file in the root directory of this source tree.
import os import os
import sys
import platform import platform
from pathlib import Path from pathlib import Path
from shutil import copy, copytree, rmtree from shutil import copy, copytree, rmtree
from typing import List 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 import find_packages, setup, Extension
from setuptools.command.build_ext import build_ext from setuptools.command.build_ext import build_ext
from setuptools.command.build_py import build_py from setuptools.command.build_py import build_py
from setuptools.command.develop import develop from setuptools.command.editable_wheel import editable_wheel
from setuptools.command.test import test as test_command
MODEL_DIR = "stan" MODEL_DIR = "stan"
@ -52,6 +49,7 @@ def prune_cmdstan(cmdstan_dir: str) -> None:
rmtree(original_dir) rmtree(original_dir)
temp_dir.rename(original_dir) temp_dir.rename(original_dir)
def repackage_cmdstan(): def repackage_cmdstan():
return os.environ.get("PROPHET_REPACKAGE_CMDSTAN", "").lower() not in ["false", "0"] return os.environ.get("PROPHET_REPACKAGE_CMDSTAN", "").lower() not in ["false", "0"]
@ -59,6 +57,7 @@ def repackage_cmdstan():
def maybe_install_cmdstan_toolchain(): def maybe_install_cmdstan_toolchain():
"""Install C++ compilers required to build stan models on Windows machines.""" """Install C++ compilers required to build stan models on Windows machines."""
import cmdstanpy import cmdstanpy
try: try:
cmdstanpy.utils.cxx_toolchain_path() cmdstanpy.utils.cxx_toolchain_path()
except Exception: except Exception:
@ -122,7 +121,6 @@ def build_cmdstan_model(target_dir):
prune_cmdstan(cmdstan_dir) prune_cmdstan(cmdstan_dir)
def get_backends_from_env() -> List[str]: def get_backends_from_env() -> List[str]:
return os.environ.get("STAN_BACKEND", "CMDSTANPY").split(",") return os.environ.get("STAN_BACKEND", "CMDSTANPY").split(",")
@ -131,9 +129,10 @@ def build_models(target_dir):
print("Compiling cmdstanpy model") print("Compiling cmdstanpy model")
build_cmdstan_model(target_dir) 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") raise ValueError("PyStan backend is not supported for Prophet >= 1.1")
class BuildPyCommand(build_py): class BuildPyCommand(build_py):
"""Custom build command to pre-compile Stan models.""" """Custom build command to pre-compile Stan models."""
@ -153,117 +152,33 @@ class BuildExtCommand(build_ext):
pass pass
class DevelopCommand(develop): class EditableWheel(editable_wheel):
"""Custom develop command to pre-compile Stan models in-place.""" """Custom develop command to pre-compile Stan models in-place."""
def run(self): def run(self):
if not self.dry_run: 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) self.mkpath(target_dir)
build_models(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 = {} about = {}
here = Path(__file__).parent.resolve() 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) exec(f.read(), about)
setup( setup(
name=about["__title__"],
version=about["__version__"], 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(), packages=find_packages(),
install_requires=install_requires,
python_requires=">=3.7",
zip_safe=False, zip_safe=False,
include_package_data=True, include_package_data=True,
ext_modules=[Extension("prophet.stan_model", [])], ext_modules=[Extension("prophet.stan_model", [])],
cmdclass={ cmdclass={
"build_ext": BuildExtCommand, "build_ext": BuildExtCommand,
"build_py": BuildPyCommand, "build_py": BuildPyCommand,
"develop": DevelopCommand, "editable_wheel": EditableWheel,
"test": TestCommand,
}, },
test_suite="prophet.tests", 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",
) )