mirror of
https://github.com/kennethreitz/heroku-buildpack-python.git
synced 2026-06-05 23:10:16 +00:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b2c0b5df9 | |||
| 49b9eaa93e | |||
| 677dfeec11 | |||
| c77a1877d3 | |||
| 1c51f5d84e | |||
| 6922a82536 | |||
| 9cc5bf1a85 | |||
| 012cb8a4df | |||
| fab60ae6ab | |||
| cd52da6155 | |||
| acd9347930 | |||
| d7e2f0fb08 | |||
| a3ed9c7155 | |||
| 8db1f07fba | |||
| 17081d0328 | |||
| 9a6fa0478a | |||
| 573ded6d41 | |||
| b4ec35433a | |||
| cf1148f0a8 | |||
| a0649b1e50 | |||
| 2f2fd24421 | |||
| f754ae16bb | |||
| cef1be80a5 | |||
| c0571d86bf | |||
| d82eddca03 | |||
| 119e8145c3 | |||
| 99dae0f671 | |||
| f54dfff8a9 | |||
| c9760ae0ee | |||
| 98ff1670b3 | |||
| bdd466f838 | |||
| 324ebc9223 | |||
| 42ec6d8701 | |||
| 19513067bb | |||
| 753c912ecc | |||
| 4e8c469ec7 | |||
| 852723f867 | |||
| 94514a8179 | |||
| 7d57744c0a | |||
| a41ddf6bd1 | |||
| 197b7bae3f | |||
| f468739cfb | |||
| 555d5bd2be | |||
| 0a4d32c8a5 | |||
| 1a1cedfc21 | |||
| d35ee2c14c | |||
| 554a8bbae6 | |||
| 6572ad3d44 | |||
| 35cabaeebc | |||
| 300285a92d | |||
| 7a6f1eb010 | |||
| 66d754978e | |||
| 9c222a9350 | |||
| 7f4273f47f | |||
| dd707c21e3 | |||
| 7833743f96 | |||
| 5a9155e311 | |||
| 857c47ad66 | |||
| 87cf6073dc | |||
| 36dd089808 | |||
| c58f52e06e |
+6
-2
@@ -1,2 +1,6 @@
|
|||||||
sudo: false
|
language: bash
|
||||||
script: exit 0
|
sudo: required
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
install: docker pull heroku/cedar:14
|
||||||
|
script: make test
|
||||||
|
|||||||
@@ -1,5 +1,52 @@
|
|||||||
# Python Buildpack Changelog
|
# Python Buildpack Changelog
|
||||||
|
|
||||||
|
## 99
|
||||||
|
|
||||||
|
Cleanup.
|
||||||
|
|
||||||
|
## 98
|
||||||
|
|
||||||
|
Official NLTK support and other improvements.
|
||||||
|
|
||||||
|
- Support for `nltk.txt` file for declaring corpora to be downloaded.
|
||||||
|
- Leading zeros for auto-set WEB_CONCURRENCY.
|
||||||
|
|
||||||
|
## 97
|
||||||
|
|
||||||
|
Improved egg-link functionality.
|
||||||
|
|
||||||
|
## 96
|
||||||
|
|
||||||
|
Bugfix.
|
||||||
|
|
||||||
|
## 95
|
||||||
|
|
||||||
|
Improved output support.
|
||||||
|
|
||||||
|
## v94
|
||||||
|
|
||||||
|
Improved support for PyPy.
|
||||||
|
|
||||||
|
## v93
|
||||||
|
|
||||||
|
Improved support for PyPy.
|
||||||
|
|
||||||
|
## v92
|
||||||
|
|
||||||
|
Improved cache functionality and fix egg-links regression.
|
||||||
|
|
||||||
|
## v91
|
||||||
|
|
||||||
|
Bugfix, rolled back to v88.
|
||||||
|
|
||||||
|
## v90
|
||||||
|
|
||||||
|
Bugfix.
|
||||||
|
|
||||||
|
## v89
|
||||||
|
|
||||||
|
Improved cache functionality and fix egg-links regression.
|
||||||
|
|
||||||
## v88
|
## v88
|
||||||
|
|
||||||
Fixed bug with editable pip installations.
|
Fixed bug with editable pip installations.
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
# These targets are not files
|
# These targets are not files
|
||||||
.PHONY: tests
|
.PHONY: tests
|
||||||
|
|
||||||
tests:
|
test: test-cedar-14
|
||||||
./bin/test
|
|
||||||
|
test-cedar-14:
|
||||||
|
@echo "Running tests in docker (cedar-14)..."
|
||||||
|
@docker run -v $(shell pwd):/buildpack:ro --rm -it -e "STACK=cedar-14" heroku/cedar:14 bash -c 'cp -r /buildpack /buildpack_test; cd /buildpack_test/; test/run;'
|
||||||
|
@echo ""
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
git clone https://github.com/kennethreitz/pip-pop.git
|
git clone https://github.com/kennethreitz/pip-pop.git
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
# Heroku Buildpack: Python
|
# Heroku Buildpack: Python
|
||||||
|
|
||||||
|
[](https://travis-ci.org/heroku/heroku-buildpack-python)
|
||||||
|
|
||||||
This is the official [Heroku buildpack](https://devcenter.heroku.com/articles/buildpacks) for Python apps, powered by [pip](https://pip.pypa.io/) and other excellent software.
|
This is the official [Heroku buildpack](https://devcenter.heroku.com/articles/buildpacks) for Python apps, powered by [pip](https://pip.pypa.io/) and other excellent software.
|
||||||
|
|
||||||
Recommended web frameworks include **Django** and **Flask**. The recommended webserver is **Gunicorn**. There are no restrictions around what software can be used (as long as it's pip-installable). Web processes must bind to `$PORT`, and only the HTTP protocol is permitted for incoming connections.
|
Recommended web frameworks include **Django** and **Flask**. The recommended webserver is **Gunicorn**. There are no restrictions around what software can be used (as long as it's pip-installable). Web processes must bind to `$PORT`, and only the HTTP protocol is permitted for incoming connections.
|
||||||
@@ -21,12 +23,12 @@ Deploying a Python application couldn't be easier:
|
|||||||
$ git push heroku master
|
$ git push heroku master
|
||||||
...
|
...
|
||||||
-----> Python app detected
|
-----> Python app detected
|
||||||
-----> Installing python-2.7.12
|
-----> Installing python-2.7.13
|
||||||
$ pip install -r requirements.txt
|
$ pip install -r requirements.txt
|
||||||
Collecting requests (from -r requirements.txt (line 1))
|
Collecting requests (from -r requirements.txt (line 1))
|
||||||
Downloading requests-2.10.0-py2.py3-none-any.whl (501kB)
|
Downloading requests-2.12.4-py2.py3-none-any.whl (576KB)
|
||||||
Installing collected packages: requests
|
Installing collected packages: requests
|
||||||
Successfully installed requests-2.10.0
|
Successfully installed requests-2.12.4
|
||||||
|
|
||||||
-----> Discovering process types
|
-----> Discovering process types
|
||||||
Procfile declares types -> (none)
|
Procfile declares types -> (none)
|
||||||
@@ -44,11 +46,11 @@ Specify a Python Runtime
|
|||||||
Specific versions of the Python runtime can be specified with a `runtime.txt` file:
|
Specific versions of the Python runtime can be specified with a `runtime.txt` file:
|
||||||
|
|
||||||
$ cat runtime.txt
|
$ cat runtime.txt
|
||||||
python-3.5.2
|
python-3.6.0
|
||||||
|
|
||||||
Runtime options include:
|
Runtime options include:
|
||||||
|
|
||||||
- `python-2.7.12`
|
- `python-2.7.13`
|
||||||
- `python-3.5.2`
|
- `python-3.6.0`
|
||||||
- `pypy-5.3.1` (unsupported, experimental)
|
- `pypy-5.6.0` (unsupported, experimental)
|
||||||
- `pypy3-2.4.0` (unsupported, experimental)
|
- `pypy3-5.5.0` (unsupported, experimental)
|
||||||
|
|||||||
+29
-10
@@ -148,20 +148,22 @@ bpwatch start restore_cache
|
|||||||
cp -R $CACHE_DIR/.heroku/python-version .heroku/ &> /dev/null || true
|
cp -R $CACHE_DIR/.heroku/python-version .heroku/ &> /dev/null || true
|
||||||
cp -R $CACHE_DIR/.heroku/vendor .heroku/ &> /dev/null || true
|
cp -R $CACHE_DIR/.heroku/vendor .heroku/ &> /dev/null || true
|
||||||
cp -R $CACHE_DIR/.heroku/venv .heroku/ &> /dev/null || true
|
cp -R $CACHE_DIR/.heroku/venv .heroku/ &> /dev/null || true
|
||||||
|
if [[ -d $CACHE_DIR/.heroku/src ]]; then
|
||||||
|
cp -R $CACHE_DIR/.heroku/src .heroku/ &> /dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
bpwatch stop restore_cache
|
bpwatch stop restore_cache
|
||||||
|
|
||||||
mkdir -p $(dirname $PROFILE_PATH)
|
mkdir -p $(dirname $PROFILE_PATH)
|
||||||
|
|
||||||
# Make the directory for -e pip installations.
|
|
||||||
mkdir -p /app/.heroku/src
|
mkdir -p /app/.heroku/src
|
||||||
|
|
||||||
if [[ $BUILD_DIR != '/app' ]]; then
|
if [[ $BUILD_DIR != '/app' ]]; then
|
||||||
# python expects to reside in /app, so set up symlinks
|
# python expects to reside in /app, so set up symlinks
|
||||||
# we will not remove these later so subsequent buildpacks can still invoke it
|
# we will not remove these later so subsequent buildpacks can still invoke it
|
||||||
ln -s $BUILD_DIR/.heroku/python /app/.heroku/python
|
ln -nsf $BUILD_DIR/.heroku/python /app/.heroku/python
|
||||||
ln -s $BUILD_DIR/.heroku/vendor /app/.heroku/vendor
|
ln -nsf $BUILD_DIR/.heroku/vendor /app/.heroku/vendor
|
||||||
ln -s $BUILD_DIR/.heroku/venv /app/.heroku/venv
|
ln -nsf $BUILD_DIR/.heroku/venv /app/.heroku/venv
|
||||||
|
# Note: .heroku/src is copied in later.
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install Python.
|
# Install Python.
|
||||||
@@ -188,13 +190,19 @@ sub-env $BIN_DIR/steps/geo-libs
|
|||||||
# GDAL support.
|
# GDAL support.
|
||||||
source $BIN_DIR/steps/gdal
|
source $BIN_DIR/steps/gdal
|
||||||
|
|
||||||
# Install dependencies with Pip.
|
# Install dependencies with Pip (where the magic happens).
|
||||||
source $BIN_DIR/steps/pip-install
|
source $BIN_DIR/steps/pip-install
|
||||||
|
|
||||||
|
# Support for NLTK corpora.
|
||||||
|
sub-env $BIN_DIR/steps/nltk
|
||||||
|
|
||||||
|
# Support for pip install -e.
|
||||||
|
rm -fr $BUILD_DIR/.heroku/src
|
||||||
|
deep-cp /app/.heroku/src $BUILD_DIR/.heroku/src
|
||||||
|
|
||||||
# Django collectstatic support.
|
# Django collectstatic support.
|
||||||
sub-env $BIN_DIR/steps/collectstatic
|
sub-env $BIN_DIR/steps/collectstatic
|
||||||
|
|
||||||
|
|
||||||
# Create .profile script for application runtime environment variables.
|
# Create .profile script for application runtime environment variables.
|
||||||
set-env PATH '$HOME/.heroku/python/bin:$PATH'
|
set-env PATH '$HOME/.heroku/python/bin:$PATH'
|
||||||
set-env PYTHONUNBUFFERED true
|
set-env PYTHONUNBUFFERED true
|
||||||
@@ -208,14 +216,21 @@ set-default-env PYTHONPATH /app/
|
|||||||
# Install sane-default script for $WEB_CONCURRENCY and $FORWARDED_ALLOW_IPS.
|
# Install sane-default script for $WEB_CONCURRENCY and $FORWARDED_ALLOW_IPS.
|
||||||
cp $ROOT_DIR/vendor/python.gunicorn.sh $GUNICORN_PROFILE_PATH
|
cp $ROOT_DIR/vendor/python.gunicorn.sh $GUNICORN_PROFILE_PATH
|
||||||
|
|
||||||
# Deep copy the directory for -e pip installations
|
|
||||||
deep-cp /app/.heroku/src $BUILD_DIR/.heroku/src
|
|
||||||
|
|
||||||
# Experimental post_compile hook.
|
# Experimental post_compile hook.
|
||||||
bpwatch start post_compile
|
bpwatch start post_compile
|
||||||
source $BIN_DIR/steps/hooks/post_compile
|
source $BIN_DIR/steps/hooks/post_compile
|
||||||
bpwatch stop post_compile
|
bpwatch stop post_compile
|
||||||
|
|
||||||
|
set +e
|
||||||
|
# rewrite build dir in egg links to /app so things are found at runtime
|
||||||
|
find .heroku/python/lib/python*/site-packages/ -name "*.pth" -print0 2> /dev/null | xargs -r -0 -n 1 sed -i -e "s#$(pwd)#/app#" &> /dev/null
|
||||||
|
set -e
|
||||||
|
|
||||||
|
set +e
|
||||||
|
# Support for PyPy
|
||||||
|
find .heroku/python/lib-python/*/site-packages/ -name "*.pth" -print0 2> /dev/null | xargs -r -0 -n 1 sed -i -e "s#$(pwd)#/app#" &> /dev/null
|
||||||
|
set -e
|
||||||
|
|
||||||
# Store new artifacts in cache.
|
# Store new artifacts in cache.
|
||||||
bpwatch start dump_cache
|
bpwatch start dump_cache
|
||||||
|
|
||||||
@@ -224,6 +239,7 @@ bpwatch start dump_cache
|
|||||||
rm -rf $CACHE_DIR/.heroku/python-stack
|
rm -rf $CACHE_DIR/.heroku/python-stack
|
||||||
rm -rf $CACHE_DIR/.heroku/vendor
|
rm -rf $CACHE_DIR/.heroku/vendor
|
||||||
rm -rf $CACHE_DIR/.heroku/venv
|
rm -rf $CACHE_DIR/.heroku/venv
|
||||||
|
rm -rf $CACHE_DIR/.heroku/src
|
||||||
|
|
||||||
mkdir -p $CACHE_DIR/.heroku
|
mkdir -p $CACHE_DIR/.heroku
|
||||||
cp -R .heroku/python $CACHE_DIR/.heroku/
|
cp -R .heroku/python $CACHE_DIR/.heroku/
|
||||||
@@ -231,6 +247,9 @@ bpwatch start dump_cache
|
|||||||
cp -R .heroku/python-stack $CACHE_DIR/.heroku/ &> /dev/null || true
|
cp -R .heroku/python-stack $CACHE_DIR/.heroku/ &> /dev/null || true
|
||||||
cp -R .heroku/vendor $CACHE_DIR/.heroku/ &> /dev/null || true
|
cp -R .heroku/vendor $CACHE_DIR/.heroku/ &> /dev/null || true
|
||||||
cp -R .heroku/venv $CACHE_DIR/.heroku/ &> /dev/null || true
|
cp -R .heroku/venv $CACHE_DIR/.heroku/ &> /dev/null || true
|
||||||
|
if [[ -d .heroku/src ]]; then
|
||||||
|
cp -R .heroku/src $CACHE_DIR/.heroku/ &> /dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
bpwatch stop dump_cache
|
bpwatch stop dump_cache
|
||||||
|
|
||||||
|
|||||||
Executable
+33
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# This script serves as the NLTK build step of the
|
||||||
|
# [**Python Buildpack**](https://github.com/heroku/heroku-buildpack-python)
|
||||||
|
# compiler.
|
||||||
|
#
|
||||||
|
# A [buildpack](https://devcenter.heroku.com/articles/buildpacks) is an
|
||||||
|
# adapter between a Python application and Heroku's runtime.
|
||||||
|
#
|
||||||
|
# This script is invoked by [`bin/compile`](/).
|
||||||
|
|
||||||
|
# Syntax sugar.
|
||||||
|
source $BIN_DIR/utils
|
||||||
|
|
||||||
|
bpwatch start nltk_download
|
||||||
|
|
||||||
|
# Check that nltk was installed by pip, otherwise obviously not needed
|
||||||
|
python -m nltk.downloader -h >/dev/null 2>&1
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
puts-step "Downloading NLTK corpora..."
|
||||||
|
nltk_packages_definition="$BUILD_DIR/nltk.txt"
|
||||||
|
if [ -f "$nltk_packages_definition" ]; then
|
||||||
|
nltk_packages=$(tr "\n" " " < "$nltk_packages_definition")
|
||||||
|
puts-step "Downloading NLTK packages: $nltk_packages"
|
||||||
|
python -m nltk.downloader -d $BUILD_DIR/.heroku/python/nltk_data $nltk_packages | indent
|
||||||
|
set-env NLTK_DATA "/app/.heroku/python/nltk_data"
|
||||||
|
else
|
||||||
|
puts-warn "nltk.txt not found, not downloading any corpora"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
bpwatch stop nltk_download
|
||||||
+13
-3
@@ -1,13 +1,23 @@
|
|||||||
# Install dependencies with Pip.
|
# Install dependencies with Pip.
|
||||||
puts-cmd "pip install -r requirements.txt"
|
puts-cmd "pip install -r requirements.txt"
|
||||||
|
|
||||||
|
set +e
|
||||||
|
# delete any existing egg links, to uninstall exisisting installations.
|
||||||
|
find .heroku/python/lib/python*/site-packages/ -name "*.egg-link" -delete 2> /dev/null
|
||||||
|
find .heroku/python/lib/python*/site-packages/ -name "*.pth" -print0 2> /dev/null | xargs -r -0 -n 1 sed -i -e "s#/app/#/$(pwd)/#" &> /dev/null
|
||||||
|
set -e
|
||||||
|
|
||||||
|
set +e
|
||||||
|
# Support for the above, for PyPy.
|
||||||
|
find .heroku/python/lib-python/*/site-packages/ -name "*.egg-link" -print0 2> /dev/null | xargs -r -0 -n 1 sed -i -e "s#/app/#$(pwd)/#" &> /dev/null
|
||||||
|
find .heroku/python/lib-python/*/site-packages/ -name "*.pth" -print0 2> /dev/null | xargs -r -0 -n 1 sed -i -e "s#/app/#/$(pwd)/#" &> /dev/null
|
||||||
|
set -e
|
||||||
|
|
||||||
[ ! "$FRESH_PYTHON" ] && bpwatch start pip_install
|
[ ! "$FRESH_PYTHON" ] && bpwatch start pip_install
|
||||||
[ "$FRESH_PYTHON" ] && bpwatch start pip_install_first
|
[ "$FRESH_PYTHON" ] && bpwatch start pip_install_first
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
/app/.heroku/python/bin/pip install -r requirements.txt --exists-action=w --src=/app/.heroku/src --disable-pip-version-check --no-cache-dir 2>&1 | tee $WARNINGS_LOG | cleanup | indent
|
/app/.heroku/python/bin/pip install -r $BUILD_DIR/requirements.txt --exists-action=w --src=/app/.heroku/src --disable-pip-version-check --no-cache-dir 2>&1 | tee $WARNINGS_LOG | cleanup | indent
|
||||||
PIP_STATUS="${PIPESTATUS[0]}"
|
PIP_STATUS="${PIPESTATUS[0]}"
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ indent() {
|
|||||||
|
|
||||||
# Clean up pip output
|
# Clean up pip output
|
||||||
cleanup() {
|
cleanup() {
|
||||||
sed -e 's/\.\.\.\+/.../g' | sed -e '/already satisfied/Id' | sed -e '/Overwriting/Id' | sed -e '/python executable/Id' | sed -e '/no previously-included files/Id'
|
sed -e 's/\.\.\.\+/.../g' | sed -e '/already satisfied/Id' | sed -e '/No files were found to uninstall/Id' | sed -e '/Overwriting/Id' | sed -e '/python executable/Id' | sed -e '/no previously-included files/Id'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Buildpack Indented line.
|
# Buildpack Indented line.
|
||||||
|
|||||||
Executable
+18
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build Path: /app/.heroku/python/
|
||||||
|
# Build Deps: libraries/sqlite
|
||||||
|
|
||||||
|
OUT_PREFIX=$1
|
||||||
|
|
||||||
|
echo "Building Python..."
|
||||||
|
SOURCE_TARBALL='https://python.org/ftp/python/3.6.0/Python-3.6.0.tgz'
|
||||||
|
curl -L $SOURCE_TARBALL | tar xz
|
||||||
|
mv Python-3.6.0 src
|
||||||
|
cd src
|
||||||
|
|
||||||
|
./configure --prefix=$OUT_PREFIX --with-ensurepip=no
|
||||||
|
make
|
||||||
|
make install
|
||||||
|
|
||||||
|
ln $OUT_PREFIX/bin/python3 $OUT_PREFIX/bin/python
|
||||||
|
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
psycopg2
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
requests
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
python-2.7.13
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
requests
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
python-3.6.0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
requests
|
||||||
Vendored
+114
@@ -0,0 +1,114 @@
|
|||||||
|
Maya: Datetime for Humans™
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/v/maya.svg
|
||||||
|
:target: https://pypi.python.org/pypi/maya
|
||||||
|
|
||||||
|
.. image:: https://travis-ci.org/kennethreitz/maya.svg?branch=master
|
||||||
|
:target: https://travis-ci.org/kennethreitz/maya
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/badge/SayThanks.io-☼-1EAEDB.svg
|
||||||
|
:target: https://saythanks.io/to/kennethreitz
|
||||||
|
|
||||||
|
|
||||||
|
Datetimes are very frustrating to work with in Python, especially when dealing
|
||||||
|
with different locales on different systems. This library exists to make the
|
||||||
|
simple things **much** easier, while admitting that time is an illusion
|
||||||
|
(timezones doubly so).
|
||||||
|
|
||||||
|
Datetimes should be interacted with via an API written for humans.
|
||||||
|
|
||||||
|
Maya is mostly built around the headaches and use-cases around parsing datetime data from websites.
|
||||||
|
|
||||||
|
|
||||||
|
☤ Basic Usage of Maya
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Behold, datetimes for humans!
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> now = maya.now()
|
||||||
|
<MayaDT epoch=1481850660.9>
|
||||||
|
|
||||||
|
>>> tomorrow = maya.when('tomorrow')
|
||||||
|
<MayaDT epoch=1481919067.23>
|
||||||
|
|
||||||
|
>>> tomorrow.slang_date()
|
||||||
|
'tomorrow'
|
||||||
|
|
||||||
|
>>> tomorrow.slang_time()
|
||||||
|
'23 hours from now'
|
||||||
|
|
||||||
|
>>> tomorrow.iso8601()
|
||||||
|
'2016-12-16T15:11:30.263350Z'
|
||||||
|
|
||||||
|
>>> tomorrow.rfc2822()
|
||||||
|
'Fri, 16 Dec 2016 20:11:30 -0000'
|
||||||
|
|
||||||
|
>>> tomorrow.datetime()
|
||||||
|
datetime.datetime(2016, 12, 16, 15, 11, 30, 263350, tzinfo=<UTC>)
|
||||||
|
|
||||||
|
# Automatically parse datetime strings and generate naive datetimes.
|
||||||
|
>>> scraped = '2016-12-16 18:23:45.423992+00:00'
|
||||||
|
>>> maya.parse(scraped).datetime(to_timezone='US/Eastern', naive=True)
|
||||||
|
datetime.datetime(2016, 12, 16, 13, 23, 45, 423992)
|
||||||
|
|
||||||
|
>>> rand_day = maya.when('2011-02-07', timezone='US/Eastern')
|
||||||
|
<MayaDT epoch=1297036800.0>
|
||||||
|
|
||||||
|
# Note how this is the 6th, not the 7th.
|
||||||
|
>>> rand_day.day
|
||||||
|
6
|
||||||
|
|
||||||
|
# Always.
|
||||||
|
>>> rand_day.timezone
|
||||||
|
UTC
|
||||||
|
|
||||||
|
☤ Why is this useful?
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
- All timezone algebra will behave identically on all machines, regardless of system locale.
|
||||||
|
- Complete symmetric import and export of both ISO 8601 and RFC 2822 datetime stamps.
|
||||||
|
- Fantastic parsing of both dates written for/by humans and machines (``maya.when()`` vs ``maya.parse()``).
|
||||||
|
- Support for human slang, both import and export (e.g. `an hour ago`).
|
||||||
|
- Datetimes can very easily be generated, with or without tzinfo attached.
|
||||||
|
- This library is based around epoch time, but dates before Jan 1 1970 are indeed supported, via negative integers.
|
||||||
|
- Maya never panics, and always carries a towel.
|
||||||
|
|
||||||
|
|
||||||
|
☤ What about Delorean, Arrow, & Pendulum?
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
Arrow, for example, is a fantastic library, but isn't what I wanted in a datetime library. In many ways, it's better than Maya for certain things. In some ways, in my opinion, it's not.
|
||||||
|
|
||||||
|
I simply desire a sane API for datetimes that made sense to me for all the things I'd ever want to do—especially when dealing with timezone algebra. Arrow doesn't do all of the things I need (but it does a lot more!). Maya does do exactly what I need.
|
||||||
|
|
||||||
|
I think these projects complement each-other, personally. Maya is great for parsing websites. For example- Arrow supports floors and ceilings and spans of dates, which Maya does not at all.
|
||||||
|
|
||||||
|
|
||||||
|
☤ Installing Maya
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Installation is easy, with pip::
|
||||||
|
|
||||||
|
$ pip install maya
|
||||||
|
|
||||||
|
✨🍰✨
|
||||||
|
|
||||||
|
☤ Like it?
|
||||||
|
----------
|
||||||
|
|
||||||
|
`Say Thanks <https://saythanks.io/to/kennethreitz>`_!
|
||||||
|
|
||||||
|
|
||||||
|
How to Contribute
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug.
|
||||||
|
#. Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it).
|
||||||
|
#. Write a test which shows that the bug was fixed or that the feature works as expected.
|
||||||
|
#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_.
|
||||||
|
|
||||||
|
.. _`the repository`: http://github.com/kennethreitz/maya
|
||||||
|
.. _AUTHORS: https://github.com/kennethreitz/maya/blob/master/AUTHORS.rst
|
||||||
Vendored
+273
@@ -0,0 +1,273 @@
|
|||||||
|
|
||||||
|
# ___ __ ___ _ _ ___
|
||||||
|
# || \/ | ||=|| \\// ||=||
|
||||||
|
# || | || || // || ||
|
||||||
|
|
||||||
|
# Ignore warnings for yaml usage.
|
||||||
|
import warnings
|
||||||
|
import ruamel.yaml
|
||||||
|
warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)
|
||||||
|
|
||||||
|
|
||||||
|
import email.utils
|
||||||
|
import time
|
||||||
|
from datetime import datetime as Datetime
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
import humanize
|
||||||
|
import dateparser
|
||||||
|
import iso8601
|
||||||
|
import dateutil.parser
|
||||||
|
from tzlocal import get_localzone
|
||||||
|
|
||||||
|
_EPOCH_START = (1970, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_class_type_arguments(operator):
|
||||||
|
"""
|
||||||
|
Decorator to validate all the arguments to function
|
||||||
|
are of the type of calling class
|
||||||
|
"""
|
||||||
|
|
||||||
|
def inner(function):
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
for arg in args + tuple(kwargs.values()):
|
||||||
|
if not isinstance(arg, self.__class__):
|
||||||
|
raise TypeError('unorderable types: {}() {} {}()'.format(
|
||||||
|
type(self).__name__, operator, type(arg).__name__))
|
||||||
|
return function(self, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MayaDT(object):
|
||||||
|
"""The Maya Datetime object."""
|
||||||
|
|
||||||
|
def __init__(self, epoch):
|
||||||
|
super(MayaDT, self).__init__()
|
||||||
|
self._epoch = epoch
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<MayaDT epoch={}>'.format(self._epoch)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.rfc2822()
|
||||||
|
|
||||||
|
def __format__(self, *args, **kwargs):
|
||||||
|
"""Return's the datetime's format"""
|
||||||
|
return format(self.datetime(), *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@validate_class_type_arguments('==')
|
||||||
|
def __eq__(self, maya_dt):
|
||||||
|
return self._epoch == maya_dt._epoch
|
||||||
|
|
||||||
|
@validate_class_type_arguments('!=')
|
||||||
|
def __ne__(self, maya_dt):
|
||||||
|
return self._epoch != maya_dt._epoch
|
||||||
|
|
||||||
|
@validate_class_type_arguments('<')
|
||||||
|
def __lt__(self, maya_dt):
|
||||||
|
return self._epoch < maya_dt._epoch
|
||||||
|
|
||||||
|
@validate_class_type_arguments('<=')
|
||||||
|
def __le__(self, maya_dt):
|
||||||
|
return self._epoch <= maya_dt._epoch
|
||||||
|
|
||||||
|
@validate_class_type_arguments('>')
|
||||||
|
def __gt__(self, maya_dt):
|
||||||
|
return self._epoch > maya_dt._epoch
|
||||||
|
|
||||||
|
@validate_class_type_arguments('>=')
|
||||||
|
def __ge__(self, maya_dt):
|
||||||
|
return self._epoch >= maya_dt._epoch
|
||||||
|
|
||||||
|
|
||||||
|
# Timezone Crap
|
||||||
|
# -------------
|
||||||
|
|
||||||
|
@property
|
||||||
|
def timezone(self):
|
||||||
|
"""Returns the UTC tzinfo name. It's always UTC. Always."""
|
||||||
|
return 'UTC'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _tz(self):
|
||||||
|
"""Returns the UTC tzinfo object."""
|
||||||
|
return pytz.timezone(self.timezone)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def local_timezone(self):
|
||||||
|
"""Returns the name of the local timezone, for informational purposes."""
|
||||||
|
return self._local_tz.zone
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _local_tz(self):
|
||||||
|
"""Returns the local timezone."""
|
||||||
|
return get_localzone()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __dt_to_epoch(dt):
|
||||||
|
"""Converts a datetime into an epoch."""
|
||||||
|
|
||||||
|
# Assume UTC if no datetime is provided.
|
||||||
|
if dt.tzinfo is None:
|
||||||
|
dt = dt.replace(tzinfo=pytz.utc)
|
||||||
|
|
||||||
|
epoch_start = Datetime(*_EPOCH_START, tzinfo=pytz.timezone('UTC'))
|
||||||
|
return (dt - epoch_start).total_seconds()
|
||||||
|
|
||||||
|
# Importers
|
||||||
|
# ---------
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_datetime(klass, dt):
|
||||||
|
"""Returns MayaDT instance from datetime."""
|
||||||
|
return klass(klass.__dt_to_epoch(dt))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_iso8601(klass, string):
|
||||||
|
"""Returns MayaDT instance from iso8601 string."""
|
||||||
|
dt = iso8601.parse_date(string)
|
||||||
|
return klass.from_datetime(dt)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_rfc2822(string):
|
||||||
|
"""Returns MayaDT instance from rfc2822 string."""
|
||||||
|
return parse(string)
|
||||||
|
|
||||||
|
# Exporters
|
||||||
|
# ---------
|
||||||
|
|
||||||
|
def datetime(self, to_timezone=None, naive=False):
|
||||||
|
"""Returns a timezone-aware datetime...
|
||||||
|
Defaulting to UTC (as it should).
|
||||||
|
|
||||||
|
Keyword Arguments:
|
||||||
|
to_timezone {string} -- timezone to convert to (default: None/UTC)
|
||||||
|
naive {boolean} -- if True, the tzinfo is simply dropped (default: False)
|
||||||
|
"""
|
||||||
|
if to_timezone:
|
||||||
|
dt = self.datetime().astimezone(pytz.timezone(to_timezone))
|
||||||
|
else:
|
||||||
|
dt = Datetime.utcfromtimestamp(self._epoch)
|
||||||
|
dt.replace(tzinfo=self._tz)
|
||||||
|
|
||||||
|
# Strip the timezone info if requested to do so.
|
||||||
|
if naive:
|
||||||
|
return dt.replace(tzinfo=None)
|
||||||
|
else:
|
||||||
|
if dt.tzinfo is None:
|
||||||
|
dt = dt.replace(tzinfo=self._tz)
|
||||||
|
|
||||||
|
return dt
|
||||||
|
|
||||||
|
def iso8601(self):
|
||||||
|
"""Returns an ISO 8601 representation of the MayaDT."""
|
||||||
|
# Get a timezone-naive datetime.
|
||||||
|
dt = self.datetime(naive=True)
|
||||||
|
return '{}Z'.format(dt.isoformat())
|
||||||
|
|
||||||
|
def rfc2822(self):
|
||||||
|
"""Returns an RFC 2822 representation of the MayaDT."""
|
||||||
|
return email.utils.formatdate(self.epoch, usegmt=True)
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
# ----------
|
||||||
|
|
||||||
|
@property
|
||||||
|
def year(self):
|
||||||
|
return self.datetime().year
|
||||||
|
|
||||||
|
@property
|
||||||
|
def month(self):
|
||||||
|
return self.datetime().month
|
||||||
|
|
||||||
|
@property
|
||||||
|
def day(self):
|
||||||
|
return self.datetime().day
|
||||||
|
|
||||||
|
@property
|
||||||
|
def week(self):
|
||||||
|
return self.datetime().isocalendar()[1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def weekday(self):
|
||||||
|
"""Return the day of the week as an integer. Monday is 1 and Sunday is 7"""
|
||||||
|
return self.datetime().isoweekday()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hour(self):
|
||||||
|
return self.datetime().hour
|
||||||
|
|
||||||
|
@property
|
||||||
|
def minute(self):
|
||||||
|
return self.datetime().minute
|
||||||
|
|
||||||
|
@property
|
||||||
|
def second(self):
|
||||||
|
return self.datetime().second
|
||||||
|
|
||||||
|
@property
|
||||||
|
def microsecond(self):
|
||||||
|
return self.datetime().microsecond
|
||||||
|
|
||||||
|
@property
|
||||||
|
def epoch(self):
|
||||||
|
return self._epoch
|
||||||
|
|
||||||
|
# Human Slang Extras
|
||||||
|
# ------------------
|
||||||
|
|
||||||
|
def slang_date(self):
|
||||||
|
""""Returns human slang representation of date."""
|
||||||
|
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
|
||||||
|
return humanize.naturaldate(dt)
|
||||||
|
|
||||||
|
def slang_time(self):
|
||||||
|
""""Returns human slang representation of time."""
|
||||||
|
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
|
||||||
|
return humanize.naturaltime(dt)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def now():
|
||||||
|
"""Returns a MayaDT instance for this exact moment."""
|
||||||
|
epoch = time.time()
|
||||||
|
return MayaDT(epoch=epoch)
|
||||||
|
|
||||||
|
def when(string, timezone='UTC'):
|
||||||
|
""""Returns a MayaDT instance for the human moment specified.
|
||||||
|
|
||||||
|
Powered by dateparser. Useful for scraping websites.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
'next week', 'now', 'tomorrow', '300 years ago', 'August 14, 2015'
|
||||||
|
|
||||||
|
Keyword Arguments:
|
||||||
|
string -- string to be parsed
|
||||||
|
timezone -- timezone referenced from (default: 'UTC')
|
||||||
|
|
||||||
|
"""
|
||||||
|
dt = dateparser.parse(string, settings={'TIMEZONE': timezone, 'RETURN_AS_TIMEZONE_AWARE': True, 'TO_TIMEZONE': 'UTC'})
|
||||||
|
|
||||||
|
if dt is None:
|
||||||
|
raise ValueError('invalid datetime input specified.')
|
||||||
|
|
||||||
|
return MayaDT.from_datetime(dt)
|
||||||
|
|
||||||
|
def parse(string, day_first=False):
|
||||||
|
""""Returns a MayaDT instance for the machine-produced moment specified.
|
||||||
|
|
||||||
|
Powered by dateutil. Accepts most known formats. Useful for working with data.
|
||||||
|
|
||||||
|
Keyword Arguments:
|
||||||
|
string -- string to be parsed
|
||||||
|
day_first -- if true, the first value (e.g. 01/05/2016) is parsed as day (default: False)
|
||||||
|
"""
|
||||||
|
dt = dateutil.parser.parse(string, dayfirst=day_first)
|
||||||
|
return MayaDT.from_datetime(dt)
|
||||||
Vendored
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import codecs
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Python 3
|
||||||
|
from os import dirname
|
||||||
|
except ImportError:
|
||||||
|
# Python 2
|
||||||
|
from os.path import dirname
|
||||||
|
|
||||||
|
here = os.path.abspath(dirname(__file__))
|
||||||
|
|
||||||
|
with codecs.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:
|
||||||
|
long_description = '\n' + f.read()
|
||||||
|
|
||||||
|
|
||||||
|
if sys.argv[-1] == "publish":
|
||||||
|
os.system("python setup.py sdist bdist_wheel upload")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
required = [
|
||||||
|
'humanize',
|
||||||
|
'pytz',
|
||||||
|
'dateparser',
|
||||||
|
'iso8601',
|
||||||
|
'python-dateutil',
|
||||||
|
'ruamel.yaml',
|
||||||
|
'tzlocal'
|
||||||
|
]
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='maya',
|
||||||
|
version='0.1.6',
|
||||||
|
description='Datetimes for Humans.',
|
||||||
|
long_description=long_description,
|
||||||
|
author='Kenneth Reitz',
|
||||||
|
author_email='me@kennethreitz.com',
|
||||||
|
url='https://github.com/kennethreitz/maya',
|
||||||
|
py_modules=['maya'],
|
||||||
|
install_requires=required,
|
||||||
|
license='MIT',
|
||||||
|
classifiers=(
|
||||||
|
|
||||||
|
),
|
||||||
|
)
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
testNoRequirements() {
|
||||||
|
compile "no-requirements"
|
||||||
|
assertCapturedError
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
testSetupPy() {
|
||||||
|
compile "setup-py"
|
||||||
|
assertCaptured "maya"
|
||||||
|
assertCapturedSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
testStandardRequirements() {
|
||||||
|
compile "requirements-standard"
|
||||||
|
assertCaptured "requests"
|
||||||
|
assertCapturedSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
testPsycopg2() {
|
||||||
|
compile "psycopg2"
|
||||||
|
assertCaptured "psycopg2"
|
||||||
|
assertCapturedSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
testPython2() {
|
||||||
|
compile "python2"
|
||||||
|
assertCaptured "python-2.7.13"
|
||||||
|
assertCapturedSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
testPython3() {
|
||||||
|
compile "python3"
|
||||||
|
assertCaptured "python-3.6.0"
|
||||||
|
assertCapturedSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pushd $(dirname 0) >/dev/null
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
source $(pwd)/test/utils
|
||||||
|
|
||||||
|
mktmpdir() {
|
||||||
|
dir=$(mktemp -t testXXXXX)
|
||||||
|
rm -rf $dir
|
||||||
|
mkdir $dir
|
||||||
|
echo $dir
|
||||||
|
}
|
||||||
|
|
||||||
|
detect() {
|
||||||
|
capture $(pwd)/bin/detect $(pwd)/test/fixtures/$1
|
||||||
|
}
|
||||||
|
|
||||||
|
compile_dir=""
|
||||||
|
|
||||||
|
default_process_types_cleanup() {
|
||||||
|
file="/tmp/default_process_types"
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
rm "$file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
compile() {
|
||||||
|
default_process_types_cleanup
|
||||||
|
bp_dir=$(mktmpdir)
|
||||||
|
compile_dir=$(mktmpdir)
|
||||||
|
cp -a $(pwd)/* ${bp_dir}
|
||||||
|
cp -a ${bp_dir}/test/fixtures/$1/. ${compile_dir}
|
||||||
|
capture ${bp_dir}/bin/compile ${compile_dir} ${2:-$(mktmpdir)} $3
|
||||||
|
}
|
||||||
|
|
||||||
|
compileDir() {
|
||||||
|
default_process_types_cleanup
|
||||||
|
|
||||||
|
local bp_dir=$(mktmpdir)
|
||||||
|
local compile_dir=${1:-$(mktmpdir)}
|
||||||
|
local cache_dir=${2:-$(mktmpdir)}
|
||||||
|
local env_dir=$3
|
||||||
|
|
||||||
|
cp -a $(pwd)/* ${bp_dir}
|
||||||
|
capture ${bp_dir}/bin/compile ${compile_dir} ${cache_dir} ${env_dir}
|
||||||
|
}
|
||||||
|
|
||||||
|
release() {
|
||||||
|
bp_dir=$(mktmpdir)
|
||||||
|
cp -a $(pwd)/* ${bp_dir}
|
||||||
|
capture ${bp_dir}/bin/release ${bp_dir}/test/fixtures/$1
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFile() {
|
||||||
|
assertEquals "$1" "$(cat ${compile_dir}/$2)"
|
||||||
|
}
|
||||||
|
|
||||||
|
source $(pwd)/test/shunit2
|
||||||
Executable
+1048
File diff suppressed because it is too large
Load Diff
+195
@@ -0,0 +1,195 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# taken from
|
||||||
|
# https://github.com/ryanbrainard/heroku-buildpack-testrunner/blob/master/lib/test_utils.sh
|
||||||
|
|
||||||
|
oneTimeSetUp()
|
||||||
|
{
|
||||||
|
TEST_SUITE_CACHE="$(mktemp -d ${SHUNIT_TMPDIR}/test_suite_cache.XXXX)"
|
||||||
|
}
|
||||||
|
|
||||||
|
oneTimeTearDown()
|
||||||
|
{
|
||||||
|
rm -rf ${TEST_SUITE_CACHE}
|
||||||
|
}
|
||||||
|
|
||||||
|
setUp()
|
||||||
|
{
|
||||||
|
OUTPUT_DIR="$(mktemp -d ${SHUNIT_TMPDIR}/output.XXXX)"
|
||||||
|
STD_OUT="${OUTPUT_DIR}/stdout"
|
||||||
|
STD_ERR="${OUTPUT_DIR}/stderr"
|
||||||
|
BUILD_DIR="${OUTPUT_DIR}/build"
|
||||||
|
CACHE_DIR="${OUTPUT_DIR}/cache"
|
||||||
|
mkdir -p ${OUTPUT_DIR}
|
||||||
|
mkdir -p ${BUILD_DIR}
|
||||||
|
mkdir -p ${CACHE_DIR}
|
||||||
|
}
|
||||||
|
|
||||||
|
tearDown()
|
||||||
|
{
|
||||||
|
rm -rf ${OUTPUT_DIR}
|
||||||
|
}
|
||||||
|
|
||||||
|
capture()
|
||||||
|
{
|
||||||
|
resetCapture
|
||||||
|
|
||||||
|
LAST_COMMAND="$@"
|
||||||
|
|
||||||
|
$@ >${STD_OUT} 2>${STD_ERR}
|
||||||
|
RETURN=$?
|
||||||
|
rtrn=${RETURN} # deprecated
|
||||||
|
}
|
||||||
|
|
||||||
|
resetCapture()
|
||||||
|
{
|
||||||
|
if [ -f ${STD_OUT} ]; then
|
||||||
|
rm ${STD_OUT}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f ${STD_ERR} ]; then
|
||||||
|
rm ${STD_ERR}
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset LAST_COMMAND
|
||||||
|
unset RETURN
|
||||||
|
unset rtrn # deprecated
|
||||||
|
}
|
||||||
|
|
||||||
|
detect()
|
||||||
|
{
|
||||||
|
capture ${BUILDPACK_HOME}/bin/detect ${BUILD_DIR}
|
||||||
|
}
|
||||||
|
|
||||||
|
compile()
|
||||||
|
{
|
||||||
|
capture ${BUILDPACK_HOME}/bin/compile ${BUILD_DIR} ${CACHE_DIR}
|
||||||
|
}
|
||||||
|
|
||||||
|
release()
|
||||||
|
{
|
||||||
|
capture ${BUILDPACK_HOME}/bin/release ${BUILD_DIR}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertCapturedEquals()
|
||||||
|
{
|
||||||
|
assertEquals "$@" "$(cat ${STD_OUT})"
|
||||||
|
}
|
||||||
|
|
||||||
|
assertCapturedNotEquals()
|
||||||
|
{
|
||||||
|
assertNotEquals "$@" "$(cat ${STD_OUT})"
|
||||||
|
}
|
||||||
|
|
||||||
|
assertCaptured()
|
||||||
|
{
|
||||||
|
assertFileContains "$@" "${STD_OUT}"
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNotCaptured()
|
||||||
|
{
|
||||||
|
assertFileNotContains "$@" "${STD_OUT}"
|
||||||
|
}
|
||||||
|
|
||||||
|
assertCapturedSuccess()
|
||||||
|
{
|
||||||
|
assertEquals "Expected captured exit code to be 0; was <${RETURN}>" "0" "${RETURN}"
|
||||||
|
assertEquals "Expected STD_ERR to be empty; was <$(cat ${STD_ERR})>" "" "$(cat ${STD_ERR})"
|
||||||
|
}
|
||||||
|
|
||||||
|
# assertCapturedError [[expectedErrorCode] expectedErrorMsg]
|
||||||
|
assertCapturedError()
|
||||||
|
{
|
||||||
|
if [ $# -gt 1 ]; then
|
||||||
|
local expectedErrorCode=${1}
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
local expectedErrorMsg=${1:-""}
|
||||||
|
|
||||||
|
if [ -z ${expectedErrorCode} ]; then
|
||||||
|
assertTrue "Expected captured exit code to be greater than 0; was <${RETURN}>" "[ ${RETURN} -gt 0 ]"
|
||||||
|
else
|
||||||
|
assertTrue "Expected captured exit code to be <${expectedErrorCode}>; was <${RETURN}>" "[ ${RETURN} -eq ${expectedErrorCode} ]"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${expectedErrorMsg}" != "" ]; then
|
||||||
|
assertFileContains "Expected STD_ERR to contain error <${expectedErrorMsg}>" "${expectedErrorMsg}" "${STD_ERR}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_assertContains()
|
||||||
|
{
|
||||||
|
if [ 5 -eq $# ]; then
|
||||||
|
local msg=$1
|
||||||
|
shift
|
||||||
|
elif [ ! 4 -eq $# ]; then
|
||||||
|
fail "Expected 4 or 5 parameters; Receieved $# parameters"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local needle=$1
|
||||||
|
local haystack=$2
|
||||||
|
local expectation=$3
|
||||||
|
local haystack_type=$4
|
||||||
|
|
||||||
|
case "${haystack_type}" in
|
||||||
|
"file") grep -q -F -e "${needle}" ${haystack} ;;
|
||||||
|
"text") echo "${haystack}" | grep -q -F -e "${needle}" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "${expectation}" != "$?" ]; then
|
||||||
|
case "${expectation}" in
|
||||||
|
0) default_msg="Expected <${haystack}> to contain <${needle}>" ;;
|
||||||
|
1) default_msg="Did not expect <${haystack}> to contain <${needle}>" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
fail "${msg:-${default_msg}}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
debug()
|
||||||
|
{
|
||||||
|
cat $STD_OUT
|
||||||
|
}
|
||||||
|
|
||||||
|
assertContains()
|
||||||
|
{
|
||||||
|
_assertContains "$@" 0 "text"
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNotContains()
|
||||||
|
{
|
||||||
|
_assertContains "$@" 1 "text"
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFileContains()
|
||||||
|
{
|
||||||
|
_assertContains "$@" 0 "file"
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFileNotContains()
|
||||||
|
{
|
||||||
|
_assertContains "$@" 1 "file"
|
||||||
|
}
|
||||||
|
|
||||||
|
command_exists () {
|
||||||
|
type "$1" > /dev/null 2>&1 ;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFileMD5()
|
||||||
|
{
|
||||||
|
expectedHash=$1
|
||||||
|
filename=$2
|
||||||
|
|
||||||
|
if command_exists "md5sum"; then
|
||||||
|
md5_cmd="md5sum ${filename}"
|
||||||
|
expected_md5_cmd_output="${expectedHash} ${filename}"
|
||||||
|
elif command_exists "md5"; then
|
||||||
|
md5_cmd="md5 ${filename}"
|
||||||
|
expected_md5_cmd_output="MD5 (${filename}) = ${expectedHash}"
|
||||||
|
else
|
||||||
|
fail "no suitable MD5 hashing command found on this system"
|
||||||
|
fi
|
||||||
|
|
||||||
|
assertEquals "${expected_md5_cmd_output}" "`${md5_cmd}`"
|
||||||
|
}
|
||||||
Vendored
+10
-4
@@ -1,29 +1,35 @@
|
|||||||
|
if [[ "${WEB_CONCURRENCY:-}" == 0* ]]; then
|
||||||
|
# another buildpack set a default value, with leading zero
|
||||||
|
unset WEB_CONCURRENCY
|
||||||
|
fi
|
||||||
|
|
||||||
case $(ulimit -u) in
|
case $(ulimit -u) in
|
||||||
|
|
||||||
# Automatic configuration for Gunicorn's Workers setting.
|
# Automatic configuration for Gunicorn's Workers setting.
|
||||||
|
# Leading zero padding so a subsequent buildpack can figure out that we set a value, and not the user
|
||||||
|
|
||||||
# Standard-1X (+Free, +Hobby) Dyno
|
# Standard-1X (+Free, +Hobby) Dyno
|
||||||
256)
|
256)
|
||||||
export DYNO_RAM=512
|
export DYNO_RAM=512
|
||||||
export WEB_CONCURRENCY=${WEB_CONCURRENCY:-2}
|
export WEB_CONCURRENCY=${WEB_CONCURRENCY:-02}
|
||||||
;;
|
;;
|
||||||
|
|
||||||
# Standard-2X Dyno
|
# Standard-2X Dyno
|
||||||
512)
|
512)
|
||||||
export DYNO_RAM=1024
|
export DYNO_RAM=1024
|
||||||
export WEB_CONCURRENCY=${WEB_CONCURRENCY:-4}
|
export WEB_CONCURRENCY=${WEB_CONCURRENCY:-04}
|
||||||
;;
|
;;
|
||||||
|
|
||||||
# Performance-M Dyno
|
# Performance-M Dyno
|
||||||
16384)
|
16384)
|
||||||
export DYNO_RAM=2560
|
export DYNO_RAM=2560
|
||||||
export WEB_CONCURRENCY=${WEB_CONCURRENCY:-8}
|
export WEB_CONCURRENCY=${WEB_CONCURRENCY:-08}
|
||||||
;;
|
;;
|
||||||
|
|
||||||
# Performance-L Dyno
|
# Performance-L Dyno
|
||||||
32768)
|
32768)
|
||||||
export DYNO_RAM=6656
|
export DYNO_RAM=6656
|
||||||
export WEB_CONCURRENCY=${WEB_CONCURRENCY:-11}
|
export WEB_CONCURRENCY=${WEB_CONCURRENCY:-011}
|
||||||
;;
|
;;
|
||||||
|
|
||||||
esac
|
esac
|
||||||
|
|||||||
Reference in New Issue
Block a user