[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