[Python-modules-commits] [python-cycler] 01/08: Import python-cycler_0.10.0.orig.tar.gz
Sandro Tosi
morph at moszumanska.debian.org
Sat Apr 9 21:31:09 UTC 2016
This is an automated email from the git hooks/post-receive script.
morph pushed a commit to branch master
in repository python-cycler.
commit 6dbb97a01ee2217f603bac46a0456134711ff1f0
Author: Sandro Tosi <morph at debian.org>
Date: Sat Apr 9 11:07:17 2016 +0100
Import python-cycler_0.10.0.orig.tar.gz
---
.travis.yml | 3 +-
MANIFEST.in | 4 +
README.rst | 2 +-
appveyor.yml | 66 +++++++++
ci/appveyor/install.ps1 | 180 ++++++++++++++++++++++++
ci/appveyor/run_with_env.cmd | 47 +++++++
conda-recipe/bld.bat | 8 ++
conda-recipe/build.sh | 9 ++
conda-recipe/meta.yaml | 63 +++++++++
cycler.py | 317 ++++++++++++++++++++++++++++++++++++++-----
doc/source/conf.py | 4 +-
doc/source/index.rst | 132 ++++++++++++++++--
setup.py | 3 +-
test_cycler.py | 246 ++++++++++++++++++++++++++++-----
14 files changed, 997 insertions(+), 87 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index bfc5179..c354030 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,10 +2,9 @@ language: python
matrix:
include:
- - python: 2.6
- python: 2.7
- - python: 3.3
- python: 3.4
+ - python: 3.5
- python: "nightly"
env: PRE=--pre
allow_failures:
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..815cfe5
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,4 @@
+include run_tests.py
+include LICENSE
+recursive-include conda-recipe *
+recursive-include doc Makefile make.bat *.rst *.py
\ No newline at end of file
diff --git a/README.rst b/README.rst
index cb68e97..2fc7323 100644
--- a/README.rst
+++ b/README.rst
@@ -1,4 +1,4 @@
cycler: composable cycles
=========================
-Docs: http://tacaswell.github.io/cycler/
+Docs: http://matplotlib.org/cycler/
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..2cddd81
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,66 @@
+# AppVeyor.com is a Continuous Integration service to build and run tests under
+# Windows
+
+environment:
+ global:
+ # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
+ # /E:ON and /V:ON options are not enabled in the batch script intepreter
+ # See: http://stackoverflow.com/a/13751649/163740
+ CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\ci\\appveyor\\run_with_env.cmd"
+
+ matrix:
+ - PYTHON: "C:\\Python27_32"
+ PYTHON_VERSION: "2.7"
+ PYTHON_ARCH: "32"
+
+ - PYTHON: "C:\\Python27_64"
+ PYTHON_VERSION: "2.7"
+ PYTHON_ARCH: "64"
+
+ - PYTHON: "C:\\Python34_32"
+ PYTHON_VERSION: "3.4.3"
+ PYTHON_ARCH: "32"
+
+ - PYTHON: "C:\\Python34_64"
+ PYTHON_VERSION: "3.4.3"
+ PYTHON_ARCH: "64"
+
+ - PYTHON: "C:\\Python35"
+ PYTHON_VERSION: "3.5.0"
+ PYTHON_ARCH: "32"
+
+ - PYTHON: "C:\\Python35-x64"
+ PYTHON_VERSION: "3.5.0"
+ PYTHON_ARCH: "64"
+
+install:
+ # Install Python (from the official .msi of http://python.org) and pip when
+ # not already installed.
+ - "powershell ./ci/appveyor/install.ps1"
+ - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
+
+ # Check that we have the expected version and architecture for Python
+ - "python --version"
+ - "python -c \"import struct; print(struct.calcsize('P') * 8)\""
+
+ # Install the build and runtime dependencies of the project.
+ - "%CMD_IN_ENV% pip install -v six nose coveralls"
+
+ # Install the generated wheel package to test it
+ - "python setup.py install"
+
+
+# Not a .NET project, we build scikit-image in the install step instead
+build: false
+
+test_script:
+
+ # Run unit tests with nose
+ - "python run_tests.py"
+
+artifacts:
+ # Archive the generated wheel package in the ci.appveyor.com build report.
+ - path: dist\*
+
+#on_success:
+# - TODO: upload the content of dist/*.whl to a public wheelhouse
diff --git a/ci/appveyor/install.ps1 b/ci/appveyor/install.ps1
new file mode 100644
index 0000000..0f165d8
--- /dev/null
+++ b/ci/appveyor/install.ps1
@@ -0,0 +1,180 @@
+# Sample script to install Python and pip under Windows
+# Authors: Olivier Grisel, Jonathan Helmus and Kyle Kastner
+# License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
+
+$MINICONDA_URL = "http://repo.continuum.io/miniconda/"
+$BASE_URL = "https://www.python.org/ftp/python/"
+$GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py"
+$GET_PIP_PATH = "C:\get-pip.py"
+
+
+function DownloadPython ($python_version, $platform_suffix) {
+ $webclient = New-Object System.Net.WebClient
+ $filename = "python-" + $python_version + $platform_suffix + ".msi"
+ $url = $BASE_URL + $python_version + "/" + $filename
+
+ $basedir = $pwd.Path + "\"
+ $filepath = $basedir + $filename
+ if (Test-Path $filename) {
+ Write-Host "Reusing" $filepath
+ return $filepath
+ }
+
+ # Download and retry up to 3 times in case of network transient errors.
+ Write-Host "Downloading" $filename "from" $url
+ $retry_attempts = 2
+ for($i=0; $i -lt $retry_attempts; $i++){
+ try {
+ $webclient.DownloadFile($url, $filepath)
+ break
+ }
+ Catch [Exception]{
+ Start-Sleep 1
+ }
+ }
+ if (Test-Path $filepath) {
+ Write-Host "File saved at" $filepath
+ } else {
+ # Retry once to get the error message if any at the last try
+ $webclient.DownloadFile($url, $filepath)
+ }
+ return $filepath
+}
+
+
+function InstallPython ($python_version, $architecture, $python_home) {
+ Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
+ if (Test-Path $python_home) {
+ Write-Host $python_home "already exists, skipping."
+ return $false
+ }
+ if ($architecture -eq "32") {
+ $platform_suffix = ""
+ } else {
+ $platform_suffix = ".amd64"
+ }
+ $msipath = DownloadPython $python_version $platform_suffix
+ Write-Host "Installing" $msipath "to" $python_home
+ $install_log = $python_home + ".log"
+ $install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home"
+ $uninstall_args = "/qn /x $msipath"
+ RunCommand "msiexec.exe" $install_args
+ if (-not(Test-Path $python_home)) {
+ Write-Host "Python seems to be installed else-where, reinstalling."
+ RunCommand "msiexec.exe" $uninstall_args
+ RunCommand "msiexec.exe" $install_args
+ }
+ if (Test-Path $python_home) {
+ Write-Host "Python $python_version ($architecture) installation complete"
+ } else {
+ Write-Host "Failed to install Python in $python_home"
+ Get-Content -Path $install_log
+ Exit 1
+ }
+}
+
+function RunCommand ($command, $command_args) {
+ Write-Host $command $command_args
+ Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru
+}
+
+
+function InstallPip ($python_home) {
+ $pip_path = $python_home + "\Scripts\pip.exe"
+ $python_path = $python_home + "\python.exe"
+ if (-not(Test-Path $pip_path)) {
+ Write-Host "Installing pip..."
+ $webclient = New-Object System.Net.WebClient
+ $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH)
+ Write-Host "Executing:" $python_path $GET_PIP_PATH
+ Start-Process -FilePath "$python_path" -ArgumentList "$GET_PIP_PATH" -Wait -Passthru
+ } else {
+ Write-Host "pip already installed."
+ }
+}
+
+
+function DownloadMiniconda ($python_version, $platform_suffix) {
+ $webclient = New-Object System.Net.WebClient
+ if ($python_version -eq "3.4") {
+ $filename = "Miniconda3-3.5.5-Windows-" + $platform_suffix + ".exe"
+ } else {
+ $filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe"
+ }
+ $url = $MINICONDA_URL + $filename
+
+ $basedir = $pwd.Path + "\"
+ $filepath = $basedir + $filename
+ if (Test-Path $filename) {
+ Write-Host "Reusing" $filepath
+ return $filepath
+ }
+
+ # Download and retry up to 3 times in case of network transient errors.
+ Write-Host "Downloading" $filename "from" $url
+ $retry_attempts = 2
+ for($i=0; $i -lt $retry_attempts; $i++){
+ try {
+ $webclient.DownloadFile($url, $filepath)
+ break
+ }
+ Catch [Exception]{
+ Start-Sleep 1
+ }
+ }
+ if (Test-Path $filepath) {
+ Write-Host "File saved at" $filepath
+ } else {
+ # Retry once to get the error message if any at the last try
+ $webclient.DownloadFile($url, $filepath)
+ }
+ return $filepath
+}
+
+
+function InstallMiniconda ($python_version, $architecture, $python_home) {
+ Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
+ if (Test-Path $python_home) {
+ Write-Host $python_home "already exists, skipping."
+ return $false
+ }
+ if ($architecture -eq "32") {
+ $platform_suffix = "x86"
+ } else {
+ $platform_suffix = "x86_64"
+ }
+ $filepath = DownloadMiniconda $python_version $platform_suffix
+ Write-Host "Installing" $filepath "to" $python_home
+ $install_log = $python_home + ".log"
+ $args = "/S /D=$python_home"
+ Write-Host $filepath $args
+ Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru
+ if (Test-Path $python_home) {
+ Write-Host "Python $python_version ($architecture) installation complete"
+ } else {
+ Write-Host "Failed to install Python in $python_home"
+ Get-Content -Path $install_log
+ Exit 1
+ }
+}
+
+
+function InstallMinicondaPip ($python_home) {
+ $pip_path = $python_home + "\Scripts\pip.exe"
+ $conda_path = $python_home + "\Scripts\conda.exe"
+ if (-not(Test-Path $pip_path)) {
+ Write-Host "Installing pip..."
+ $args = "install --yes pip"
+ Write-Host $conda_path $args
+ Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru
+ } else {
+ Write-Host "pip already installed."
+ }
+}
+
+function main () {
+ InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON
+ InstallPip $env:PYTHON
+}
+
+main
diff --git a/ci/appveyor/run_with_env.cmd b/ci/appveyor/run_with_env.cmd
new file mode 100644
index 0000000..0c70d63
--- /dev/null
+++ b/ci/appveyor/run_with_env.cmd
@@ -0,0 +1,47 @@
+:: To build extensions for 64 bit Python 3, we need to configure environment
+:: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of:
+:: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1)
+::
+:: To build extensions for 64 bit Python 2, we need to configure environment
+:: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of:
+:: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0)
+::
+:: 32 bit builds do not require specific environment configurations.
+::
+:: Note: this script needs to be run with the /E:ON and /V:ON flags for the
+:: cmd interpreter, at least for (SDK v7.0)
+::
+:: More details at:
+:: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows
+:: http://stackoverflow.com/a/13751649/163740
+::
+:: Author: Olivier Grisel
+:: License: BSD 3 clause
+ at ECHO OFF
+
+SET COMMAND_TO_RUN=%*
+SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows
+
+SET MAJOR_PYTHON_VERSION="%PYTHON_VERSION:~0,1%"
+IF %MAJOR_PYTHON_VERSION% == "2" (
+ SET WINDOWS_SDK_VERSION="v7.0"
+) ELSE IF %MAJOR_PYTHON_VERSION% == "3" (
+ SET WINDOWS_SDK_VERSION="v7.1"
+) ELSE (
+ ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%"
+ EXIT 1
+)
+
+IF "%PYTHON_ARCH%"=="64" (
+ ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture
+ SET DISTUTILS_USE_SDK=1
+ SET MSSdk=1
+ "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION%
+ "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release
+ ECHO Executing: %COMMAND_TO_RUN%
+ call %COMMAND_TO_RUN% || EXIT 1
+) ELSE (
+ ECHO Using default MSVC build environment for 32 bit architecture
+ ECHO Executing: %COMMAND_TO_RUN%
+ call %COMMAND_TO_RUN% || EXIT 1
+)
diff --git a/conda-recipe/bld.bat b/conda-recipe/bld.bat
new file mode 100644
index 0000000..87b1481
--- /dev/null
+++ b/conda-recipe/bld.bat
@@ -0,0 +1,8 @@
+"%PYTHON%" setup.py install
+if errorlevel 1 exit 1
+
+:: Add more build steps here, if they are necessary.
+
+:: See
+:: http://docs.continuum.io/conda/build.html
+:: for a list of environment variables that are set during the build process.
diff --git a/conda-recipe/build.sh b/conda-recipe/build.sh
new file mode 100644
index 0000000..4d7fc03
--- /dev/null
+++ b/conda-recipe/build.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+$PYTHON setup.py install
+
+# Add more build steps here, if they are necessary.
+
+# See
+# http://docs.continuum.io/conda/build.html
+# for a list of environment variables that are set during the build process.
diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml
new file mode 100644
index 0000000..ea33deb
--- /dev/null
+++ b/conda-recipe/meta.yaml
@@ -0,0 +1,63 @@
+package:
+ name: cycler
+ version: {{ environ['GIT_DESCRIBE_TAG'] }}.post{{ environ['GIT_DESCRIBE_NUMBER'] }}
+
+
+source:
+ git_url: ../
+
+# patches:
+ # List any patch files here
+ # - fix.patch
+
+build:
+ string: {{ environ.get('GIT_BUILD_STR', '') }}_py{{ py }}
+ # preserve_egg_dir: True
+ # entry_points:
+ # Put any entry points (scripts to be generated automatically) here. The
+ # syntax is module:function. For example
+ #
+ # - cycler = cycler:main
+ #
+ # Would create an entry point called cycler that calls cycler.main()
+
+
+ # If this is a new build for the same version, increment the build
+ # number. If you do not include this key, it defaults to 0.
+ # number: 1
+
+requirements:
+ build:
+ - python
+ - setuptools
+ - six
+
+ run:
+ - python
+ - six
+
+test:
+ # Python imports
+ imports:
+ - cycler
+
+ # commands:
+ # You can put test commands to be run here. Use this to test that the
+ # entry points work.
+
+
+ # You can also put a file called run_test.py in the recipe that will be run
+ # at test time.
+
+ # requires:
+ # Put any additional test requirements here. For example
+ # - nose
+
+about:
+ home: http://github.com/matplotlib/cycler
+ license: BSD
+ summary: 'Composable style cycles'
+
+# See
+# http://docs.continuum.io/conda/build.html for
+# more information about meta.yaml
diff --git a/cycler.py b/cycler.py
index d0d983a..3c3eb2d 100644
--- a/cycler.py
+++ b/cycler.py
@@ -1,13 +1,55 @@
+"""
+Cycler
+======
+
+Cycling through combinations of values, producing dictionaries.
+
+You can add cyclers::
+
+ from cycler import cycler
+ cc = (cycler(color=list('rgb')) +
+ cycler(linestyle=['-', '--', '-.']))
+ for d in cc:
+ print(d)
+
+Results in::
+
+ {'color': 'r', 'linestyle': '-'}
+ {'color': 'g', 'linestyle': '--'}
+ {'color': 'b', 'linestyle': '-.'}
+
+
+You can multiply cyclers::
+
+ from cycler import cycler
+ cc = (cycler(color=list('rgb')) *
+ cycler(linestyle=['-', '--', '-.']))
+ for d in cc:
+ print(d)
+
+Results in::
+
+ {'color': 'r', 'linestyle': '-'}
+ {'color': 'r', 'linestyle': '--'}
+ {'color': 'r', 'linestyle': '-.'}
+ {'color': 'g', 'linestyle': '-'}
+ {'color': 'g', 'linestyle': '--'}
+ {'color': 'g', 'linestyle': '-.'}
+ {'color': 'b', 'linestyle': '-'}
+ {'color': 'b', 'linestyle': '--'}
+ {'color': 'b', 'linestyle': '-.'}
+"""
+
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import six
-from itertools import product
+from itertools import product, cycle
from six.moves import zip, reduce
from operator import mul, add
import copy
-__version__ = '0.9.0'
+__version__ = '0.10.0'
def _process_keys(left, right):
@@ -16,15 +58,17 @@ def _process_keys(left, right):
Parameters
----------
- left, right : Cycler or None
+ left, right : iterable of dictionaries or None
The cyclers to be composed
Returns
-------
keys : set
The keys in the composition of the two cyclers
"""
- l_key = left.keys if left is not None else set()
- r_key = right.keys if right is not None else set()
+ l_peek = next(iter(left)) if left is not None else {}
+ r_peek = next(iter(right)) if right is not None else {}
+ l_key = set(l_peek.keys())
+ r_key = set(r_peek.keys())
if l_key & r_key:
raise ValueError("Can not compose overlapping cycles")
return l_key | r_key
@@ -62,14 +106,33 @@ class Cycler(object):
Function which composes the 'left' and 'right' cyclers.
"""
+ def __call__(self):
+ return cycle(self)
+
def __init__(self, left, right=None, op=None):
"""Semi-private init
Do not use this directly, use `cycler` function instead.
"""
- self._keys = _process_keys(left, right)
- self._left = copy.copy(left)
- self._right = copy.copy(right)
+ if isinstance(left, Cycler):
+ self._left = Cycler(left._left, left._right, left._op)
+ elif left is not None:
+ # Need to copy the dictionary or else that will be a residual
+ # mutable that could lead to strange errors
+ self._left = [copy.copy(v) for v in left]
+ else:
+ self._left = None
+
+ if isinstance(right, Cycler):
+ self._right = Cycler(right._left, right._right, right._op)
+ elif right is not None:
+ # Need to copy the dictionary or else that will be a residual
+ # mutable that could lead to strange errors
+ self._right = [copy.copy(v) for v in right]
+ else:
+ self._right = None
+
+ self._keys = _process_keys(self._left, self._right)
self._op = op
@property
@@ -79,6 +142,41 @@ class Cycler(object):
"""
return set(self._keys)
+ def change_key(self, old, new):
+ """
+ Change a key in this cycler to a new name.
+ Modification is performed in-place.
+
+ Does nothing if the old key is the same as the new key.
+ Raises a ValueError if the new key is already a key.
+ Raises a KeyError if the old key isn't a key.
+
+ """
+ if old == new:
+ return
+ if new in self._keys:
+ raise ValueError("Can't replace %s with %s, %s is already a key" %
+ (old, new, new))
+ if old not in self._keys:
+ raise KeyError("Can't replace %s with %s, %s is not a key" %
+ (old, new, old))
+
+ self._keys.remove(old)
+ self._keys.add(new)
+
+ if self._right is not None and old in self._right.keys:
+ self._right.change_key(old, new)
+
+ # self._left should always be non-None
+ # if self._keys is non-empty.
+ elif isinstance(self._left, Cycler):
+ self._left.change_key(old, new)
+ else:
+ # It should be completely safe at this point to
+ # assume that the old key can be found in each
+ # iteration.
+ self._left = [{new: entry[old]} for entry in self._left]
+
def _compose(self):
"""
Compose the 'left' and 'right' components of this cycle
@@ -118,15 +216,15 @@ class Cycler(object):
def __getitem__(self, key):
# TODO : maybe add numpy style fancy slicing
if isinstance(key, slice):
- trans = self._transpose()
- return reduce(add, (cycler(k, v[key])
+ trans = self.by_key()
+ return reduce(add, (_cycler(k, v[key])
for k, v in six.iteritems(trans)))
else:
raise ValueError("Can only use slices with Cycler.__getitem__")
def __iter__(self):
if self._right is None:
- return iter(self._left)
+ return iter(dict(l) for l in self._left)
return self._compose()
@@ -157,8 +255,8 @@ class Cycler(object):
if isinstance(other, Cycler):
return Cycler(self, other, product)
elif isinstance(other, int):
- trans = self._transpose()
- return reduce(add, (cycler(k, v*other)
+ trans = self.by_key()
+ return reduce(add, (_cycler(k, v*other)
for k, v in six.iteritems(trans)))
else:
return NotImplemented
@@ -183,11 +281,14 @@ class Cycler(object):
other : Cycler
The second Cycler
"""
+ if not isinstance(other, Cycler):
+ raise TypeError("Cannot += with a non-Cycler object")
+ # True shallow copy of self is fine since this is in-place
old_self = copy.copy(self)
self._keys = _process_keys(old_self, other)
self._left = old_self
self._op = zip
- self._right = copy.copy(other)
+ self._right = Cycler(other._left, other._right, other._op)
return self
def __imul__(self, other):
@@ -199,14 +300,27 @@ class Cycler(object):
other : Cycler
The second Cycler
"""
-
+ if not isinstance(other, Cycler):
+ raise TypeError("Cannot *= with a non-Cycler object")
+ # True shallow copy of self is fine since this is in-place
old_self = copy.copy(self)
self._keys = _process_keys(old_self, other)
self._left = old_self
self._op = product
- self._right = copy.copy(other)
+ self._right = Cycler(other._left, other._right, other._op)
return self
+ def __eq__(self, other):
+ """
+ Check equality
+ """
+ if len(self) != len(other):
+ return False
+ if self.keys ^ other.keys:
+ return False
+
+ return all(a == b for a, b in zip(self, other))
+
def __repr__(self):
op_map = {zip: '+', product: '*'}
if self._right is None:
@@ -221,7 +335,7 @@ class Cycler(object):
def _repr_html_(self):
# an table showing the value of each key through a full cycle
output = "<table>"
- sorted_keys = sorted(self.keys)
+ sorted_keys = sorted(self.keys, key=repr)
for key in sorted_keys:
output += "<th>{key!r}</th>".format(key=key)
for d in iter(self):
@@ -232,17 +346,21 @@ class Cycler(object):
output += "</table>"
return output
- def _transpose(self):
- """
- Internal helper function which iterates through the
- styles and returns a dict of lists instead of a list of
- dicts. This is needed for multiplying by integers and
- for __getitem__
+ def by_key(self):
+ """Values by key
+
+ This returns the transposed values of the cycler. Iterating
+ over a `Cycler` yields dicts with a single value for each key,
+ this method returns a `dict` of `list` which are the values
+ for the given key.
+
+ The returned value can be used to create an equivalent `Cycler`
+ using only `+`.
Returns
-------
- trans : dict
- dict of lists for the styles
+ transpose : dict
+ dict of lists of the values for each key.
"""
# TODO : sort out if this is a bottle neck, if there is a better way
@@ -257,6 +375,9 @@ class Cycler(object):
out[k].append(d[k])
return out
+ # for back compatibility
+ _transpose = by_key
+
def simplify(self):
"""Simplify the Cycler
@@ -272,18 +393,147 @@ class Cycler(object):
# ((a + b) + (c + d))
# I would believe that there is some performance implications
- trans = self._transpose()
- return reduce(add, (cycler(k, v) for k, v in six.iteritems(trans)))
+ trans = self.by_key()
+ return reduce(add, (_cycler(k, v) for k, v in six.iteritems(trans)))
+
+ def concat(self, other):
+ """Concatenate this cycler and an other.
+ The keys must match exactly.
+
+ This returns a single Cycler which is equivalent to
+ `itertools.chain(self, other)`
+
+ Examples
+ --------
+
+ >>> num = cycler('a', range(3))
+ >>> let = cycler('a', 'abc')
+ >>> num.concat(let)
+ cycler('a', [0, 1, 2, 'a', 'b', 'c'])
+
+ Parameters
+ ----------
+ other : `Cycler`
+ The `Cycler` to concatenate to this one.
+
+ Returns
+ -------
+ ret : `Cycler`
+ The concatenated `Cycler`
+ """
+ return concat(self, other)
-def cycler(label, itr):
+
+def concat(left, right):
+ """Concatenate two cyclers.
+
+ The keys must match exactly.
+
+ This returns a single Cycler which is equivalent to
+ `itertools.chain(left, right)`
+
+ Examples
+ --------
+
+ >>> num = cycler('a', range(3))
+ >>> let = cycler('a', 'abc')
+ >>> num.concat(let)
+ cycler('a', [0, 1, 2, 'a', 'b', 'c'])
+
+ Parameters
+ ----------
+ left, right : `Cycler`
+ The two `Cycler` instances to concatenate
+
+ Returns
+ -------
+ ret : `Cycler`
+ The concatenated `Cycler`
+ """
+ if left.keys != right.keys:
+ msg = '\n\t'.join(["Keys do not match:",
+ "Intersection: {both!r}",
+ "Disjoint: {just_one!r}"]).format(
+ both=left.keys & right.keys,
+ just_one=left.keys ^ right.keys)
+
+ raise ValueError(msg)
+
+ _l = left.by_key()
+ _r = right.by_key()
+ return reduce(add, (_cycler(k, _l[k] + _r[k]) for k in left.keys))
+
+
+def cycler(*args, **kwargs):
+ """
+ Create a new `Cycler` object from a single positional argument,
+ a pair of positional arguments, or the combination of keyword arguments.
+
+ cycler(arg)
+ cycler(label1=itr1[, label2=iter2[, ...]])
+ cycler(label, itr)
+
+ Form 1 simply copies a given `Cycler` object.
+
+ Form 2 composes a `Cycler` as an inner product of the
+ pairs of keyword arguments. In other words, all of the
+ iterables are cycled simultaneously, as if through zip().
+
+ Form 3 creates a `Cycler` from a label and an iterable.
+ This is useful for when the label cannot be a keyword argument
+ (e.g., an integer or a name that has a space in it).
+
+ Parameters
+ ----------
+ arg : Cycler
+ Copy constructor for Cycler (does a shallow copy of iterables).
+
+ label : name
+ The property key. In the 2-arg form of the function,
+ the label can be any hashable object. In the keyword argument
+ form of the function, it must be a valid python identifier.
+
+ itr : iterable
+ Finite length iterable of the property values.
+ Can be a single-property `Cycler` that would
+ be like a key change, but as a shallow copy.
+
+ Returns
+ -------
+ cycler : Cycler
+ New `Cycler` for the given property
+
+ """
+ if args and kwargs:
+ raise TypeError("cyl() can only accept positional OR keyword "
+ "arguments -- not both.")
+
+ if len(args) == 1:
+ if not isinstance(args[0], Cycler):
+ raise TypeError("If only one positional argument given, it must "
+ " be a Cycler instance.")
+ return Cycler(args[0])
+ elif len(args) == 2:
+ return _cycler(*args)
+ elif len(args) > 2:
+ raise TypeError("Only a single Cycler can be accepted as the lone "
+ "positional argument. Use keyword arguments instead.")
+
+ if kwargs:
+ return reduce(add, (_cycler(k, v) for k, v in six.iteritems(kwargs)))
+
+ raise TypeError("Must have at least a positional OR keyword arguments")
+
+
+def _cycler(label, itr):
"""
Create a new `Cycler` object from a property name and
iterable of values.
Parameters
----------
- label : str
+ label : hashable
The property key.
itr : iterable
@@ -300,10 +550,9 @@ def cycler(label, itr):
msg = "Can not create Cycler from a multi-property Cycler"
raise ValueError(msg)
- if label in keys:
- return copy.copy(itr)
- else:
- lab = keys.pop()
- itr = list(v[lab] for v in itr)
+ lab = keys.pop()
+ # Doesn't need to be a new list because
+ # _from_iter() will be creating that new list anyway.
+ itr = (v[lab] for v in itr)
return Cycler._from_iter(label, itr)
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 2685527..fb48a87 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -71,9 +71,9 @@ copyright = '2015, Matplotlib Developers'
# built documents.
#
# The short X.Y version.
-version = '0.9.0'
+version = '0.10.0'
# The full version, including alpha/beta/rc tags.
-release = '0.9.0'
+release = '0.10.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 79f6808..911c403 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -1,14 +1,22 @@
.. currentmodule:: cycler
-====================
- Style/kwarg cycler
-====================
+===================
+ Composable cycles
+===================
.. htmlonly::
- :Release: |version|
+ :Version: |version|
:Date: |today|
+
+====== ====================================
+docs http://matplotlib.org/cycler
+pypi https://pypi.python.org/pypi/Cycler
+github https://github.com/matplotlib/cycler
+====== ====================================
+
+
:py:mod:`cycler` API
====================
@@ -42,7 +50,7 @@ hashable (as it will eventually be used as the key in a :obj:`dict`).
from cycler import cycler
- color_cycle = cycler('color', ['r', 'g', 'b'])
+ color_cycle = cycler(color=['r', 'g', 'b'])
color_cycle
The `Cycler` knows it's length and keys:
@@ -61,12 +69,22 @@ the label
for v in color_cycle:
print(v)
-`Cycler` objects can be passed as the second argument to :func:`cycler`
+`Cycler` objects can be passed as the argument to :func:`cycler`
which returns a new `Cycler` with a new label, but the same values.
.. ipython:: python
- cycler('ec', color_cycle)
+ cycler(ec=color_cycle)
+
+
+Iterating over a `Cycler` results in the finite list of entries, to
+get an infinite cycle, call the `Cycler` object (a-la a generator)
+
+.. ipython:: python
+
+ cc = color_cycle()
+ for j, c in zip(range(5), cc):
+ print(j, c)
... 530 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-cycler.git
More information about the Python-modules-commits
mailing list