├── .coveragerc
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── dependabot.yml
├── pull_request_template.md
└── workflows
│ ├── python-app.yml
│ └── python-publish.yml
├── .gitignore
├── .readthedocs.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── __init__.py
├── codecov.yml
├── development.txt
├── docs
├── Makefile
├── make.bat
└── source
│ ├── _build
│ ├── doctrees
│ │ ├── contributing.doctree
│ │ ├── environment.pickle
│ │ ├── examples.doctree
│ │ ├── get_started.doctree
│ │ ├── index.doctree
│ │ ├── introduction.doctree
│ │ ├── modules.doctree
│ │ └── pyfuncol.doctree
│ └── html
│ │ ├── .buildinfo
│ │ ├── .doctrees
│ │ ├── environment.pickle
│ │ ├── index.doctree
│ │ └── introduction.doctree
│ │ ├── _modules
│ │ ├── index.html
│ │ └── pyfuncol
│ │ │ ├── dict.html
│ │ │ ├── list.html
│ │ │ └── set.html
│ │ ├── _sources
│ │ ├── contributing.md.txt
│ │ ├── examples.md.txt
│ │ ├── get_started.md.txt
│ │ ├── index.rst.txt
│ │ ├── introduction.md.txt
│ │ ├── modules.rst.txt
│ │ └── pyfuncol.rst.txt
│ │ ├── _static
│ │ ├── alabaster.css
│ │ ├── basic.css
│ │ ├── css
│ │ │ ├── badge_only.css
│ │ │ ├── fonts
│ │ │ │ ├── Roboto-Slab-Bold.woff
│ │ │ │ ├── Roboto-Slab-Bold.woff2
│ │ │ │ ├── Roboto-Slab-Regular.woff
│ │ │ │ ├── Roboto-Slab-Regular.woff2
│ │ │ │ ├── fontawesome-webfont.eot
│ │ │ │ ├── fontawesome-webfont.svg
│ │ │ │ ├── fontawesome-webfont.ttf
│ │ │ │ ├── fontawesome-webfont.woff
│ │ │ │ ├── fontawesome-webfont.woff2
│ │ │ │ ├── lato-bold-italic.woff
│ │ │ │ ├── lato-bold-italic.woff2
│ │ │ │ ├── lato-bold.woff
│ │ │ │ ├── lato-bold.woff2
│ │ │ │ ├── lato-normal-italic.woff
│ │ │ │ ├── lato-normal-italic.woff2
│ │ │ │ ├── lato-normal.woff
│ │ │ │ └── lato-normal.woff2
│ │ │ └── theme.css
│ │ ├── custom.css
│ │ ├── doctools.js
│ │ ├── documentation_options.js
│ │ ├── file.png
│ │ ├── jquery-3.5.1.js
│ │ ├── jquery.js
│ │ ├── js
│ │ │ ├── badge_only.js
│ │ │ ├── html5shiv-printshiv.min.js
│ │ │ ├── html5shiv.min.js
│ │ │ └── theme.js
│ │ ├── language_data.js
│ │ ├── minus.png
│ │ ├── plus.png
│ │ ├── pygments.css
│ │ ├── searchtools.js
│ │ ├── underscore-1.13.1.js
│ │ └── underscore.js
│ │ ├── contributing.html
│ │ ├── examples.html
│ │ ├── genindex.html
│ │ ├── get_started.html
│ │ ├── index.html
│ │ ├── introduction.html
│ │ ├── modules.html
│ │ ├── objects.inv
│ │ ├── py-modindex.html
│ │ ├── pyfuncol.html
│ │ ├── search.html
│ │ └── searchindex.js
│ ├── conf.py
│ ├── contributing.md
│ ├── examples.md
│ ├── get_started.md
│ ├── index.rst
│ ├── introduction.md
│ ├── modules.rst
│ └── pyfuncol.rst
├── pyfuncol
├── __init__.py
├── dict.py
├── extend_builtins.py
├── list.py
├── set.py
└── tests
│ ├── __init__.py
│ ├── test_dict.py
│ ├── test_list.py
│ ├── test_no_forbiddenfruit.py
│ └── test_set.py
├── requirements.txt
├── setup.cfg
└── setup.py
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | omit = **/tests/*
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: ['paypal.me/gondolav']
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG] Title"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 |
16 | 1. ...
17 |
18 | **Expected behavior**
19 | A clear and concise description of what you expected to happen.
20 |
21 | **Desktop (please complete the following information):**
22 | - OS: [e.g. macOS Monterey 12.1]
23 | - Python version [e.g. 3.9]
24 | - pyfuncol version [e.g. 1.0]
25 |
26 | **Additional context**
27 | Add any other context about the problem here.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: "[FEATURE] Title"
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "pip"
9 | # Files stored in repository root
10 | directory: "/"
11 | schedule:
12 | interval: "daily"
13 | - package-ecosystem: "github-actions"
14 | # Workflow files stored in the
15 | # default location of `.github/workflows`
16 | directory: "/"
17 | schedule:
18 | interval: "daily"
19 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
4 |
5 | Fixes # (issue)
6 |
7 | ## Type of change
8 |
9 | Please delete options that are not relevant.
10 |
11 | - [ ] Bug fix (non-breaking change which fixes an issue)
12 | - [ ] New feature (non-breaking change which adds functionality)
13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
14 | - [ ] This change requires a documentation update
15 |
16 | ## Checklist
17 |
18 | - [ ] My code follows the style guidelines of this project
19 | - [ ] I have made corresponding changes to the documentation
20 | - [ ] I have added tests that prove my fix is effective or that my feature works
21 |
--------------------------------------------------------------------------------
/.github/workflows/python-app.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3 |
4 | name: CI
5 |
6 | on:
7 | push:
8 | pull_request:
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 | - name: Set up Python 3.10
17 | uses: actions/setup-python@v4.5.0
18 | with:
19 | python-version: "3.10"
20 | - name: Install dependencies
21 | run: |
22 | python -m pip install --upgrade pip
23 | if [ -f development.txt ]; then pip install -r development.txt; fi
24 | - name: Test with pytest
25 | run: |
26 | pytest --cov-config=.coveragerc --cov=pyfuncol --cov-report=xml
27 | - name: Upload code coverage to Codecov
28 | uses: codecov/codecov-action@v3
29 |
--------------------------------------------------------------------------------
/.github/workflows/python-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will upload a Python Package using Twine when a release is created
2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3 |
4 | # This workflow uses actions that are not certified by GitHub.
5 | # They are provided by a third-party and are governed by
6 | # separate terms of service, privacy policy, and support
7 | # documentation.
8 |
9 | name: Upload Python Package
10 |
11 | on:
12 | release:
13 | types: [published]
14 |
15 | jobs:
16 | deploy:
17 |
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 | - name: Set up Python
23 | uses: actions/setup-python@v4.5.0
24 | with:
25 | python-version: '3.x'
26 | - name: Install dependencies
27 | run: |
28 | python -m pip install --upgrade pip
29 | pip install build
30 | - name: Build package
31 | run: python -m build
32 | - name: Publish package
33 | uses: pypa/gh-action-pypi-publish@f5622bde02b04381239da3573277701ceca8f6a0
34 | with:
35 | user: __token__
36 | password: ${{ secrets.PYPI_API_TOKEN }}
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 |
3 | # Byte-compiled / optimized / DLL files
4 | __pycache__/
5 | *.py[cod]
6 | *$py.class
7 |
8 | # C extensions
9 | *.so
10 |
11 | # Distribution / packaging
12 | .Python
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | pip-wheel-metadata/
26 | share/python-wheels/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 | MANIFEST
31 |
32 | # PyInstaller
33 | # Usually these files are written by a python script from a template
34 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
35 | *.manifest
36 | *.spec
37 |
38 | # Installer logs
39 | pip-log.txt
40 | pip-delete-this-directory.txt
41 |
42 | # Unit test / coverage reports
43 | htmlcov/
44 | .tox/
45 | .nox/
46 | .coverage
47 | .coverage.*
48 | .cache
49 | nosetests.xml
50 | coverage.xml
51 | *.cover
52 | *.py,cover
53 | .hypothesis/
54 | .pytest_cache/
55 |
56 | # Translations
57 | *.mo
58 | *.pot
59 |
60 | # Django stuff:
61 | *.log
62 | local_settings.py
63 | db.sqlite3
64 | db.sqlite3-journal
65 |
66 | # Flask stuff:
67 | instance/
68 | .webassets-cache
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # IPython
83 | profile_default/
84 | ipython_config.py
85 |
86 | # pyenv
87 | .python-version
88 |
89 | # pipenv
90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
93 | # install all needed dependencies.
94 | #Pipfile.lock
95 |
96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
97 | __pypackages__/
98 |
99 | # Celery stuff
100 | celerybeat-schedule
101 | celerybeat.pid
102 |
103 | # SageMath parsed files
104 | *.sage.py
105 |
106 | # Environments
107 | .env
108 | .venv
109 | env/
110 | venv/
111 | ENV/
112 | env.bak/
113 | venv.bak/
114 |
115 | # Spyder project settings
116 | .spyderproject
117 | .spyproject
118 |
119 | # Rope project settings
120 | .ropeproject
121 |
122 | # mkdocs documentation
123 | /site
124 |
125 | # mypy
126 | .mypy_cache/
127 | .dmypy.json
128 | dmypy.json
129 |
130 | # Pyre type checker
131 | .pyre/
132 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | sphinx:
9 | configuration: docs/source/conf.py
10 |
11 | formats:
12 | - pdf
13 |
14 | python:
15 | version: 3.8
16 | install:
17 | - requirements: development.txt
18 | - method: setuptools
19 | path: .
20 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | andrea.veneziano@icloud.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Setup
4 |
5 | Fork the repo, then install all development requirements with:
6 |
7 | ```shell
8 | pip install -r development.txt
9 | ```
10 |
11 | When your changes are ready, submit a pull request!
12 |
13 | ## Style
14 |
15 | For formatting and code style, we use [black](https://github.com/psf/black). Docstrings should follow the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings).
16 |
17 | ## Tests
18 |
19 | To run the tests, execute `pytest` at the root of the project.
20 |
21 | To run the tests with coverage enabled, execute:
22 |
23 | ```shell
24 | pytest --cov-config=.coveragerc --cov=pyfuncol --cov-report=xml
25 | ```
26 |
27 | ## Docs
28 |
29 | The docs are hosted on [Read the Docs](https://pyfuncol.readthedocs.io/en/latest/). Source files are in `docs/source/`.
30 |
31 | To build them locally, run in `docs/`:
32 |
33 | ```shell
34 | make html
35 | ```
36 |
37 | The HTML files will be stored in `docs/build/`.
38 |
39 | ## Project structure
40 |
41 | ```
42 | ┌── docs - Contains the docs source code
43 | ├── pyfuncol - Contains all the library source code
44 | ├── tests - Contains tests for all the modules
45 | ├── __init__.py - Contains the function calls that extend built-in types
46 | ├── dict.py - Contains extension functions for dictionaries
47 | ├── list.py - Contains extension functions for lists
48 | └── ...
49 | └── ...
50 | ```
51 |
52 | ## Release
53 |
54 | To publish a new release on [PyPI](https://pypi.org/project/pyfuncol/):
55 |
56 | 1. Update the version in `setup.py`
57 | 2. Update the version (`release` field) in `docs/source/conf.py`
58 | 3. Push the version bump
59 | 4. Create a new release on [GitHub](https://github.com/didactic-meme/pyfuncol/releases). The newly created tag and the release title should match the version in `setup.py` and `docs/source/conf.py` with 'v' prepended. An example: for version `1.1.1`, the tag and release title should be `v1.1.1`.
60 |
61 | The GitHub release creation will trigger the deploy workflow that builds and uploads the project to PyPI.
62 |
63 | ## Code of Conduct
64 |
65 | Our Code of Conduct is [here](https://github.com/didactic-meme/pyfuncol/blob/main/CODE_OF_CONDUCT.md). By contributing to pyfuncol, you implicitly accept it.
66 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Andrea Veneziano
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pyfuncol
2 |
3 | 
4 | [](https://codecov.io/gh/didactic-meme/pyfuncol)
5 | 
6 | [](https://pepy.tech/project/pyfuncol)
7 | [](https://pyfuncol.readthedocs.io/en/latest/?badge=latest)
8 | [](https://github.com/didactic-meme/pyfuncol/blob/main/LICENSE)
9 |
10 | - [pyfuncol](#pyfuncol)
11 | - [Installation](#installation)
12 | - [Usage](#usage)
13 | - [Usage without forbiddenfruit](#usage-without-forbiddenfruit)
14 | - [API](#api)
15 | - [Documentation](#documentation)
16 | - [Compatibility](#compatibility)
17 | - [Contributing](#contributing)
18 | - [License](#license)
19 |
20 | A Python functional collections library. It _extends_ collections built-in types with useful methods to write functional Python code. It uses [Forbidden Fruit](https://github.com/clarete/forbiddenfruit) under the hood.
21 |
22 | pyfuncol provides:
23 |
24 | - Standard "eager" methods, such as `map`, `flat_map`, `group_by`, etc.
25 | - Parallel methods, such as `par_map`, `par_flat_map`, etc.
26 | - Pure methods that leverage memoization to improve performance, such as `pure_map`, `pure_flat_map`, etc.
27 | - Lazy methods that return iterators and never materialize results, such as `lazy_map`, `lazy_flat_map`, etc.
28 |
29 | pyfuncol can also be [used without forbiddenfruit](#usage-without-forbiddenfruit).
30 |
31 | ## Installation
32 |
33 | `pip install pyfuncol`
34 |
35 | ## Usage
36 |
37 | > **Note:** If you are not using forbiddenfruit, the functions will not extend the builtins. Please [see here](#usage-without-forbiddenfruit) for usage without forbiddenfruit.
38 |
39 | To use the methods, you just need to import pyfuncol. Some examples:
40 |
41 | ```python
42 | import pyfuncol
43 |
44 | [1, 2, 3, 4].map(lambda x: x * 2).filter(lambda x: x > 4)
45 | # [6, 8]
46 |
47 | [1, 2, 3, 4].fold_left(0, lambda acc, n: acc + n)
48 | # 10
49 |
50 | {1, 2, 3, 4}.map(lambda x: x * 2).filter_not(lambda x: x <= 4)
51 | # {6, 8}
52 |
53 | ["abc", "def", "e"].group_by(lambda s: len(s))
54 | # {3: ["abc", "def"], 1: ["e"]}
55 |
56 | {"a": 1, "b": 2, "c": 3}.flat_map(lambda kv: {kv[0]: kv[1] ** 2})
57 | # {"a": 1, "b": 4, "c": 9}
58 | ```
59 |
60 | pyfuncol provides parallel operations (for now `par_map`, `par_flat_map`, `par_filter` and `par_filter_not`):
61 |
62 | ```python
63 | [1, 2, 3, 4].par_map(lambda x: x * 2).par_filter(lambda x: x > 4)
64 | # [6, 8]
65 |
66 | {1, 2, 3, 4}.par_map(lambda x: x * 2).par_filter_not(lambda x: x <= 4)
67 | # {6, 8}
68 |
69 | {"a": 1, "b": 2, "c": 3}.par_flat_map(lambda kv: {kv[0]: kv[1] ** 2})
70 | # {"a": 1, "b": 4, "c": 9}
71 | ```
72 |
73 | pyfuncol provides operations leveraging memoization to improve performance (for now `pure_map`, `pure_flat_map`, `pure_filter` and `pure_filter_not`). These versions work only for **pure** functions (i.e., all calls to the same args return the same value) on hashable inputs:
74 |
75 | ```python
76 | [1, 2, 3, 4].pure_map(lambda x: x * 2).pure_filter(lambda x: x > 4)
77 | # [6, 8]
78 |
79 | {1, 2, 3, 4}.pure_map(lambda x: x * 2).pure_filter_not(lambda x: x <= 4)
80 | # {6, 8}
81 |
82 | {"a": 1, "b": 2, "c": 3}.pure_flat_map(lambda kv: {kv[0]: kv[1] ** 2})
83 | # {"a": 1, "b": 4, "c": 9}
84 | ```
85 |
86 | pyfuncol provides lazy operations that never materialize results:
87 |
88 | ```python
89 | list([1, 2, 3, 4].lazy_map(lambda x: x * 2).lazy_filter(lambda x: x > 4))
90 | # [6, 8]
91 |
92 | list({1, 2, 3, 4}.lazy_map(lambda x: x * 2).lazy_filter_not(lambda x: x <= 4))
93 | # [6, 8]
94 |
95 | list({"a": 1, "b": 2, "c": 3}.lazy_flat_map(lambda kv: {kv[0]: kv[1] ** 2}))
96 | # [("a", 1), ("b", 4), ("c", 9)]
97 |
98 | set([1, 2, 3, 4].lazy_map(lambda x: x * 2).lazy_filter(lambda x: x > 4))
99 | # {6, 8}
100 | ```
101 |
102 | ### Usage without forbiddenfruit
103 |
104 | If you are using a Python interpreter other than CPython, forbiddenfruit will not work.
105 |
106 | Fortunately, if forbiddenfruit does not work on your installation or if you do not want to use it, pyfuncol also supports direct function calls without extending builtins.
107 |
108 | ```python
109 | from pyfuncol import list as pfclist
110 |
111 | pfclist.map([1, 2, 3], lambda x: x * 2)
112 | # [2, 4, 6]
113 | ```
114 |
115 | ### API
116 |
117 | For lists, please refer to the [docs](https://pyfuncol.readthedocs.io/en/latest/pyfuncol.html#module-pyfuncol.list).
118 |
119 | For dictionaries, please refer to the [docs](https://pyfuncol.readthedocs.io/en/latest/pyfuncol.html#module-pyfuncol.dict).
120 |
121 | For sets and frozensets, please refer to the [docs](https://pyfuncol.readthedocs.io/en/latest/pyfuncol.html#module-pyfuncol.set).
122 |
123 | For more details, please have a look at the [API reference](https://pyfuncol.readthedocs.io/en/latest/modules.html).
124 |
125 | We support all subclasses with default constructors (`OrderedDict`, for example).
126 |
127 | ## Documentation
128 |
129 | See .
130 |
131 | ## Compatibility
132 |
133 | For functions to extend built-ins, [Forbidden Fruit](https://github.com/clarete/forbiddenfruit) is necessary (CPython only).
134 |
135 | ## Contributing
136 |
137 | See the [contributing guide](https://github.com/didactic-meme/pyfuncol/blob/main/CONTRIBUTING.md) for detailed instructions on how to get started with the project.
138 |
139 | ## License
140 |
141 | pyfuncol is licensed under the [MIT license](https://github.com/didactic-meme/pyfuncol/blob/main/LICENSE).
142 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/__init__.py
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | patch: off
4 | project:
5 | default:
6 | # basic
7 | target: 95%
8 |
--------------------------------------------------------------------------------
/development.txt:
--------------------------------------------------------------------------------
1 | -r requirements.txt
2 | black==23.1.0
3 | pytest==7.2.2
4 | pytest-cov==4.0.0
5 | myst-parser==1.0.0
6 | Sphinx==6.1.3
7 | sphinx-rtd-theme==1.2.0
8 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.https://www.sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/source/_build/doctrees/contributing.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/doctrees/contributing.doctree
--------------------------------------------------------------------------------
/docs/source/_build/doctrees/environment.pickle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/doctrees/environment.pickle
--------------------------------------------------------------------------------
/docs/source/_build/doctrees/examples.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/doctrees/examples.doctree
--------------------------------------------------------------------------------
/docs/source/_build/doctrees/get_started.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/doctrees/get_started.doctree
--------------------------------------------------------------------------------
/docs/source/_build/doctrees/index.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/doctrees/index.doctree
--------------------------------------------------------------------------------
/docs/source/_build/doctrees/introduction.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/doctrees/introduction.doctree
--------------------------------------------------------------------------------
/docs/source/_build/doctrees/modules.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/doctrees/modules.doctree
--------------------------------------------------------------------------------
/docs/source/_build/doctrees/pyfuncol.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/doctrees/pyfuncol.doctree
--------------------------------------------------------------------------------
/docs/source/_build/html/.buildinfo:
--------------------------------------------------------------------------------
1 | # Sphinx build info version 1
2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
3 | config: 81d4c97122ee566d295f0a6a4f07523d
4 | tags: 645f666f9bcd5a90fca523b33c5a78b7
5 |
--------------------------------------------------------------------------------
/docs/source/_build/html/.doctrees/environment.pickle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/.doctrees/environment.pickle
--------------------------------------------------------------------------------
/docs/source/_build/html/.doctrees/index.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/.doctrees/index.doctree
--------------------------------------------------------------------------------
/docs/source/_build/html/.doctrees/introduction.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/.doctrees/introduction.doctree
--------------------------------------------------------------------------------
/docs/source/_build/html/_modules/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Overview: module code — pyfuncol 1.3.1 documentation
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
48 |
49 |
50 |
51 |
52 | pyfuncol
53 |
54 |
55 |
56 |
57 |
58 |
59 | »
60 | Overview: module code
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
All modules for which code is available
70 |
74 |
75 |
76 |
77 |
91 |
92 |
93 |
94 |
95 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/docs/source/_build/html/_sources/contributing.md.txt:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Setup
4 |
5 | Fork the repo, then install all development requirements with:
6 |
7 | ```shell
8 | pip install -r development.txt
9 | ```
10 |
11 | When your changes are ready, submit a pull request!
12 |
13 | ## Style
14 |
15 | For formatting and code style, we use [black](https://github.com/psf/black). Docstrings should follow the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings).
16 |
17 | ## Tests
18 |
19 | To run the tests, execute `pytest` at the root of the project.
20 |
21 | To run the tests with coverage enabled, execute:
22 |
23 | ```shell
24 | pytest --cov-config=.coveragerc --cov=pyfuncol --cov-report=xml
25 | ```
26 |
27 | ## Docs
28 |
29 | The docs are hosted on [Read the Docs](https://pyfuncol.readthedocs.io/en/latest/). Source files are in `docs/source/`.
30 |
31 | To build them locally, run in `docs/`:
32 |
33 | ```shell
34 | make html
35 | ```
36 |
37 | The HTML files will be stored in `docs/build/`.
38 |
39 | ## Project structure
40 |
41 | ```
42 | ┌── docs - Contains the docs source code
43 | ├── pyfuncol - Contains all the library source code
44 | ├── tests - Contains tests for all the modules
45 | ├── __init__.py - Contains the function calls that extend built-in types
46 | ├── dict.py - Contains extension functions for dictionaries
47 | ├── list.py - Contains extension functions for lists
48 | └── ...
49 | └── ...
50 | ```
51 |
52 | ## Release
53 |
54 | To publish a new release on [PyPI](https://pypi.org/project/pyfuncol/):
55 |
56 | 1. Update the version in `setup.py`
57 | 2. Update the version (`release` field) in `docs/source/conf.py`
58 | 3. Create a new release on [GitHub](https://github.com/didactic-meme/pyfuncol/releases). The newly created tag and the release title should match the version in `setup.py` and `docs/source/conf.py` with 'v' prepended. An example: for version `1.1.1`, the tag and release title should be `v1.1.1`.
59 |
60 | The GitHub release creation will trigger the deploy workflow that builds and uploads the project to PyPI.
61 |
62 | ## Code of Conduct
63 |
64 | Our Code of Conduct is [here](https://github.com/didactic-meme/pyfuncol/blob/main/CODE_OF_CONDUCT.md). By contributing to pyfuncol, you implicitly accept it.
65 |
--------------------------------------------------------------------------------
/docs/source/_build/html/_sources/examples.md.txt:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | > **Note:** If you are not using forbiddenfruit, the functions will not extend the builtins. Please [see here](#usage-without-forbiddenfruit) for usage without forbiddenfruit
4 |
5 | To use the methods, you just need to import pyfuncol. Some examples:
6 |
7 | ```python
8 | import pyfuncol
9 |
10 | [1, 2, 3, 4].map(lambda x: x * 2).filter(lambda x: x > 4)
11 | # [6, 8]
12 |
13 | [1, 2, 3, 4].fold_left(0, lambda acc, n: acc + n)
14 | # 10
15 |
16 | {1, 2, 3, 4}.map(lambda x: x * 2).filter_not(lambda x: x <= 4)
17 | # {6, 8}
18 |
19 | ["abc", "def", "e"].group_by(lambda s: len(s))
20 | # {3: ["abc", "def"], 1: ["e"]}
21 |
22 | {"a": 1, "b": 2, "c": 3}.flat_map(lambda kv: {kv[0]: kv[1] ** 2})
23 | # {"a": 1, "b": 4, "c": 9}
24 | ```
25 |
26 | pyfuncol provides parallel operations (for now `par_map`, `par_flat_map`, `par_filter` and `par_filter_not`):
27 |
28 | ```python
29 | [1, 2, 3, 4].par_map(lambda x: x * 2).par_filter(lambda x: x > 4)
30 | # [6, 8]
31 |
32 | {1, 2, 3, 4}.par_map(lambda x: x * 2).par_filter_not(lambda x: x <= 4)
33 | # {6, 8}
34 |
35 | {"a": 1, "b": 2, "c": 3}.par_flat_map(lambda kv: {kv[0]: kv[1] ** 2})
36 | # {"a": 1, "b": 4, "c": 9}
37 | ```
38 |
39 | pyfuncol provides operations leveraging memoization to improve performance (for now `pure_map`, `pure_flat_map`, `pure_filter` and `pure_filter_not`). These versions work only for **pure** functions (i.e., all calls to the same args return the same value) on hashable inputs:
40 |
41 | ```python
42 | [1, 2, 3, 4].pure_map(lambda x: x * 2).pure_filter(lambda x: x > 4)
43 | # [6, 8]
44 |
45 | {1, 2, 3, 4}.pure_map(lambda x: x * 2).pure_filter_not(lambda x: x <= 4)
46 | # {6, 8}
47 |
48 | {"a": 1, "b": 2, "c": 3}.pure_flat_map(lambda kv: {kv[0]: kv[1] ** 2})
49 | # {"a": 1, "b": 4, "c": 9}
50 | ```
51 |
52 | pyfuncol provides lazy operations that never materialize results:
53 |
54 | ```python
55 | list([1, 2, 3, 4].lazy_map(lambda x: x * 2).lazy_filter(lambda x: x > 4))
56 | # [6, 8]
57 |
58 | list({1, 2, 3, 4}.lazy_map(lambda x: x * 2).lazy_filter_not(lambda x: x <= 4))
59 | # [6, 8]
60 |
61 | list({"a": 1, "b": 2, "c": 3}.lazy_flat_map(lambda kv: {kv[0]: kv[1] ** 2}))
62 | # [("a", 1), ("b", 4), ("c", 9)]
63 |
64 | set([1, 2, 3, 4].lazy_map(lambda x: x * 2).lazy_filter(lambda x: x > 4))
65 | # {6, 8}
66 | ```
67 |
68 | We support all subclasses with default constructors (`OrderedDict`, for example).
69 |
70 | (usage-without-forbiddenfruit)=
71 | ## Usage without forbiddenfruit
72 |
73 | If you are using a Python intepreter other than CPython, forbiddenfruit will not work.
74 |
75 | Fortunately, if forbiddenfruit does not work on your installation or if you do not want to use it, pyfuncol also supports direct function calls without extending builtins.
76 |
77 | ```python
78 | from pyfuncol import list as pfclist
79 |
80 | pfclist.map([1, 2, 3], lambda x: x * 2)
81 | # [2, 4, 6]
82 | ```
83 |
--------------------------------------------------------------------------------
/docs/source/_build/html/_sources/get_started.md.txt:
--------------------------------------------------------------------------------
1 | # Get Started
2 |
3 | pyfuncol is supported only on Python 3.6+ and CPython (+ forbiddenfruit) is required for extending builtins. Albeit untested, it might still work on older Python versions.
4 |
5 | To install it, run:
6 |
7 | ```shell
8 | pip install pyfuncol
9 | ```
10 |
--------------------------------------------------------------------------------
/docs/source/_build/html/_sources/index.rst.txt:
--------------------------------------------------------------------------------
1 | .. pyfuncol documentation master file, created by
2 | sphinx-quickstart on Sat Dec 18 23:25:15 2021.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to pyfuncol's documentation!
7 | ====================================
8 |
9 | .. toctree::
10 | :maxdepth: 2
11 | :caption: Contents
12 |
13 | introduction
14 | get_started
15 | examples
16 | contributing
17 | modules
18 |
19 |
20 |
21 | Indices and tables
22 | ==================
23 |
24 | * :ref:`genindex`
25 | * :ref:`modindex`
26 | * :ref:`search`
27 |
--------------------------------------------------------------------------------
/docs/source/_build/html/_sources/introduction.md.txt:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | [pyfuncol](https://github.com/didactic-meme/pyfuncol) is a Python functional collections library. It _extends_ collections built-in types with useful methods to write functional Python code. It uses [Forbidden Fruit](https://github.com/clarete/forbiddenfruit) under the hood.
4 |
5 | pyfuncol provides:
6 |
7 | - Standard "eager" methods, such as `map`, `flat_map`, `group_by`, etc.
8 | - Parallel methods, such as `par_map`, `par_flat_map`, etc.
9 | - Pure methods that leverage memoization to improve performance, such as `pure_map`, `pure_flat_map`, etc.
10 | - Lazy methods that return iterators and never materialize results, such as `lazy_map`, `lazy_flat_map`, etc.
11 |
12 | pyfuncol can also be [used without forbiddenfruit](usage-without-forbiddenfruit).
13 |
--------------------------------------------------------------------------------
/docs/source/_build/html/_sources/modules.rst.txt:
--------------------------------------------------------------------------------
1 | API reference
2 | =============
3 |
4 | .. toctree::
5 | :maxdepth: 4
6 |
7 | pyfuncol
8 |
--------------------------------------------------------------------------------
/docs/source/_build/html/_sources/pyfuncol.rst.txt:
--------------------------------------------------------------------------------
1 | pyfuncol package
2 | ================
3 |
4 | Submodules
5 | ----------
6 |
7 | pyfuncol.dict module
8 | --------------------
9 |
10 | .. automodule:: pyfuncol.dict
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
15 | pyfuncol.list module
16 | --------------------
17 |
18 | .. automodule:: pyfuncol.list
19 | :members:
20 | :undoc-members:
21 | :show-inheritance:
22 |
23 | pyfuncol.set module
24 | --------------------
25 |
26 | .. automodule:: pyfuncol.set
27 | :members:
28 | :undoc-members:
29 | :show-inheritance:
30 |
31 |
32 | Module contents
33 | ---------------
34 |
35 | .. automodule:: pyfuncol
36 | :members:
37 | :undoc-members:
38 | :show-inheritance:
39 |
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/alabaster.css:
--------------------------------------------------------------------------------
1 | @import url("basic.css");
2 |
3 | /* -- page layout ----------------------------------------------------------- */
4 |
5 | body {
6 | font-family: Georgia, serif;
7 | font-size: 17px;
8 | background-color: #fff;
9 | color: #000;
10 | margin: 0;
11 | padding: 0;
12 | }
13 |
14 |
15 | div.document {
16 | width: 940px;
17 | margin: 30px auto 0 auto;
18 | }
19 |
20 | div.documentwrapper {
21 | float: left;
22 | width: 100%;
23 | }
24 |
25 | div.bodywrapper {
26 | margin: 0 0 0 220px;
27 | }
28 |
29 | div.sphinxsidebar {
30 | width: 220px;
31 | font-size: 14px;
32 | line-height: 1.5;
33 | }
34 |
35 | hr {
36 | border: 1px solid #B1B4B6;
37 | }
38 |
39 | div.body {
40 | background-color: #fff;
41 | color: #3E4349;
42 | padding: 0 30px 0 30px;
43 | }
44 |
45 | div.body > .section {
46 | text-align: left;
47 | }
48 |
49 | div.footer {
50 | width: 940px;
51 | margin: 20px auto 30px auto;
52 | font-size: 14px;
53 | color: #888;
54 | text-align: right;
55 | }
56 |
57 | div.footer a {
58 | color: #888;
59 | }
60 |
61 | p.caption {
62 | font-family: inherit;
63 | font-size: inherit;
64 | }
65 |
66 |
67 | div.relations {
68 | display: none;
69 | }
70 |
71 |
72 | div.sphinxsidebar a {
73 | color: #444;
74 | text-decoration: none;
75 | border-bottom: 1px dotted #999;
76 | }
77 |
78 | div.sphinxsidebar a:hover {
79 | border-bottom: 1px solid #999;
80 | }
81 |
82 | div.sphinxsidebarwrapper {
83 | padding: 18px 10px;
84 | }
85 |
86 | div.sphinxsidebarwrapper p.logo {
87 | padding: 0;
88 | margin: -10px 0 0 0px;
89 | text-align: center;
90 | }
91 |
92 | div.sphinxsidebarwrapper h1.logo {
93 | margin-top: -10px;
94 | text-align: center;
95 | margin-bottom: 5px;
96 | text-align: left;
97 | }
98 |
99 | div.sphinxsidebarwrapper h1.logo-name {
100 | margin-top: 0px;
101 | }
102 |
103 | div.sphinxsidebarwrapper p.blurb {
104 | margin-top: 0;
105 | font-style: normal;
106 | }
107 |
108 | div.sphinxsidebar h3,
109 | div.sphinxsidebar h4 {
110 | font-family: Georgia, serif;
111 | color: #444;
112 | font-size: 24px;
113 | font-weight: normal;
114 | margin: 0 0 5px 0;
115 | padding: 0;
116 | }
117 |
118 | div.sphinxsidebar h4 {
119 | font-size: 20px;
120 | }
121 |
122 | div.sphinxsidebar h3 a {
123 | color: #444;
124 | }
125 |
126 | div.sphinxsidebar p.logo a,
127 | div.sphinxsidebar h3 a,
128 | div.sphinxsidebar p.logo a:hover,
129 | div.sphinxsidebar h3 a:hover {
130 | border: none;
131 | }
132 |
133 | div.sphinxsidebar p {
134 | color: #555;
135 | margin: 10px 0;
136 | }
137 |
138 | div.sphinxsidebar ul {
139 | margin: 10px 0;
140 | padding: 0;
141 | color: #000;
142 | }
143 |
144 | div.sphinxsidebar ul li.toctree-l1 > a {
145 | font-size: 120%;
146 | }
147 |
148 | div.sphinxsidebar ul li.toctree-l2 > a {
149 | font-size: 110%;
150 | }
151 |
152 | div.sphinxsidebar input {
153 | border: 1px solid #CCC;
154 | font-family: Georgia, serif;
155 | font-size: 1em;
156 | }
157 |
158 | div.sphinxsidebar hr {
159 | border: none;
160 | height: 1px;
161 | color: #AAA;
162 | background: #AAA;
163 |
164 | text-align: left;
165 | margin-left: 0;
166 | width: 50%;
167 | }
168 |
169 | div.sphinxsidebar .badge {
170 | border-bottom: none;
171 | }
172 |
173 | div.sphinxsidebar .badge:hover {
174 | border-bottom: none;
175 | }
176 |
177 | /* To address an issue with donation coming after search */
178 | div.sphinxsidebar h3.donation {
179 | margin-top: 10px;
180 | }
181 |
182 | /* -- body styles ----------------------------------------------------------- */
183 |
184 | a {
185 | color: #004B6B;
186 | text-decoration: underline;
187 | }
188 |
189 | a:hover {
190 | color: #6D4100;
191 | text-decoration: underline;
192 | }
193 |
194 | div.body h1,
195 | div.body h2,
196 | div.body h3,
197 | div.body h4,
198 | div.body h5,
199 | div.body h6 {
200 | font-family: Georgia, serif;
201 | font-weight: normal;
202 | margin: 30px 0px 10px 0px;
203 | padding: 0;
204 | }
205 |
206 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
207 | div.body h2 { font-size: 180%; }
208 | div.body h3 { font-size: 150%; }
209 | div.body h4 { font-size: 130%; }
210 | div.body h5 { font-size: 100%; }
211 | div.body h6 { font-size: 100%; }
212 |
213 | a.headerlink {
214 | color: #DDD;
215 | padding: 0 4px;
216 | text-decoration: none;
217 | }
218 |
219 | a.headerlink:hover {
220 | color: #444;
221 | background: #EAEAEA;
222 | }
223 |
224 | div.body p, div.body dd, div.body li {
225 | line-height: 1.4em;
226 | }
227 |
228 | div.admonition {
229 | margin: 20px 0px;
230 | padding: 10px 30px;
231 | background-color: #EEE;
232 | border: 1px solid #CCC;
233 | }
234 |
235 | div.admonition tt.xref, div.admonition code.xref, div.admonition a tt {
236 | background-color: #FBFBFB;
237 | border-bottom: 1px solid #fafafa;
238 | }
239 |
240 | div.admonition p.admonition-title {
241 | font-family: Georgia, serif;
242 | font-weight: normal;
243 | font-size: 24px;
244 | margin: 0 0 10px 0;
245 | padding: 0;
246 | line-height: 1;
247 | }
248 |
249 | div.admonition p.last {
250 | margin-bottom: 0;
251 | }
252 |
253 | div.highlight {
254 | background-color: #fff;
255 | }
256 |
257 | dt:target, .highlight {
258 | background: #FAF3E8;
259 | }
260 |
261 | div.warning {
262 | background-color: #FCC;
263 | border: 1px solid #FAA;
264 | }
265 |
266 | div.danger {
267 | background-color: #FCC;
268 | border: 1px solid #FAA;
269 | -moz-box-shadow: 2px 2px 4px #D52C2C;
270 | -webkit-box-shadow: 2px 2px 4px #D52C2C;
271 | box-shadow: 2px 2px 4px #D52C2C;
272 | }
273 |
274 | div.error {
275 | background-color: #FCC;
276 | border: 1px solid #FAA;
277 | -moz-box-shadow: 2px 2px 4px #D52C2C;
278 | -webkit-box-shadow: 2px 2px 4px #D52C2C;
279 | box-shadow: 2px 2px 4px #D52C2C;
280 | }
281 |
282 | div.caution {
283 | background-color: #FCC;
284 | border: 1px solid #FAA;
285 | }
286 |
287 | div.attention {
288 | background-color: #FCC;
289 | border: 1px solid #FAA;
290 | }
291 |
292 | div.important {
293 | background-color: #EEE;
294 | border: 1px solid #CCC;
295 | }
296 |
297 | div.note {
298 | background-color: #EEE;
299 | border: 1px solid #CCC;
300 | }
301 |
302 | div.tip {
303 | background-color: #EEE;
304 | border: 1px solid #CCC;
305 | }
306 |
307 | div.hint {
308 | background-color: #EEE;
309 | border: 1px solid #CCC;
310 | }
311 |
312 | div.seealso {
313 | background-color: #EEE;
314 | border: 1px solid #CCC;
315 | }
316 |
317 | div.topic {
318 | background-color: #EEE;
319 | }
320 |
321 | p.admonition-title {
322 | display: inline;
323 | }
324 |
325 | p.admonition-title:after {
326 | content: ":";
327 | }
328 |
329 | pre, tt, code {
330 | font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
331 | font-size: 0.9em;
332 | }
333 |
334 | .hll {
335 | background-color: #FFC;
336 | margin: 0 -12px;
337 | padding: 0 12px;
338 | display: block;
339 | }
340 |
341 | img.screenshot {
342 | }
343 |
344 | tt.descname, tt.descclassname, code.descname, code.descclassname {
345 | font-size: 0.95em;
346 | }
347 |
348 | tt.descname, code.descname {
349 | padding-right: 0.08em;
350 | }
351 |
352 | img.screenshot {
353 | -moz-box-shadow: 2px 2px 4px #EEE;
354 | -webkit-box-shadow: 2px 2px 4px #EEE;
355 | box-shadow: 2px 2px 4px #EEE;
356 | }
357 |
358 | table.docutils {
359 | border: 1px solid #888;
360 | -moz-box-shadow: 2px 2px 4px #EEE;
361 | -webkit-box-shadow: 2px 2px 4px #EEE;
362 | box-shadow: 2px 2px 4px #EEE;
363 | }
364 |
365 | table.docutils td, table.docutils th {
366 | border: 1px solid #888;
367 | padding: 0.25em 0.7em;
368 | }
369 |
370 | table.field-list, table.footnote {
371 | border: none;
372 | -moz-box-shadow: none;
373 | -webkit-box-shadow: none;
374 | box-shadow: none;
375 | }
376 |
377 | table.footnote {
378 | margin: 15px 0;
379 | width: 100%;
380 | border: 1px solid #EEE;
381 | background: #FDFDFD;
382 | font-size: 0.9em;
383 | }
384 |
385 | table.footnote + table.footnote {
386 | margin-top: -15px;
387 | border-top: none;
388 | }
389 |
390 | table.field-list th {
391 | padding: 0 0.8em 0 0;
392 | }
393 |
394 | table.field-list td {
395 | padding: 0;
396 | }
397 |
398 | table.field-list p {
399 | margin-bottom: 0.8em;
400 | }
401 |
402 | /* Cloned from
403 | * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68
404 | */
405 | .field-name {
406 | -moz-hyphens: manual;
407 | -ms-hyphens: manual;
408 | -webkit-hyphens: manual;
409 | hyphens: manual;
410 | }
411 |
412 | table.footnote td.label {
413 | width: .1px;
414 | padding: 0.3em 0 0.3em 0.5em;
415 | }
416 |
417 | table.footnote td {
418 | padding: 0.3em 0.5em;
419 | }
420 |
421 | dl {
422 | margin: 0;
423 | padding: 0;
424 | }
425 |
426 | dl dd {
427 | margin-left: 30px;
428 | }
429 |
430 | blockquote {
431 | margin: 0 0 0 30px;
432 | padding: 0;
433 | }
434 |
435 | ul, ol {
436 | /* Matches the 30px from the narrow-screen "li > ul" selector below */
437 | margin: 10px 0 10px 30px;
438 | padding: 0;
439 | }
440 |
441 | pre {
442 | background: #EEE;
443 | padding: 7px 30px;
444 | margin: 15px 0px;
445 | line-height: 1.3em;
446 | }
447 |
448 | div.viewcode-block:target {
449 | background: #ffd;
450 | }
451 |
452 | dl pre, blockquote pre, li pre {
453 | margin-left: 0;
454 | padding-left: 30px;
455 | }
456 |
457 | tt, code {
458 | background-color: #ecf0f3;
459 | color: #222;
460 | /* padding: 1px 2px; */
461 | }
462 |
463 | tt.xref, code.xref, a tt {
464 | background-color: #FBFBFB;
465 | border-bottom: 1px solid #fff;
466 | }
467 |
468 | a.reference {
469 | text-decoration: none;
470 | border-bottom: 1px dotted #004B6B;
471 | }
472 |
473 | /* Don't put an underline on images */
474 | a.image-reference, a.image-reference:hover {
475 | border-bottom: none;
476 | }
477 |
478 | a.reference:hover {
479 | border-bottom: 1px solid #6D4100;
480 | }
481 |
482 | a.footnote-reference {
483 | text-decoration: none;
484 | font-size: 0.7em;
485 | vertical-align: top;
486 | border-bottom: 1px dotted #004B6B;
487 | }
488 |
489 | a.footnote-reference:hover {
490 | border-bottom: 1px solid #6D4100;
491 | }
492 |
493 | a:hover tt, a:hover code {
494 | background: #EEE;
495 | }
496 |
497 |
498 | @media screen and (max-width: 870px) {
499 |
500 | div.sphinxsidebar {
501 | display: none;
502 | }
503 |
504 | div.document {
505 | width: 100%;
506 |
507 | }
508 |
509 | div.documentwrapper {
510 | margin-left: 0;
511 | margin-top: 0;
512 | margin-right: 0;
513 | margin-bottom: 0;
514 | }
515 |
516 | div.bodywrapper {
517 | margin-top: 0;
518 | margin-right: 0;
519 | margin-bottom: 0;
520 | margin-left: 0;
521 | }
522 |
523 | ul {
524 | margin-left: 0;
525 | }
526 |
527 | li > ul {
528 | /* Matches the 30px from the "ul, ol" selector above */
529 | margin-left: 30px;
530 | }
531 |
532 | .document {
533 | width: auto;
534 | }
535 |
536 | .footer {
537 | width: auto;
538 | }
539 |
540 | .bodywrapper {
541 | margin: 0;
542 | }
543 |
544 | .footer {
545 | width: auto;
546 | }
547 |
548 | .github {
549 | display: none;
550 | }
551 |
552 |
553 |
554 | }
555 |
556 |
557 |
558 | @media screen and (max-width: 875px) {
559 |
560 | body {
561 | margin: 0;
562 | padding: 20px 30px;
563 | }
564 |
565 | div.documentwrapper {
566 | float: none;
567 | background: #fff;
568 | }
569 |
570 | div.sphinxsidebar {
571 | display: block;
572 | float: none;
573 | width: 102.5%;
574 | margin: 50px -30px -20px -30px;
575 | padding: 10px 20px;
576 | background: #333;
577 | color: #FFF;
578 | }
579 |
580 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
581 | div.sphinxsidebar h3 a {
582 | color: #fff;
583 | }
584 |
585 | div.sphinxsidebar a {
586 | color: #AAA;
587 | }
588 |
589 | div.sphinxsidebar p.logo {
590 | display: none;
591 | }
592 |
593 | div.document {
594 | width: 100%;
595 | margin: 0;
596 | }
597 |
598 | div.footer {
599 | display: none;
600 | }
601 |
602 | div.bodywrapper {
603 | margin: 0;
604 | }
605 |
606 | div.body {
607 | min-height: 0;
608 | padding: 0;
609 | }
610 |
611 | .rtd_doc_footer {
612 | display: none;
613 | }
614 |
615 | .document {
616 | width: auto;
617 | }
618 |
619 | .footer {
620 | width: auto;
621 | }
622 |
623 | .footer {
624 | width: auto;
625 | }
626 |
627 | .github {
628 | display: none;
629 | }
630 | }
631 |
632 |
633 | /* misc. */
634 |
635 | .revsys-inline {
636 | display: none!important;
637 | }
638 |
639 | /* Make nested-list/multi-paragraph items look better in Releases changelog
640 | * pages. Without this, docutils' magical list fuckery causes inconsistent
641 | * formatting between different release sub-lists.
642 | */
643 | div#changelog > div.section > ul > li > p:only-child {
644 | margin-bottom: 0;
645 | }
646 |
647 | /* Hide fugly table cell borders in ..bibliography:: directive output */
648 | table.docutils.citation, table.docutils.citation td, table.docutils.citation th {
649 | border: none;
650 | /* Below needed in some edge cases; if not applied, bottom shadows appear */
651 | -moz-box-shadow: none;
652 | -webkit-box-shadow: none;
653 | box-shadow: none;
654 | }
655 |
656 |
657 | /* relbar */
658 |
659 | .related {
660 | line-height: 30px;
661 | width: 100%;
662 | font-size: 0.9rem;
663 | }
664 |
665 | .related.top {
666 | border-bottom: 1px solid #EEE;
667 | margin-bottom: 20px;
668 | }
669 |
670 | .related.bottom {
671 | border-top: 1px solid #EEE;
672 | }
673 |
674 | .related ul {
675 | padding: 0;
676 | margin: 0;
677 | list-style: none;
678 | }
679 |
680 | .related li {
681 | display: inline;
682 | }
683 |
684 | nav#rellinks {
685 | float: right;
686 | }
687 |
688 | nav#rellinks li+li:before {
689 | content: "|";
690 | }
691 |
692 | nav#breadcrumbs li+li:before {
693 | content: "\00BB";
694 | }
695 |
696 | /* Hide certain items when printing */
697 | @media print {
698 | div.related {
699 | display: none;
700 | }
701 | }
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/badge_only.css:
--------------------------------------------------------------------------------
1 | .fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/lato-bold-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/lato-bold-italic.woff
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/lato-bold-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/lato-bold-italic.woff2
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/lato-bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/lato-bold.woff
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/lato-bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/lato-bold.woff2
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/lato-normal-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/lato-normal-italic.woff
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/lato-normal-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/lato-normal-italic.woff2
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/lato-normal.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/lato-normal.woff
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/css/fonts/lato-normal.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/css/fonts/lato-normal.woff2
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/custom.css:
--------------------------------------------------------------------------------
1 | /* This file intentionally left blank. */
2 |
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/doctools.js:
--------------------------------------------------------------------------------
1 | /*
2 | * doctools.js
3 | * ~~~~~~~~~~~
4 | *
5 | * Sphinx JavaScript utilities for all documentation.
6 | *
7 | * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
8 | * :license: BSD, see LICENSE for details.
9 | *
10 | */
11 |
12 | /**
13 | * select a different prefix for underscore
14 | */
15 | $u = _.noConflict();
16 |
17 | /**
18 | * make the code below compatible with browsers without
19 | * an installed firebug like debugger
20 | if (!window.console || !console.firebug) {
21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace",
23 | "profile", "profileEnd"];
24 | window.console = {};
25 | for (var i = 0; i < names.length; ++i)
26 | window.console[names[i]] = function() {};
27 | }
28 | */
29 |
30 | /**
31 | * small helper function to urldecode strings
32 | *
33 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL
34 | */
35 | jQuery.urldecode = function(x) {
36 | if (!x) {
37 | return x
38 | }
39 | return decodeURIComponent(x.replace(/\+/g, ' '));
40 | };
41 |
42 | /**
43 | * small helper function to urlencode strings
44 | */
45 | jQuery.urlencode = encodeURIComponent;
46 |
47 | /**
48 | * This function returns the parsed url parameters of the
49 | * current request. Multiple values per key are supported,
50 | * it will always return arrays of strings for the value parts.
51 | */
52 | jQuery.getQueryParameters = function(s) {
53 | if (typeof s === 'undefined')
54 | s = document.location.search;
55 | var parts = s.substr(s.indexOf('?') + 1).split('&');
56 | var result = {};
57 | for (var i = 0; i < parts.length; i++) {
58 | var tmp = parts[i].split('=', 2);
59 | var key = jQuery.urldecode(tmp[0]);
60 | var value = jQuery.urldecode(tmp[1]);
61 | if (key in result)
62 | result[key].push(value);
63 | else
64 | result[key] = [value];
65 | }
66 | return result;
67 | };
68 |
69 | /**
70 | * highlight a given string on a jquery object by wrapping it in
71 | * span elements with the given class name.
72 | */
73 | jQuery.fn.highlightText = function(text, className) {
74 | function highlight(node, addItems) {
75 | if (node.nodeType === 3) {
76 | var val = node.nodeValue;
77 | var pos = val.toLowerCase().indexOf(text);
78 | if (pos >= 0 &&
79 | !jQuery(node.parentNode).hasClass(className) &&
80 | !jQuery(node.parentNode).hasClass("nohighlight")) {
81 | var span;
82 | var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg");
83 | if (isInSVG) {
84 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
85 | } else {
86 | span = document.createElement("span");
87 | span.className = className;
88 | }
89 | span.appendChild(document.createTextNode(val.substr(pos, text.length)));
90 | node.parentNode.insertBefore(span, node.parentNode.insertBefore(
91 | document.createTextNode(val.substr(pos + text.length)),
92 | node.nextSibling));
93 | node.nodeValue = val.substr(0, pos);
94 | if (isInSVG) {
95 | var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
96 | var bbox = node.parentElement.getBBox();
97 | rect.x.baseVal.value = bbox.x;
98 | rect.y.baseVal.value = bbox.y;
99 | rect.width.baseVal.value = bbox.width;
100 | rect.height.baseVal.value = bbox.height;
101 | rect.setAttribute('class', className);
102 | addItems.push({
103 | "parent": node.parentNode,
104 | "target": rect});
105 | }
106 | }
107 | }
108 | else if (!jQuery(node).is("button, select, textarea")) {
109 | jQuery.each(node.childNodes, function() {
110 | highlight(this, addItems);
111 | });
112 | }
113 | }
114 | var addItems = [];
115 | var result = this.each(function() {
116 | highlight(this, addItems);
117 | });
118 | for (var i = 0; i < addItems.length; ++i) {
119 | jQuery(addItems[i].parent).before(addItems[i].target);
120 | }
121 | return result;
122 | };
123 |
124 | /*
125 | * backward compatibility for jQuery.browser
126 | * This will be supported until firefox bug is fixed.
127 | */
128 | if (!jQuery.browser) {
129 | jQuery.uaMatch = function(ua) {
130 | ua = ua.toLowerCase();
131 |
132 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
133 | /(webkit)[ \/]([\w.]+)/.exec(ua) ||
134 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
135 | /(msie) ([\w.]+)/.exec(ua) ||
136 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
137 | [];
138 |
139 | return {
140 | browser: match[ 1 ] || "",
141 | version: match[ 2 ] || "0"
142 | };
143 | };
144 | jQuery.browser = {};
145 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
146 | }
147 |
148 | /**
149 | * Small JavaScript module for the documentation.
150 | */
151 | var Documentation = {
152 |
153 | init : function() {
154 | this.fixFirefoxAnchorBug();
155 | this.highlightSearchWords();
156 | this.initIndexTable();
157 | if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) {
158 | this.initOnKeyListeners();
159 | }
160 | },
161 |
162 | /**
163 | * i18n support
164 | */
165 | TRANSLATIONS : {},
166 | PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; },
167 | LOCALE : 'unknown',
168 |
169 | // gettext and ngettext don't access this so that the functions
170 | // can safely bound to a different name (_ = Documentation.gettext)
171 | gettext : function(string) {
172 | var translated = Documentation.TRANSLATIONS[string];
173 | if (typeof translated === 'undefined')
174 | return string;
175 | return (typeof translated === 'string') ? translated : translated[0];
176 | },
177 |
178 | ngettext : function(singular, plural, n) {
179 | var translated = Documentation.TRANSLATIONS[singular];
180 | if (typeof translated === 'undefined')
181 | return (n == 1) ? singular : plural;
182 | return translated[Documentation.PLURALEXPR(n)];
183 | },
184 |
185 | addTranslations : function(catalog) {
186 | for (var key in catalog.messages)
187 | this.TRANSLATIONS[key] = catalog.messages[key];
188 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
189 | this.LOCALE = catalog.locale;
190 | },
191 |
192 | /**
193 | * add context elements like header anchor links
194 | */
195 | addContextElements : function() {
196 | $('div[id] > :header:first').each(function() {
197 | $('').
198 | attr('href', '#' + this.id).
199 | attr('title', _('Permalink to this headline')).
200 | appendTo(this);
201 | });
202 | $('dt[id]').each(function() {
203 | $('').
204 | attr('href', '#' + this.id).
205 | attr('title', _('Permalink to this definition')).
206 | appendTo(this);
207 | });
208 | },
209 |
210 | /**
211 | * workaround a firefox stupidity
212 | * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075
213 | */
214 | fixFirefoxAnchorBug : function() {
215 | if (document.location.hash && $.browser.mozilla)
216 | window.setTimeout(function() {
217 | document.location.href += '';
218 | }, 10);
219 | },
220 |
221 | /**
222 | * highlight the search words provided in the url in the text
223 | */
224 | highlightSearchWords : function() {
225 | var params = $.getQueryParameters();
226 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
227 | if (terms.length) {
228 | var body = $('div.body');
229 | if (!body.length) {
230 | body = $('body');
231 | }
232 | window.setTimeout(function() {
233 | $.each(terms, function() {
234 | body.highlightText(this.toLowerCase(), 'highlighted');
235 | });
236 | }, 10);
237 | $('' + _('Hide Search Matches') + '
')
239 | .appendTo($('#searchbox'));
240 | }
241 | },
242 |
243 | /**
244 | * init the domain index toggle buttons
245 | */
246 | initIndexTable : function() {
247 | var togglers = $('img.toggler').click(function() {
248 | var src = $(this).attr('src');
249 | var idnum = $(this).attr('id').substr(7);
250 | $('tr.cg-' + idnum).toggle();
251 | if (src.substr(-9) === 'minus.png')
252 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
253 | else
254 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
255 | }).css('display', '');
256 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
257 | togglers.click();
258 | }
259 | },
260 |
261 | /**
262 | * helper function to hide the search marks again
263 | */
264 | hideSearchWords : function() {
265 | $('#searchbox .highlight-link').fadeOut(300);
266 | $('span.highlighted').removeClass('highlighted');
267 | var url = new URL(window.location);
268 | url.searchParams.delete('highlight');
269 | window.history.replaceState({}, '', url);
270 | },
271 |
272 | /**
273 | * make the url absolute
274 | */
275 | makeURL : function(relativeURL) {
276 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
277 | },
278 |
279 | /**
280 | * get the current relative url
281 | */
282 | getCurrentURL : function() {
283 | var path = document.location.pathname;
284 | var parts = path.split(/\//);
285 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
286 | if (this === '..')
287 | parts.pop();
288 | });
289 | var url = parts.join('/');
290 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
291 | },
292 |
293 | initOnKeyListeners: function() {
294 | $(document).keydown(function(event) {
295 | var activeElementType = document.activeElement.tagName;
296 | // don't navigate when in search box, textarea, dropdown or button
297 | if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT'
298 | && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey
299 | && !event.shiftKey) {
300 | switch (event.keyCode) {
301 | case 37: // left
302 | var prevHref = $('link[rel="prev"]').prop('href');
303 | if (prevHref) {
304 | window.location.href = prevHref;
305 | return false;
306 | }
307 | break;
308 | case 39: // right
309 | var nextHref = $('link[rel="next"]').prop('href');
310 | if (nextHref) {
311 | window.location.href = nextHref;
312 | return false;
313 | }
314 | break;
315 | }
316 | }
317 | });
318 | }
319 | };
320 |
321 | // quick alias for translations
322 | _ = Documentation.gettext;
323 |
324 | $(document).ready(function() {
325 | Documentation.init();
326 | });
327 |
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/documentation_options.js:
--------------------------------------------------------------------------------
1 | var DOCUMENTATION_OPTIONS = {
2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
3 | VERSION: '1.3.1',
4 | LANGUAGE: 'None',
5 | COLLAPSE_INDEX: false,
6 | BUILDER: 'html',
7 | FILE_SUFFIX: '.html',
8 | LINK_SUFFIX: '.html',
9 | HAS_SOURCE: true,
10 | SOURCELINK_SUFFIX: '.txt',
11 | NAVIGATION_WITH_KEYS: false
12 | };
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/file.png
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/js/badge_only.js:
--------------------------------------------------------------------------------
1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}});
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/js/html5shiv-printshiv.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @preserve HTML5 Shiv 3.7.3-pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
3 | */
4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML=" ",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document);
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/js/html5shiv.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
3 | */
4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML=" ",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document);
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/js/theme.js:
--------------------------------------------------------------------------------
1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap(""),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(' '),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0
63 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
64 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
65 | var s_v = "^(" + C + ")?" + v; // vowel in stem
66 |
67 | this.stemWord = function (w) {
68 | var stem;
69 | var suffix;
70 | var firstch;
71 | var origword = w;
72 |
73 | if (w.length < 3)
74 | return w;
75 |
76 | var re;
77 | var re2;
78 | var re3;
79 | var re4;
80 |
81 | firstch = w.substr(0,1);
82 | if (firstch == "y")
83 | w = firstch.toUpperCase() + w.substr(1);
84 |
85 | // Step 1a
86 | re = /^(.+?)(ss|i)es$/;
87 | re2 = /^(.+?)([^s])s$/;
88 |
89 | if (re.test(w))
90 | w = w.replace(re,"$1$2");
91 | else if (re2.test(w))
92 | w = w.replace(re2,"$1$2");
93 |
94 | // Step 1b
95 | re = /^(.+?)eed$/;
96 | re2 = /^(.+?)(ed|ing)$/;
97 | if (re.test(w)) {
98 | var fp = re.exec(w);
99 | re = new RegExp(mgr0);
100 | if (re.test(fp[1])) {
101 | re = /.$/;
102 | w = w.replace(re,"");
103 | }
104 | }
105 | else if (re2.test(w)) {
106 | var fp = re2.exec(w);
107 | stem = fp[1];
108 | re2 = new RegExp(s_v);
109 | if (re2.test(stem)) {
110 | w = stem;
111 | re2 = /(at|bl|iz)$/;
112 | re3 = new RegExp("([^aeiouylsz])\\1$");
113 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
114 | if (re2.test(w))
115 | w = w + "e";
116 | else if (re3.test(w)) {
117 | re = /.$/;
118 | w = w.replace(re,"");
119 | }
120 | else if (re4.test(w))
121 | w = w + "e";
122 | }
123 | }
124 |
125 | // Step 1c
126 | re = /^(.+?)y$/;
127 | if (re.test(w)) {
128 | var fp = re.exec(w);
129 | stem = fp[1];
130 | re = new RegExp(s_v);
131 | if (re.test(stem))
132 | w = stem + "i";
133 | }
134 |
135 | // Step 2
136 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
137 | if (re.test(w)) {
138 | var fp = re.exec(w);
139 | stem = fp[1];
140 | suffix = fp[2];
141 | re = new RegExp(mgr0);
142 | if (re.test(stem))
143 | w = stem + step2list[suffix];
144 | }
145 |
146 | // Step 3
147 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
148 | if (re.test(w)) {
149 | var fp = re.exec(w);
150 | stem = fp[1];
151 | suffix = fp[2];
152 | re = new RegExp(mgr0);
153 | if (re.test(stem))
154 | w = stem + step3list[suffix];
155 | }
156 |
157 | // Step 4
158 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
159 | re2 = /^(.+?)(s|t)(ion)$/;
160 | if (re.test(w)) {
161 | var fp = re.exec(w);
162 | stem = fp[1];
163 | re = new RegExp(mgr1);
164 | if (re.test(stem))
165 | w = stem;
166 | }
167 | else if (re2.test(w)) {
168 | var fp = re2.exec(w);
169 | stem = fp[1] + fp[2];
170 | re2 = new RegExp(mgr1);
171 | if (re2.test(stem))
172 | w = stem;
173 | }
174 |
175 | // Step 5
176 | re = /^(.+?)e$/;
177 | if (re.test(w)) {
178 | var fp = re.exec(w);
179 | stem = fp[1];
180 | re = new RegExp(mgr1);
181 | re2 = new RegExp(meq1);
182 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
183 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
184 | w = stem;
185 | }
186 | re = /ll$/;
187 | re2 = new RegExp(mgr1);
188 | if (re.test(w) && re2.test(w)) {
189 | re = /.$/;
190 | w = w.replace(re,"");
191 | }
192 |
193 | // and turn initial Y back to y
194 | if (firstch == "y")
195 | w = firstch.toLowerCase() + w.substr(1);
196 | return w;
197 | }
198 | }
199 |
200 |
201 |
202 |
203 | var splitChars = (function() {
204 | var result = {};
205 | var singles = [96, 180, 187, 191, 215, 247, 749, 885, 903, 907, 909, 930, 1014, 1648,
206 | 1748, 1809, 2416, 2473, 2481, 2526, 2601, 2609, 2612, 2615, 2653, 2702,
207 | 2706, 2729, 2737, 2740, 2857, 2865, 2868, 2910, 2928, 2948, 2961, 2971,
208 | 2973, 3085, 3089, 3113, 3124, 3213, 3217, 3241, 3252, 3295, 3341, 3345,
209 | 3369, 3506, 3516, 3633, 3715, 3721, 3736, 3744, 3748, 3750, 3756, 3761,
210 | 3781, 3912, 4239, 4347, 4681, 4695, 4697, 4745, 4785, 4799, 4801, 4823,
211 | 4881, 5760, 5901, 5997, 6313, 7405, 8024, 8026, 8028, 8030, 8117, 8125,
212 | 8133, 8181, 8468, 8485, 8487, 8489, 8494, 8527, 11311, 11359, 11687, 11695,
213 | 11703, 11711, 11719, 11727, 11735, 12448, 12539, 43010, 43014, 43019, 43587,
214 | 43696, 43713, 64286, 64297, 64311, 64317, 64319, 64322, 64325, 65141];
215 | var i, j, start, end;
216 | for (i = 0; i < singles.length; i++) {
217 | result[singles[i]] = true;
218 | }
219 | var ranges = [[0, 47], [58, 64], [91, 94], [123, 169], [171, 177], [182, 184], [706, 709],
220 | [722, 735], [741, 747], [751, 879], [888, 889], [894, 901], [1154, 1161],
221 | [1318, 1328], [1367, 1368], [1370, 1376], [1416, 1487], [1515, 1519], [1523, 1568],
222 | [1611, 1631], [1642, 1645], [1750, 1764], [1767, 1773], [1789, 1790], [1792, 1807],
223 | [1840, 1868], [1958, 1968], [1970, 1983], [2027, 2035], [2038, 2041], [2043, 2047],
224 | [2070, 2073], [2075, 2083], [2085, 2087], [2089, 2307], [2362, 2364], [2366, 2383],
225 | [2385, 2391], [2402, 2405], [2419, 2424], [2432, 2436], [2445, 2446], [2449, 2450],
226 | [2483, 2485], [2490, 2492], [2494, 2509], [2511, 2523], [2530, 2533], [2546, 2547],
227 | [2554, 2564], [2571, 2574], [2577, 2578], [2618, 2648], [2655, 2661], [2672, 2673],
228 | [2677, 2692], [2746, 2748], [2750, 2767], [2769, 2783], [2786, 2789], [2800, 2820],
229 | [2829, 2830], [2833, 2834], [2874, 2876], [2878, 2907], [2914, 2917], [2930, 2946],
230 | [2955, 2957], [2966, 2968], [2976, 2978], [2981, 2983], [2987, 2989], [3002, 3023],
231 | [3025, 3045], [3059, 3076], [3130, 3132], [3134, 3159], [3162, 3167], [3170, 3173],
232 | [3184, 3191], [3199, 3204], [3258, 3260], [3262, 3293], [3298, 3301], [3312, 3332],
233 | [3386, 3388], [3390, 3423], [3426, 3429], [3446, 3449], [3456, 3460], [3479, 3481],
234 | [3518, 3519], [3527, 3584], [3636, 3647], [3655, 3663], [3674, 3712], [3717, 3718],
235 | [3723, 3724], [3726, 3731], [3752, 3753], [3764, 3772], [3774, 3775], [3783, 3791],
236 | [3802, 3803], [3806, 3839], [3841, 3871], [3892, 3903], [3949, 3975], [3980, 4095],
237 | [4139, 4158], [4170, 4175], [4182, 4185], [4190, 4192], [4194, 4196], [4199, 4205],
238 | [4209, 4212], [4226, 4237], [4250, 4255], [4294, 4303], [4349, 4351], [4686, 4687],
239 | [4702, 4703], [4750, 4751], [4790, 4791], [4806, 4807], [4886, 4887], [4955, 4968],
240 | [4989, 4991], [5008, 5023], [5109, 5120], [5741, 5742], [5787, 5791], [5867, 5869],
241 | [5873, 5887], [5906, 5919], [5938, 5951], [5970, 5983], [6001, 6015], [6068, 6102],
242 | [6104, 6107], [6109, 6111], [6122, 6127], [6138, 6159], [6170, 6175], [6264, 6271],
243 | [6315, 6319], [6390, 6399], [6429, 6469], [6510, 6511], [6517, 6527], [6572, 6592],
244 | [6600, 6607], [6619, 6655], [6679, 6687], [6741, 6783], [6794, 6799], [6810, 6822],
245 | [6824, 6916], [6964, 6980], [6988, 6991], [7002, 7042], [7073, 7085], [7098, 7167],
246 | [7204, 7231], [7242, 7244], [7294, 7400], [7410, 7423], [7616, 7679], [7958, 7959],
247 | [7966, 7967], [8006, 8007], [8014, 8015], [8062, 8063], [8127, 8129], [8141, 8143],
248 | [8148, 8149], [8156, 8159], [8173, 8177], [8189, 8303], [8306, 8307], [8314, 8318],
249 | [8330, 8335], [8341, 8449], [8451, 8454], [8456, 8457], [8470, 8472], [8478, 8483],
250 | [8506, 8507], [8512, 8516], [8522, 8525], [8586, 9311], [9372, 9449], [9472, 10101],
251 | [10132, 11263], [11493, 11498], [11503, 11516], [11518, 11519], [11558, 11567],
252 | [11622, 11630], [11632, 11647], [11671, 11679], [11743, 11822], [11824, 12292],
253 | [12296, 12320], [12330, 12336], [12342, 12343], [12349, 12352], [12439, 12444],
254 | [12544, 12548], [12590, 12592], [12687, 12689], [12694, 12703], [12728, 12783],
255 | [12800, 12831], [12842, 12880], [12896, 12927], [12938, 12976], [12992, 13311],
256 | [19894, 19967], [40908, 40959], [42125, 42191], [42238, 42239], [42509, 42511],
257 | [42540, 42559], [42592, 42593], [42607, 42622], [42648, 42655], [42736, 42774],
258 | [42784, 42785], [42889, 42890], [42893, 43002], [43043, 43055], [43062, 43071],
259 | [43124, 43137], [43188, 43215], [43226, 43249], [43256, 43258], [43260, 43263],
260 | [43302, 43311], [43335, 43359], [43389, 43395], [43443, 43470], [43482, 43519],
261 | [43561, 43583], [43596, 43599], [43610, 43615], [43639, 43641], [43643, 43647],
262 | [43698, 43700], [43703, 43704], [43710, 43711], [43715, 43738], [43742, 43967],
263 | [44003, 44015], [44026, 44031], [55204, 55215], [55239, 55242], [55292, 55295],
264 | [57344, 63743], [64046, 64047], [64110, 64111], [64218, 64255], [64263, 64274],
265 | [64280, 64284], [64434, 64466], [64830, 64847], [64912, 64913], [64968, 65007],
266 | [65020, 65135], [65277, 65295], [65306, 65312], [65339, 65344], [65371, 65381],
267 | [65471, 65473], [65480, 65481], [65488, 65489], [65496, 65497]];
268 | for (i = 0; i < ranges.length; i++) {
269 | start = ranges[i][0];
270 | end = ranges[i][1];
271 | for (j = start; j <= end; j++) {
272 | result[j] = true;
273 | }
274 | }
275 | return result;
276 | })();
277 |
278 | function splitQuery(query) {
279 | var result = [];
280 | var start = -1;
281 | for (var i = 0; i < query.length; i++) {
282 | if (splitChars[query.charCodeAt(i)]) {
283 | if (start !== -1) {
284 | result.push(query.slice(start, i));
285 | start = -1;
286 | }
287 | } else if (start === -1) {
288 | start = i;
289 | }
290 | }
291 | if (start !== -1) {
292 | result.push(query.slice(start));
293 | }
294 | return result;
295 | }
296 |
297 |
298 |
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/minus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/minus.png
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/_static/plus.png
--------------------------------------------------------------------------------
/docs/source/_build/html/_static/pygments.css:
--------------------------------------------------------------------------------
1 | pre { line-height: 125%; }
2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
6 | .highlight .hll { background-color: #ffffcc }
7 | .highlight { background: #eeffcc; }
8 | .highlight .c { color: #408090; font-style: italic } /* Comment */
9 | .highlight .err { border: 1px solid #FF0000 } /* Error */
10 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */
11 | .highlight .o { color: #666666 } /* Operator */
12 | .highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */
13 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */
14 | .highlight .cp { color: #007020 } /* Comment.Preproc */
15 | .highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */
16 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */
17 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
18 | .highlight .gd { color: #A00000 } /* Generic.Deleted */
19 | .highlight .ge { font-style: italic } /* Generic.Emph */
20 | .highlight .gr { color: #FF0000 } /* Generic.Error */
21 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
22 | .highlight .gi { color: #00A000 } /* Generic.Inserted */
23 | .highlight .go { color: #333333 } /* Generic.Output */
24 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
25 | .highlight .gs { font-weight: bold } /* Generic.Strong */
26 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
27 | .highlight .gt { color: #0044DD } /* Generic.Traceback */
28 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
29 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
30 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
31 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */
32 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
33 | .highlight .kt { color: #902000 } /* Keyword.Type */
34 | .highlight .m { color: #208050 } /* Literal.Number */
35 | .highlight .s { color: #4070a0 } /* Literal.String */
36 | .highlight .na { color: #4070a0 } /* Name.Attribute */
37 | .highlight .nb { color: #007020 } /* Name.Builtin */
38 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
39 | .highlight .no { color: #60add5 } /* Name.Constant */
40 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
41 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
42 | .highlight .ne { color: #007020 } /* Name.Exception */
43 | .highlight .nf { color: #06287e } /* Name.Function */
44 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
45 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
46 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
47 | .highlight .nv { color: #bb60d5 } /* Name.Variable */
48 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
49 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */
50 | .highlight .mb { color: #208050 } /* Literal.Number.Bin */
51 | .highlight .mf { color: #208050 } /* Literal.Number.Float */
52 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */
53 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */
54 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */
55 | .highlight .sa { color: #4070a0 } /* Literal.String.Affix */
56 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
57 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */
58 | .highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */
59 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
60 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */
61 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
62 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
63 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
64 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */
65 | .highlight .sr { color: #235388 } /* Literal.String.Regex */
66 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */
67 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */
68 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
69 | .highlight .fm { color: #06287e } /* Name.Function.Magic */
70 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
71 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
72 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
73 | .highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */
74 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */
--------------------------------------------------------------------------------
/docs/source/_build/html/contributing.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Contributing — pyfuncol 1.3.1 documentation
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
60 |
61 |
62 |
63 |
64 | pyfuncol
65 |
66 |
67 |
68 |
69 |
79 |
80 |
81 |
82 |
83 | Contributing
84 |
85 | Setup
86 | Fork the repo, then install all development requirements with:
87 | pip install -r development.txt
88 |
89 |
90 | When your changes are ready, submit a pull request!
91 |
92 |
96 |
97 | Tests
98 | To run the tests, execute pytest
at the root of the project.
99 | To run the tests with coverage enabled, execute:
100 | pytest --cov-config= .coveragerc --cov= pyfuncol --cov-report= xml
101 |
102 |
103 |
104 |
105 | Docs
106 | The docs are hosted on Read the Docs . Source files are in docs/source/
.
107 | To build them locally, run in docs/
:
108 |
111 | The HTML files will be stored in docs/build/
.
112 |
113 |
114 | Project structure
115 | ┌── docs - Contains the docs source code
116 | ├── pyfuncol - Contains all the library source code
117 | ├── tests - Contains tests for all the modules
118 | ├── __init__.py - Contains the function calls that extend built-in types
119 | ├── dict.py - Contains extension functions for dictionaries
120 | ├── list.py - Contains extension functions for lists
121 | └── ...
122 | └── ...
123 |
124 |
125 |
126 |
127 | Release
128 | To publish a new release on PyPI :
129 |
130 | Update the version in setup.py
131 | Update the version (release
field) in docs/source/conf.py
132 | Create a new release on GitHub . The newly created tag and the release title should match the version in setup.py
and docs/source/conf.py
with ‘v’ prepended. An example: for version 1.1.1
, the tag and release title should be v1.1.1
.
133 |
134 | The GitHub release creation will trigger the deploy workflow that builds and uploads the project to PyPI.
135 |
136 |
137 | Code of Conduct
138 | Our Code of Conduct is here . By contributing to pyfuncol, you implicitly accept it.
139 |
140 |
141 |
142 |
143 |
144 |
145 |
162 |
163 |
164 |
165 |
166 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/docs/source/_build/html/get_started.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Get Started — pyfuncol 1.3.1 documentation
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
51 |
52 |
53 |
54 |
55 | pyfuncol
56 |
57 |
58 |
59 |
60 |
70 |
71 |
72 |
73 |
74 | Get Started
75 | pyfuncol is supported only on Python 3.6+ and CPython (+ forbiddenfruit) is required for extending builtins. Albeit untested, it might still work on older Python versions.
76 | To install it, run:
77 | pip install pyfuncol
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
102 |
103 |
104 |
105 |
106 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/docs/source/_build/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Welcome to pyfuncol’s documentation! — pyfuncol 1.3.1 documentation
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
50 |
51 |
52 |
53 |
54 | pyfuncol
55 |
56 |
57 |
58 |
59 |
60 |
61 | »
62 | Welcome to pyfuncol’s documentation!
63 |
64 | Edit on GitHub
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | Welcome to pyfuncol’s documentation!
74 |
99 |
100 |
101 | Indices and tables
102 |
107 |
108 |
109 |
110 |
111 |
112 |
128 |
129 |
130 |
131 |
132 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/docs/source/_build/html/introduction.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Introduction — pyfuncol 1.3.1 documentation
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
51 |
52 |
53 |
54 |
55 | pyfuncol
56 |
57 |
58 |
59 |
60 |
70 |
71 |
72 |
73 |
74 | Introduction
75 | pyfuncol is a Python functional collections library. It extends collections built-in types with useful methods to write functional Python code. It uses Forbidden Fruit under the hood.
76 | pyfuncol provides:
77 |
78 | Standard “eager” methods, such as map
, flat_map
, group_by
, etc.
79 | Parallel methods, such as par_map
, par_flat_map
, etc.
80 | Pure methods that leverage memoization to improve performance, such as pure_map
, pure_flat_map
, etc.
81 | Lazy methods that return iterators and never materialize results, such as lazy_map
, lazy_flat_map
, etc.
82 |
83 | pyfuncol can also be used without forbiddenfruit .
84 |
85 |
86 |
87 |
88 |
89 |
106 |
107 |
108 |
109 |
110 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/docs/source/_build/html/modules.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | API reference — pyfuncol 1.3.1 documentation
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
54 |
55 |
56 |
57 |
58 | pyfuncol
59 |
60 |
61 |
62 |
63 |
73 |
74 |
75 |
76 |
77 | API reference
78 |
90 |
91 |
92 |
93 |
94 |
95 |
112 |
113 |
114 |
115 |
116 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/docs/source/_build/html/objects.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/docs/source/_build/html/objects.inv
--------------------------------------------------------------------------------
/docs/source/_build/html/py-modindex.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Python Module Index — pyfuncol 1.3.1 documentation
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
51 |
52 |
53 |
54 |
55 | pyfuncol
56 |
57 |
58 |
59 |
60 |
61 |
62 | »
63 | Python Module Index
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
Python Module Index
74 |
75 |
78 |
79 |
105 |
106 |
107 |
108 |
109 |
123 |
124 |
125 |
126 |
127 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/docs/source/_build/html/search.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Search — pyfuncol 1.3.1 documentation
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
51 |
52 |
53 |
54 |
55 | pyfuncol
56 |
57 |
58 |
59 |
60 |
61 |
62 | »
63 | Search
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | Please activate JavaScript to enable the search functionality.
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
101 |
102 |
103 |
104 |
105 |
110 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/docs/source/_build/html/searchindex.js:
--------------------------------------------------------------------------------
1 | Search.setIndex({docnames:["contributing","examples","get_started","index","introduction","modules","pyfuncol"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.viewcode":1,sphinx:56},filenames:["contributing.md","examples.md","get_started.md","index.rst","introduction.md","modules.rst","pyfuncol.rst"],objects:{"":[[6,0,0,"-","pyfuncol"]],"pyfuncol.dict":[[6,1,1,"","contains"],[6,1,1,"","count"],[6,1,1,"","filter"],[6,1,1,"","filter_not"],[6,1,1,"","find"],[6,1,1,"","flat_map"],[6,1,1,"","fold_left"],[6,1,1,"","fold_right"],[6,1,1,"","forall"],[6,1,1,"","foreach"],[6,1,1,"","is_empty"],[6,1,1,"","lazy_filter"],[6,1,1,"","lazy_filter_not"],[6,1,1,"","lazy_flat_map"],[6,1,1,"","lazy_map"],[6,1,1,"","map"],[6,1,1,"","par_filter"],[6,1,1,"","par_filter_not"],[6,1,1,"","par_flat_map"],[6,1,1,"","par_map"],[6,1,1,"","pure_filter"],[6,1,1,"","pure_filter_not"],[6,1,1,"","pure_flat_map"],[6,1,1,"","pure_map"],[6,1,1,"","size"],[6,1,1,"","to_iterator"],[6,1,1,"","to_list"]],"pyfuncol.list":[[6,1,1,"","contains"],[6,1,1,"","distinct"],[6,1,1,"","filter"],[6,1,1,"","filter_not"],[6,1,1,"","find"],[6,1,1,"","flat_map"],[6,1,1,"","flatten"],[6,1,1,"","fold_left"],[6,1,1,"","fold_right"],[6,1,1,"","forall"],[6,1,1,"","foreach"],[6,1,1,"","group_by"],[6,1,1,"","head"],[6,1,1,"","index_of"],[6,1,1,"","is_empty"],[6,1,1,"","lazy_distinct"],[6,1,1,"","lazy_filter"],[6,1,1,"","lazy_filter_not"],[6,1,1,"","lazy_flat_map"],[6,1,1,"","lazy_flatten"],[6,1,1,"","lazy_map"],[6,1,1,"","lazy_take"],[6,1,1,"","length"],[6,1,1,"","map"],[6,1,1,"","par_filter"],[6,1,1,"","par_filter_not"],[6,1,1,"","par_flat_map"],[6,1,1,"","par_map"],[6,1,1,"","pure_filter"],[6,1,1,"","pure_filter_not"],[6,1,1,"","pure_flat_map"],[6,1,1,"","pure_map"],[6,1,1,"","size"],[6,1,1,"","tail"],[6,1,1,"","take"],[6,1,1,"","to_iterator"]],"pyfuncol.set":[[6,1,1,"","contains"],[6,1,1,"","filter"],[6,1,1,"","filter_not"],[6,1,1,"","find"],[6,1,1,"","flat_map"],[6,1,1,"","fold_left"],[6,1,1,"","fold_right"],[6,1,1,"","forall"],[6,1,1,"","foreach"],[6,1,1,"","group_by"],[6,1,1,"","is_empty"],[6,1,1,"","lazy_filter"],[6,1,1,"","lazy_filter_not"],[6,1,1,"","lazy_flat_map"],[6,1,1,"","lazy_map"],[6,1,1,"","length"],[6,1,1,"","map"],[6,1,1,"","par_filter"],[6,1,1,"","par_filter_not"],[6,1,1,"","par_flat_map"],[6,1,1,"","par_map"],[6,1,1,"","pure_filter"],[6,1,1,"","pure_filter_not"],[6,1,1,"","pure_flat_map"],[6,1,1,"","pure_map"],[6,1,1,"","size"],[6,1,1,"","to_iterator"]],pyfuncol:[[6,0,0,"-","dict"],[6,0,0,"-","list"],[6,0,0,"-","set"]]},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"]},objtypes:{"0":"py:module","1":"py:function"},terms:{"0":1,"1":[0,1,6],"10":1,"2":1,"3":[1,2],"4":1,"6":[1,2],"8":1,"9":1,"default":1,"do":[1,6],"function":[0,1,4,6],"import":1,"int":6,"new":[0,6],"return":[1,4,6],"true":6,A:6,By:0,For:0,If:[1,6],It:4,The:[0,6],These:1,To:[0,1,2],__init__:0,abc:1,acc:1,accept:0,accord:6,albeit:2,alia:6,all:[0,1,6],also:[1,4],alwai:6,an:[0,6],ani:6,api:3,appli:6,ar:[0,1,6],arg:1,associ:6,b:[1,6],between:6,binari:6,bind:6,black:0,bool:6,build:[0,6],built:[0,4],builtin:[1,2],c:[1,6],call:[0,1,6],callabl:6,can:4,chang:0,code:[3,4],collect:[4,6],commut:6,comput:6,conduct:3,conf:0,config:0,consecut:6,consist:6,constructor:1,contain:[0,6],content:5,contribut:3,convert:6,correspond:6,count:6,cov:0,coverag:0,coveragerc:0,cpython:[1,2],creat:0,creation:0,d:6,def:1,deploi:0,develop:0,dict:[0,5],dictionari:[0,6],differ:6,direct:1,discrimin:6,distinct:6,doc:3,docstr:0,doe:[1,6],duplic:6,e:[1,6],each:6,eager:4,effect:6,elem:6,element:6,els:6,empti:6,enabl:0,entri:6,etc:4,exampl:[0,3],execut:0,exist:6,extend:[0,1,2,4],extens:0,f:6,fals:6,field:0,file:0,filter:[1,6],filter_not:[1,6],find:6,first:6,flat_map:[1,4,6],flatten:6,fold_left:[1,6],fold_right:6,follow:0,foral:6,forbidden:4,forbiddenfruit:[2,3,4],foreach:6,fork:0,form:6,format:0,fortun:1,from:[1,6],fruit:4,get:3,github:0,given:6,go:6,googl:0,group:6,group_bi:[1,4,6],guid:0,ha:6,hash:6,hashabl:[1,6],head:6,here:[0,1],hold:6,hood:4,host:0,html:0,i:[1,6],ignor:6,implicitli:0,improv:[1,4,6],index:[3,6],index_of:6,indexerror:6,infinit:6,input:[1,6],insert:6,instal:[0,1,2],intepret:1,introduct:3,is_empti:6,iter:[4,6],its:6,just:1,k:6,kei:6,kv:1,lambda:1,lazi:[1,4,6],lazili:6,lazy_distinct:6,lazy_filt:[1,6],lazy_filter_not:[1,6],lazy_flat_map:[1,4,6],lazy_flatten:6,lazy_map:[1,4,6],lazy_tak:6,lead:6,left:6,len:1,length:6,less:6,leverag:[1,4],librari:[0,4],list:[0,1,5],local:0,look:6,mai:6,make:0,map:[1,4,6],match:0,materi:[1,4],memoiz:[1,4,6],method:[1,4],might:[2,6],modul:[0,3,5],must:6,n:[1,6],need:1,neg:6,never:[1,4],newli:0,none:6,note:[1,6],now:1,number:6,occurr:6,older:2,onli:[1,2,6],op:6,oper:[1,6],option:6,order:6,ordereddict:1,other:1,otherwis:6,our:0,p:6,packag:[3,5],page:3,pair:6,par_filt:[1,6],par_filter_not:[1,6],par_flat_map:[1,4,6],par_map:[1,4,6],parallel:[1,4,6],paramet:6,partit:6,perform:[1,4,6],pfclist:1,pip:[0,2],pleas:1,predic:6,prepend:0,project:3,provid:[1,4],publish:0,pull:0,pure:[1,4,6],pure_filt:[1,6],pure_filter_not:[1,6],pure_flat_map:[1,4,6],pure_map:[1,4,6],py:0,pyfuncol:[0,1,2,4,5],pypi:0,pytest:0,python:[0,1,2,4],r:0,rais:6,read:0,readi:0,refer:3,releas:3,repo:0,report:0,request:0,requir:[0,2],rest:6,result:[1,4,6],right:6,root:0,run:[0,2,6],s:1,same:[1,6],satisfi:6,search:3,see:1,select:6,self:6,sequenc:6,set:[1,5],setup:3,should:0,side:6,size:6,some:[1,6],sourc:[0,6],standard:4,start:[3,6],still:2,store:0,structur:3,style:3,subclass:1,submit:0,submodul:5,support:[1,2],tag:0,tail:6,take:6,termin:6,test:[3,6],than:[1,6],them:0,thi:6,titl:0,to_iter:6,to_list:6,trigger:0,tupl:6,txt:0,type:[0,4,6],u:6,under:4,underli:6,unless:6,untest:2,updat:0,upload:0,us:[0,1,4,6],usag:3,v1:0,v:0,valu:[1,6],version:[0,1,2],want:1,warn:6,we:[0,1],when:0,where:6,whether:6,which:6,whole:6,whose:6,without:[3,4,6],work:[1,2],workflow:0,write:4,x1:6,x:1,x_1:6,x_2:6,x_n:6,xml:0,xn:6,you:[0,1],your:[0,1],z:6},titles:["Contributing","Examples","Get Started","Welcome to pyfuncol\u2019s documentation!","Introduction","API reference","pyfuncol package"],titleterms:{api:5,code:0,conduct:0,content:[3,6],contribut:0,dict:6,doc:0,document:3,exampl:1,forbiddenfruit:1,get:2,indic:3,introduct:4,list:6,modul:6,packag:6,project:0,pyfuncol:[3,6],refer:5,releas:0,s:3,set:6,setup:0,start:2,structur:0,style:0,submodul:6,tabl:3,test:0,usag:1,welcom:3,without:1}})
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 | #
13 | import os
14 | import sys
15 |
16 | sys.path.insert(0, os.path.abspath("../.."))
17 |
18 |
19 | # -- Project information -----------------------------------------------------
20 |
21 | project = "pyfuncol"
22 | copyright = "2021, Andrea Veneziano"
23 | author = "Andrea Veneziano"
24 | description = "Functional collections extension functions for Python"
25 |
26 | # The full version, including alpha/beta/rc tags
27 | release = "1.3.1"
28 |
29 |
30 | # -- General configuration ---------------------------------------------------
31 |
32 | # Add any Sphinx extension module names here, as strings. They can be
33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
34 | # ones.
35 | extensions = [
36 | "myst_parser",
37 | "sphinx.ext.autodoc",
38 | "sphinx.ext.napoleon",
39 | "sphinx.ext.autosectionlabel",
40 | "sphinx.ext.viewcode",
41 | ]
42 |
43 | autodoc_typehints = "signature"
44 |
45 | autodoc_default_options = {
46 | "undoc-members": True,
47 | "member-order": "bysource",
48 | "autosummary": True,
49 | }
50 |
51 | # Add any paths that contain templates here, relative to this directory.
52 | templates_path = ["_templates"]
53 |
54 | # List of patterns, relative to source directory, that match files and
55 | # directories to ignore when looking for source files.
56 | # This pattern also affects html_static_path and html_extra_path.
57 | exclude_patterns = []
58 |
59 | # The name of the Pygments (syntax highlighting) style to use.
60 | pygments_style = "sphinx"
61 |
62 | # -- Options for HTML output -------------------------------------------------
63 |
64 | # The theme to use for HTML and HTML Help pages. See the documentation for
65 | # a list of builtin themes.
66 | #
67 | html_theme = "sphinx_rtd_theme"
68 |
69 | # Add any paths that contain custom static files (such as style sheets) here,
70 | # relative to this directory. They are copied after the builtin static files,
71 | # so a file named "default.css" will overwrite the builtin "default.css".
72 | html_static_path = ["_static"]
73 |
74 | html_context = {
75 | "display_github": True,
76 | "github_user": "didactic-meme",
77 | "github_repo": "pyfuncol",
78 | "github_version": "main/docs/",
79 | }
80 |
--------------------------------------------------------------------------------
/docs/source/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Setup
4 |
5 | Fork the repo, then install all development requirements with:
6 |
7 | ```shell
8 | pip install -r development.txt
9 | ```
10 |
11 | When your changes are ready, submit a pull request!
12 |
13 | ## Style
14 |
15 | For formatting and code style, we use [black](https://github.com/psf/black). Docstrings should follow the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings).
16 |
17 | ## Tests
18 |
19 | To run the tests, execute `pytest` at the root of the project.
20 |
21 | To run the tests with coverage enabled, execute:
22 |
23 | ```shell
24 | pytest --cov-config=.coveragerc --cov=pyfuncol --cov-report=xml
25 | ```
26 |
27 | ## Docs
28 |
29 | The docs are hosted on [Read the Docs](https://pyfuncol.readthedocs.io/en/latest/). Source files are in `docs/source/`.
30 |
31 | To build them locally, run in `docs/`:
32 |
33 | ```shell
34 | make html
35 | ```
36 |
37 | The HTML files will be stored in `docs/build/`.
38 |
39 | ## Project structure
40 |
41 | ```
42 | ┌── docs - Contains the docs source code
43 | ├── pyfuncol - Contains all the library source code
44 | ├── tests - Contains tests for all the modules
45 | ├── __init__.py - Contains the function calls that extend built-in types
46 | ├── dict.py - Contains extension functions for dictionaries
47 | ├── list.py - Contains extension functions for lists
48 | └── ...
49 | └── ...
50 | ```
51 |
52 | ## Release
53 |
54 | To publish a new release on [PyPI](https://pypi.org/project/pyfuncol/):
55 |
56 | 1. Update the version in `setup.py`
57 | 2. Update the version (`release` field) in `docs/source/conf.py`
58 | 3. Create a new release on [GitHub](https://github.com/didactic-meme/pyfuncol/releases). The newly created tag and the release title should match the version in `setup.py` and `docs/source/conf.py` with 'v' prepended. An example: for version `1.1.1`, the tag and release title should be `v1.1.1`.
59 |
60 | The GitHub release creation will trigger the deploy workflow that builds and uploads the project to PyPI.
61 |
62 | ## Code of Conduct
63 |
64 | Our Code of Conduct is [here](https://github.com/didactic-meme/pyfuncol/blob/main/CODE_OF_CONDUCT.md). By contributing to pyfuncol, you implicitly accept it.
65 |
--------------------------------------------------------------------------------
/docs/source/examples.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | > **Note:** If you are not using forbiddenfruit, the functions will not extend the builtins. Please [see here](#usage-without-forbiddenfruit) for usage without forbiddenfruit
4 |
5 | To use the methods, you just need to import pyfuncol. Some examples:
6 |
7 | ```python
8 | import pyfuncol
9 |
10 | [1, 2, 3, 4].map(lambda x: x * 2).filter(lambda x: x > 4)
11 | # [6, 8]
12 |
13 | [1, 2, 3, 4].fold_left(0, lambda acc, n: acc + n)
14 | # 10
15 |
16 | {1, 2, 3, 4}.map(lambda x: x * 2).filter_not(lambda x: x <= 4)
17 | # {6, 8}
18 |
19 | ["abc", "def", "e"].group_by(lambda s: len(s))
20 | # {3: ["abc", "def"], 1: ["e"]}
21 |
22 | {"a": 1, "b": 2, "c": 3}.flat_map(lambda kv: {kv[0]: kv[1] ** 2})
23 | # {"a": 1, "b": 4, "c": 9}
24 | ```
25 |
26 | pyfuncol provides parallel operations (for now `par_map`, `par_flat_map`, `par_filter` and `par_filter_not`):
27 |
28 | ```python
29 | [1, 2, 3, 4].par_map(lambda x: x * 2).par_filter(lambda x: x > 4)
30 | # [6, 8]
31 |
32 | {1, 2, 3, 4}.par_map(lambda x: x * 2).par_filter_not(lambda x: x <= 4)
33 | # {6, 8}
34 |
35 | {"a": 1, "b": 2, "c": 3}.par_flat_map(lambda kv: {kv[0]: kv[1] ** 2})
36 | # {"a": 1, "b": 4, "c": 9}
37 | ```
38 |
39 | pyfuncol provides operations leveraging memoization to improve performance (for now `pure_map`, `pure_flat_map`, `pure_filter` and `pure_filter_not`). These versions work only for **pure** functions (i.e., all calls to the same args return the same value) on hashable inputs:
40 |
41 | ```python
42 | [1, 2, 3, 4].pure_map(lambda x: x * 2).pure_filter(lambda x: x > 4)
43 | # [6, 8]
44 |
45 | {1, 2, 3, 4}.pure_map(lambda x: x * 2).pure_filter_not(lambda x: x <= 4)
46 | # {6, 8}
47 |
48 | {"a": 1, "b": 2, "c": 3}.pure_flat_map(lambda kv: {kv[0]: kv[1] ** 2})
49 | # {"a": 1, "b": 4, "c": 9}
50 | ```
51 |
52 | pyfuncol provides lazy operations that never materialize results:
53 |
54 | ```python
55 | list([1, 2, 3, 4].lazy_map(lambda x: x * 2).lazy_filter(lambda x: x > 4))
56 | # [6, 8]
57 |
58 | list({1, 2, 3, 4}.lazy_map(lambda x: x * 2).lazy_filter_not(lambda x: x <= 4))
59 | # [6, 8]
60 |
61 | list({"a": 1, "b": 2, "c": 3}.lazy_flat_map(lambda kv: {kv[0]: kv[1] ** 2}))
62 | # [("a", 1), ("b", 4), ("c", 9)]
63 |
64 | set([1, 2, 3, 4].lazy_map(lambda x: x * 2).lazy_filter(lambda x: x > 4))
65 | # {6, 8}
66 | ```
67 |
68 | We support all subclasses with default constructors (`OrderedDict`, for example).
69 |
70 | (usage-without-forbiddenfruit)=
71 | ## Usage without forbiddenfruit
72 |
73 | If you are using a Python intepreter other than CPython, forbiddenfruit will not work.
74 |
75 | Fortunately, if forbiddenfruit does not work on your installation or if you do not want to use it, pyfuncol also supports direct function calls without extending builtins.
76 |
77 | ```python
78 | from pyfuncol import list as pfclist
79 |
80 | pfclist.map([1, 2, 3], lambda x: x * 2)
81 | # [2, 4, 6]
82 | ```
83 |
--------------------------------------------------------------------------------
/docs/source/get_started.md:
--------------------------------------------------------------------------------
1 | # Get Started
2 |
3 | pyfuncol is supported only on Python 3.6+ and CPython (+ forbiddenfruit) is required for extending builtins. Albeit untested, it might still work on older Python versions.
4 |
5 | To install it, run:
6 |
7 | ```shell
8 | pip install pyfuncol
9 | ```
10 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | .. pyfuncol documentation master file, created by
2 | sphinx-quickstart on Sat Dec 18 23:25:15 2021.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to pyfuncol's documentation!
7 | ====================================
8 |
9 | .. toctree::
10 | :maxdepth: 2
11 | :caption: Contents
12 |
13 | introduction
14 | get_started
15 | examples
16 | contributing
17 | modules
18 |
19 |
20 |
21 | Indices and tables
22 | ==================
23 |
24 | * :ref:`genindex`
25 | * :ref:`modindex`
26 | * :ref:`search`
27 |
--------------------------------------------------------------------------------
/docs/source/introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | [pyfuncol](https://github.com/didactic-meme/pyfuncol) is a Python functional collections library. It _extends_ collections built-in types with useful methods to write functional Python code. It uses [Forbidden Fruit](https://github.com/clarete/forbiddenfruit) under the hood.
4 |
5 | pyfuncol provides:
6 |
7 | - Standard "eager" methods, such as `map`, `flat_map`, `group_by`, etc.
8 | - Parallel methods, such as `par_map`, `par_flat_map`, etc.
9 | - Pure methods that leverage memoization to improve performance, such as `pure_map`, `pure_flat_map`, etc.
10 | - Lazy methods that return iterators and never materialize results, such as `lazy_map`, `lazy_flat_map`, etc.
11 |
12 | pyfuncol can also be [used without forbiddenfruit](usage-without-forbiddenfruit).
13 |
--------------------------------------------------------------------------------
/docs/source/modules.rst:
--------------------------------------------------------------------------------
1 | API reference
2 | =============
3 |
4 | .. toctree::
5 | :maxdepth: 4
6 |
7 | pyfuncol
8 |
--------------------------------------------------------------------------------
/docs/source/pyfuncol.rst:
--------------------------------------------------------------------------------
1 | pyfuncol package
2 | ================
3 |
4 | Submodules
5 | ----------
6 |
7 | pyfuncol.dict module
8 | --------------------
9 |
10 | .. automodule:: pyfuncol.dict
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
15 | pyfuncol.list module
16 | --------------------
17 |
18 | .. automodule:: pyfuncol.list
19 | :members:
20 | :undoc-members:
21 | :show-inheritance:
22 |
23 | pyfuncol.set module
24 | --------------------
25 |
26 | .. automodule:: pyfuncol.set
27 | :members:
28 | :undoc-members:
29 | :show-inheritance:
30 |
31 |
32 | Module contents
33 | ---------------
34 |
35 | .. automodule:: pyfuncol
36 | :members:
37 | :undoc-members:
38 | :show-inheritance:
39 |
--------------------------------------------------------------------------------
/pyfuncol/__init__.py:
--------------------------------------------------------------------------------
1 | from . import extend_builtins
--------------------------------------------------------------------------------
/pyfuncol/dict.py:
--------------------------------------------------------------------------------
1 | from typing import Callable, Dict, Iterator, Optional, Tuple, TypeVar, List, cast
2 | import functools
3 | import dask
4 |
5 | A = TypeVar("A")
6 | B = TypeVar("B")
7 | C = TypeVar("C")
8 | D = TypeVar("D")
9 | U = TypeVar("U")
10 |
11 |
12 | def contains(self: Dict[A, B], key: A) -> bool:
13 | """
14 | Tests whether this dict contains a binding for a key.
15 |
16 | Args:
17 | key: The key to find.
18 |
19 | Returns:
20 | True if the dict contains a binding for the key, False otherwise.
21 | """
22 | return key in self
23 |
24 |
25 | def size(self: Dict[A, B]) -> int:
26 | """
27 | Computes the size of this dict.
28 |
29 | Returns:
30 | The size of the dict.
31 | """
32 | return len(self)
33 |
34 |
35 | def filter(self: Dict[A, B], p: Callable[[Tuple[A, B]], bool]) -> Dict[A, B]:
36 | """
37 | Selects all elements of this dict which satisfy a predicate.
38 |
39 | Args:
40 | p: The predicate to satisfy.
41 |
42 | Returns:
43 | The filtered dict.
44 | """
45 | return type(self)({k: v for k, v in self.items() if p((k, v))})
46 |
47 |
48 | def filter_not(self: Dict[A, B], p: Callable[[Tuple[A, B]], bool]) -> Dict[A, B]:
49 | """
50 | Selects all elements of this dict which do not satisfy a predicate.
51 |
52 | Args:
53 | p: The predicate to not satisfy.
54 |
55 | Returns:
56 | The filtered dict.
57 | """
58 | return type(self)({k: v for k, v in self.items() if not p((k, v))})
59 |
60 |
61 | def flat_map(self: Dict[A, B], f: Callable[[Tuple[A, B]], Dict[C, D]]) -> Dict[C, D]:
62 | """
63 | Builds a new dict by applying a function to all elements of this dict and using the elements of the resulting collections.
64 |
65 | Args:
66 | f: The function to apply to all elements.
67 |
68 | Returns:
69 | The new dict.
70 | """
71 | res = cast(Dict[C, D], type(self)())
72 | for k, v in self.items():
73 | d = f((k, v))
74 | res.update(d)
75 | return res
76 |
77 |
78 | def foreach(self: Dict[A, B], f: Callable[[Tuple[A, B]], U]) -> None:
79 | """
80 | Apply f to each element for its side effects.
81 |
82 | Args:
83 | f: The function to apply to all elements for its side effects.
84 | """
85 | for k, v in self.items():
86 | f((k, v))
87 |
88 |
89 | def is_empty(self: Dict[A, B]) -> bool:
90 | """
91 | Tests whether the dict is empty.
92 |
93 | Returns:
94 | True if the dict is empty, False otherwise.
95 | """
96 | return len(self) == 0
97 |
98 |
99 | def map(self: Dict[A, B], f: Callable[[Tuple[A, B]], Tuple[C, D]]) -> Dict[C, D]:
100 | """
101 | Builds a new dict by applying a function to all elements of this dict.
102 |
103 | Args:
104 | f: The function to apply to all elements.
105 |
106 | Returns:
107 | The new dict.
108 | """
109 | return cast(Dict[C, D], type(self)(f(x) for x in self.items()))
110 |
111 |
112 | def to_list(self: Dict[A, B]) -> List[Tuple[A, B]]:
113 | """
114 | Converts this dict to a list of (key, value) pairs.
115 |
116 | Returns:
117 | A list of pairs corresponding to the entries of the dict
118 | """
119 | return [(k, v) for k, v in self.items()]
120 |
121 |
122 | def to_iterator(self: Dict[A, B]) -> Iterator[Tuple[A, B]]:
123 | """
124 | Converts this dict to an iterator of (key, value) pairs.
125 |
126 | Returns:
127 | An iterator of pairs corresponding to the entries of the dict
128 | """
129 | return ((k, v) for k, v in self.items())
130 |
131 |
132 | def count(self: Dict[A, B], p: Callable[[Tuple[A, B]], bool]) -> int:
133 | """
134 | Counts the number of elements in the collection which satisfy a predicate.
135 |
136 | Note: will not terminate for infinite-sized collections.
137 |
138 | Args:
139 | p: The predicate used to test elements.
140 |
141 | Returns:
142 | The number of elements satisfying the predicate p.
143 | """
144 | c = 0
145 | for t in self.items():
146 | if p(t):
147 | c += 1
148 |
149 | return c
150 |
151 |
152 | def fold_left(self: Dict[A, B], z: B, op: Callable[[B, Tuple[A, B]], B]) -> B:
153 | """
154 | Applies a binary operator to a start value and all elements of this collection, going left to right.
155 |
156 | Note: will not terminate for infinite-sized collections.
157 |
158 | Note: might return different results for different runs, unless the underlying collection type is ordered or the operator is associative and commutative.
159 |
160 | Args:
161 | z: The start value.
162 | op: The binary operator.
163 |
164 | Returns:
165 | The result of inserting op between consecutive elements of this collection, going left to right with the start value z on the left:
166 |
167 | op(...op(z, x_1), x_2, ..., x_n)
168 | where x1, ..., xn are the elements of this collection. Returns z if this collection is empty.
169 | """
170 | acc = z
171 | for t in self.items():
172 | acc = op(acc, t)
173 |
174 | return acc
175 |
176 |
177 | def fold_right(self: Dict[A, B], z: B, op: Callable[[Tuple[A, B], B], B]) -> B:
178 | """
179 | Applies a binary operator to a start value and all elements of this collection, going right to left.
180 |
181 | Note: will not terminate for infinite-sized collections.
182 |
183 | Note: might return different results for different runs, unless the underlying collection type is ordered or the operator is associative and commutative.
184 |
185 | Args:
186 | z: The start value.
187 | op: The binary operator.
188 |
189 | Returns:
190 | The result of inserting op between consecutive elements of this collection, going right to left with the start value z on the right:
191 |
192 | op(x_1, op(x_2, ... op(x_n, z)...))
193 | where x1, ..., xn are the elements of this collection. Returns z if this collection is empty.
194 | """
195 | acc = z
196 | for t in reversed(self.items()):
197 | acc = op(t, acc)
198 |
199 | return acc
200 |
201 |
202 | def forall(self: Dict[A, B], p: Callable[[Tuple[A, B]], bool]) -> bool:
203 | """
204 | Tests whether a predicate holds for all elements of this collection.
205 |
206 | Note: may not terminate for infinite-sized collections.
207 |
208 | Args:
209 | p: The predicate used to test elements.
210 |
211 | Returns:
212 | True if this collection is empty or the given predicate p holds for all elements of this collection, otherwise False.
213 | """
214 | for t in self.items():
215 | if not p(t):
216 | return False
217 | return True
218 |
219 |
220 | def find(self: Dict[A, B], p: Callable[[Tuple[A, B]], bool]) -> Optional[Tuple[A, B]]:
221 | """
222 | Finds the first element of the collection satisfying a predicate, if any.
223 |
224 | Note: may not terminate for infinite-sized collections.
225 |
226 | Note: might return different results for different runs, unless the underlying collection type is ordered.
227 |
228 | Args:
229 | p: The predicate used to test elements.
230 |
231 | Returns:
232 | An option value containing the first element in the collection that satisfies p, or None if none exists.
233 | """
234 | for t in self.items():
235 | if p(t):
236 | return t
237 |
238 | return None
239 |
240 |
241 | # Parallel operations
242 |
243 |
244 | def par_filter(self: Dict[A, B], p: Callable[[Tuple[A, B]], bool]) -> Dict[A, B]:
245 | """
246 | Selects in parallel all elements of this dict which satisfy a predicate.
247 |
248 | Args:
249 | p: The predicate to satisfy.
250 |
251 | Returns:
252 | The filtered dict.
253 | """
254 | preds = dask.compute(*(dask.delayed(p)(x) for x in self.items()))
255 | return type(self)({k: v for i, (k, v) in enumerate(self.items()) if preds[i]})
256 |
257 |
258 | def par_filter_not(self: Dict[A, B], p: Callable[[Tuple[A, B]], bool]) -> Dict[A, B]:
259 | """
260 | Selects in parallel all elements of this dict which do not satisfy a predicate.
261 |
262 | Args:
263 | p: The predicate to not satisfy.
264 |
265 | Returns:
266 | The filtered dict.
267 | """
268 | preds = dask.compute(*(dask.delayed(p)(x) for x in self.items()))
269 | return type(self)({k: v for i, (k, v) in enumerate(self.items()) if not preds[i]})
270 |
271 |
272 | def par_flat_map(
273 | self: Dict[A, B], f: Callable[[Tuple[A, B]], Dict[C, D]]
274 | ) -> Dict[C, D]:
275 | """
276 | Builds a new dict by applying a function in parallel to all elements of this dict and using the elements of the resulting collections.
277 |
278 | Args:
279 | f: The function to apply to all elements.
280 |
281 | Returns:
282 | The new dict.
283 | """
284 | applications = dask.compute(*(dask.delayed(f)(x) for x in self.items()))
285 | return cast(
286 | Dict[C, D], type(self)({k: v for y in applications for k, v in y.items()})
287 | )
288 |
289 |
290 | def par_map(self: Dict[A, B], f: Callable[[Tuple[A, B]], Tuple[C, D]]) -> Dict[C, D]:
291 | """
292 | Builds a new dict by applying a function in parallel to all elements of this dict.
293 |
294 | Args:
295 | f: The function to apply to all elements.
296 |
297 | Returns:
298 | The new dict.
299 | """
300 | return cast(
301 | Dict[C, D],
302 | type(self)((dask.compute(*(dask.delayed(f)(x) for x in self.items())))),
303 | )
304 |
305 |
306 | # Pure operations
307 |
308 |
309 | def pure_map(self: Dict[A, B], f: Callable[[Tuple[A, B]], Tuple[C, D]]) -> Dict[C, D]:
310 | """
311 | Builds a new dict by applying a function to all elements of this dict using memoization to improve performance.
312 |
313 | WARNING: f must be a PURE function i.e., calling f on the same input must always lead to the same result!
314 |
315 | Type A must be hashable using `hash()` function.
316 |
317 | Args:
318 | f: The function to apply to all elements.
319 |
320 | Returns:
321 | The new dict.
322 | """
323 | f_cache = functools.cache(f)
324 | return cast(Dict[C, D], type(self)(f_cache(x) for x in self.items()))
325 |
326 |
327 | def pure_flat_map(
328 | self: Dict[A, B], f: Callable[[Tuple[A, B]], Dict[C, D]]
329 | ) -> Dict[C, D]:
330 | """
331 | Builds a new dict by applying a function to all elements of this dict and using the elements of the resulting collections using memoization to improve performance.
332 |
333 | WARNING: f must be a PURE function i.e., calling f on the same input must always lead to the same result!
334 |
335 | Type A must be hashable using `hash()` function.
336 |
337 | Args:
338 | f: The function to apply to all elements.
339 |
340 | Returns:
341 | The new dict.
342 | """
343 | res = cast(Dict[C, D], type(self)())
344 | f_cache = functools.cache(f)
345 | for k, v in self.items():
346 | d = f_cache((k, v))
347 | res.update(d)
348 | return res
349 |
350 |
351 | def pure_filter(self: Dict[A, B], p: Callable[[Tuple[A, B]], bool]) -> Dict[A, B]:
352 | """
353 | Selects all elements of this dict which satisfy a predicate using memoization to improve performance.
354 |
355 | WARNING: p must be a PURE function i.e., calling p on the same input must always lead to the same result!
356 |
357 | Type A must be hashable using `hash()` function.
358 |
359 |
360 | Args:
361 | p: The predicate to satisfy.
362 |
363 | Returns:
364 | The filtered dict.
365 | """
366 | p_cache = functools.cache(p)
367 | return type(self)({k: v for k, v in self.items() if p_cache((k, v))})
368 |
369 |
370 | def pure_filter_not(self: Dict[A, B], p: Callable[[Tuple[A, B]], bool]) -> Dict[A, B]:
371 | """
372 | Selects all elements of this dict which do not satisfy a predicate using memoization to improve performance.
373 |
374 | WARNING: p must be a PURE function i.e., calling p on the same input must always lead to the same result!
375 |
376 | Type A must be hashable using `hash()` function.
377 |
378 |
379 | Args:
380 | p: The predicate not to satisfy.
381 |
382 | Returns:
383 | The filtered dict.
384 | """
385 | p_cache = functools.cache(p)
386 | return type(self)({k: v for k, v in self.items() if not p_cache((k, v))})
387 |
388 |
389 | # Lazy operations
390 |
391 |
392 | def lazy_map(
393 | self: Dict[A, B], f: Callable[[Tuple[A, B]], Tuple[C, D]]
394 | ) -> Iterator[Tuple[C, D]]:
395 | """
396 | Builds a new list of tuples by applying a function to all elements of this dict, lazily.
397 |
398 | Args:
399 | f: The function to apply to all elements.
400 |
401 | Returns:
402 | The new lazy list of tuples, as an iterator.
403 | """
404 | for x in self.items():
405 | yield f(x)
406 |
407 |
408 | def lazy_flat_map(
409 | self: Dict[A, B], f: Callable[[Tuple[A, B]], Dict[C, D]]
410 | ) -> Iterator[Tuple[C, D]]:
411 | """
412 | Builds a new list of tuples by applying a function to all elements of this dict and using the elements of the resulting collections, lazily.
413 |
414 | Args:
415 | f: The function to apply to all elements.
416 |
417 | Returns:
418 | The new lazy list of tuples, as an iterator.
419 | """
420 | return (y for x in self.items() for y in f(x).items())
421 |
422 |
423 | def lazy_filter(
424 | self: Dict[A, B], p: Callable[[Tuple[A, B]], bool]
425 | ) -> Iterator[Tuple[A, B]]:
426 | """
427 | Selects all elements of this dict which satisfy a predicate, lazily.
428 |
429 | Args:
430 | p: The predicate to satisfy.
431 |
432 | Returns:
433 | The filtered lazy list of tuples, as an iterator.
434 | """
435 | for x in self.items():
436 | if p(x):
437 | yield x
438 |
439 |
440 | def lazy_filter_not(
441 | self: Dict[A, B], p: Callable[[Tuple[A, B]], bool]
442 | ) -> Iterator[Tuple[A, B]]:
443 | """
444 | Selects all elements of this dict which do not satisfy a predicate, lazily.
445 |
446 | Args:
447 | p: The predicate to not satisfy.
448 |
449 | Returns:
450 | The filtered lazy list of tuples, as an iterator.
451 | """
452 | for x in self.items():
453 | if not p(x):
454 | yield x
455 |
--------------------------------------------------------------------------------
/pyfuncol/extend_builtins.py:
--------------------------------------------------------------------------------
1 | from warnings import warn
2 |
3 | from . import dict as pfcdict
4 | from . import list as pfclist
5 | from . import set as pfcset
6 |
7 | EXTEND_BUILTINS = False
8 | try:
9 | from forbiddenfruit import curse
10 |
11 | EXTEND_BUILTINS = True
12 | except ImportError:
13 | warn(
14 | "[WARNING] You are using pyfuncol without forbiddenfruit. Functions will not extend builtins"
15 | )
16 |
17 |
18 | def extend_dict():
19 | """
20 | Extends the dict built-in type with methods.
21 | """
22 | curse(dict, "contains", pfcdict.contains)
23 | curse(dict, "size", pfcdict.size)
24 | curse(dict, "filter", pfcdict.filter)
25 | curse(dict, "filter_not", pfcdict.filter_not)
26 | curse(dict, "flat_map", pfcdict.flat_map)
27 | curse(dict, "foreach", pfcdict.foreach)
28 | curse(dict, "is_empty", pfcdict.is_empty)
29 | curse(dict, "map", pfcdict.map)
30 | curse(dict, "to_list", pfcdict.to_list)
31 | curse(dict, "to_iterator", pfcdict.to_iterator)
32 | curse(dict, "count", pfcdict.count)
33 | curse(dict, "fold_left", pfcdict.fold_left)
34 | curse(dict, "fold_right", pfcdict.fold_right)
35 | curse(dict, "forall", pfcdict.forall)
36 | curse(dict, "find", pfcdict.find)
37 |
38 | # Parallel operations
39 | curse(dict, "par_map", pfcdict.par_map)
40 | curse(dict, "par_filter", pfcdict.par_filter)
41 | curse(dict, "par_filter_not", pfcdict.par_filter_not)
42 | curse(dict, "par_flat_map", pfcdict.par_flat_map)
43 |
44 | # Pure operations
45 | curse(dict, "pure_map", pfcdict.pure_map)
46 | curse(dict, "pure_flat_map", pfcdict.pure_flat_map)
47 | curse(dict, "pure_filter", pfcdict.pure_filter)
48 | curse(dict, "pure_filter_not", pfcdict.pure_filter_not)
49 |
50 | # Lazy operations
51 | curse(dict, "lazy_map", pfcdict.lazy_map)
52 | curse(dict, "lazy_flat_map", pfcdict.lazy_flat_map)
53 | curse(dict, "lazy_filter", pfcdict.lazy_filter)
54 | curse(dict, "lazy_filter_not", pfcdict.lazy_filter_not)
55 |
56 |
57 | def extend_list():
58 | """
59 | Extends the list built-in type with methods.
60 | """
61 | curse(list, "map", pfclist.map)
62 | curse(list, "filter", pfclist.filter)
63 | curse(list, "filter_not", pfclist.filter_not)
64 | curse(list, "flat_map", pfclist.flat_map)
65 | curse(list, "flatten", pfclist.flatten)
66 | curse(list, "contains", pfclist.contains)
67 | curse(list, "distinct", pfclist.distinct)
68 | curse(list, "foreach", pfclist.foreach)
69 | curse(list, "group_by", pfclist.group_by)
70 | curse(list, "is_empty", pfclist.is_empty)
71 | curse(list, "size", pfclist.size)
72 | curse(list, "find", pfclist.find)
73 | curse(list, "index_of", pfclist.index_of)
74 | curse(list, "fold_left", pfclist.fold_left)
75 | curse(list, "fold_right", pfclist.fold_right)
76 | curse(list, "forall", pfclist.forall)
77 | curse(list, "head", pfclist.head)
78 | curse(list, "tail", pfclist.tail)
79 | curse(list, "take", pfclist.take)
80 | curse(list, "length", pfclist.length)
81 | curse(list, "to_iterator", pfclist.to_iterator)
82 |
83 | # Parallel operations
84 | curse(list, "par_map", pfclist.par_map)
85 | curse(list, "par_filter", pfclist.par_filter)
86 | curse(list, "par_filter_not", pfclist.par_filter_not)
87 | curse(list, "par_flat_map", pfclist.par_flat_map)
88 |
89 | # Pure operations
90 | curse(list, "pure_map", pfclist.pure_map)
91 | curse(list, "pure_flat_map", pfclist.pure_flat_map)
92 | curse(list, "pure_filter", pfclist.pure_filter)
93 | curse(list, "pure_filter_not", pfclist.pure_filter_not)
94 |
95 | # Lazy operations
96 | curse(list, "lazy_map", pfclist.lazy_map)
97 | curse(list, "lazy_flat_map", pfclist.lazy_flat_map)
98 | curse(list, "lazy_filter", pfclist.lazy_filter)
99 | curse(list, "lazy_filter_not", pfclist.lazy_filter_not)
100 | curse(list, "lazy_flatten", pfclist.lazy_flatten)
101 | curse(list, "lazy_distinct", pfclist.lazy_distinct)
102 | curse(list, "lazy_take", pfclist.lazy_take)
103 |
104 |
105 | def extend_set():
106 | """
107 | Extends the set and frozenset built-in type with methods.
108 | """
109 | curse(set, "map", pfcset.map)
110 | curse(set, "filter", pfcset.filter)
111 | curse(set, "filter_not", pfcset.filter_not)
112 | curse(set, "flat_map", pfcset.flat_map)
113 | curse(set, "contains", pfcset.contains)
114 | curse(set, "foreach", pfcset.foreach)
115 | curse(set, "group_by", pfcset.group_by)
116 | curse(set, "is_empty", pfcset.is_empty)
117 | curse(set, "size", pfcset.size)
118 | curse(set, "find", pfcset.find)
119 | curse(set, "fold_left", pfcset.fold_left)
120 | curse(set, "fold_right", pfcset.fold_right)
121 | curse(set, "forall", pfcset.forall)
122 | curse(set, "length", pfcset.length)
123 | curse(set, "to_iterator", pfcset.to_iterator)
124 |
125 | curse(frozenset, "map", pfcset.map)
126 | curse(frozenset, "filter", pfcset.filter)
127 | curse(frozenset, "filter_not", pfcset.filter_not)
128 | curse(frozenset, "flat_map", pfcset.flat_map)
129 | curse(frozenset, "contains", pfcset.contains)
130 | curse(frozenset, "foreach", pfcset.foreach)
131 | curse(frozenset, "group_by", pfcset.group_by)
132 | curse(frozenset, "is_empty", pfcset.is_empty)
133 | curse(frozenset, "size", pfcset.size)
134 | curse(frozenset, "find", pfcset.find)
135 | curse(frozenset, "fold_left", pfcset.fold_left)
136 | curse(frozenset, "fold_right", pfcset.fold_right)
137 | curse(frozenset, "forall", pfcset.forall)
138 | curse(frozenset, "length", pfcset.length)
139 | curse(frozenset, "to_iterator", pfcset.to_iterator)
140 |
141 | # Parallel operations
142 | curse(set, "par_map", pfcset.par_map)
143 | curse(set, "par_filter", pfcset.par_filter)
144 | curse(set, "par_filter_not", pfcset.par_filter_not)
145 | curse(set, "par_flat_map", pfcset.par_flat_map)
146 |
147 | curse(frozenset, "par_map", pfcset.par_map)
148 | curse(frozenset, "par_filter", pfcset.par_filter)
149 | curse(frozenset, "par_filter_not", pfcset.par_filter_not)
150 | curse(frozenset, "par_flat_map", pfcset.par_flat_map)
151 |
152 | # Pure operations
153 | curse(set, "pure_map", pfcset.pure_map)
154 | curse(set, "pure_flat_map", pfcset.pure_flat_map)
155 | curse(set, "pure_filter", pfcset.pure_filter)
156 | curse(set, "pure_filter_not", pfcset.pure_filter_not)
157 |
158 | curse(frozenset, "pure_map", pfcset.pure_map)
159 | curse(frozenset, "pure_flat_map", pfcset.pure_flat_map)
160 | curse(frozenset, "pure_filter", pfcset.pure_filter)
161 | curse(frozenset, "pure_filter_not", pfcset.pure_filter_not)
162 |
163 | # Lazy operations
164 | curse(set, "lazy_map", pfcset.lazy_map)
165 | curse(set, "lazy_flat_map", pfcset.lazy_flat_map)
166 | curse(set, "lazy_filter", pfcset.lazy_filter)
167 | curse(set, "lazy_filter_not", pfcset.lazy_filter_not)
168 |
169 | curse(frozenset, "lazy_map", pfcset.lazy_map)
170 | curse(frozenset, "lazy_flat_map", pfcset.lazy_flat_map)
171 | curse(frozenset, "lazy_filter", pfcset.lazy_filter)
172 | curse(frozenset, "lazy_filter_not", pfcset.lazy_filter_not)
173 |
174 |
175 | if EXTEND_BUILTINS:
176 | extend_dict()
177 | extend_list()
178 | extend_set()
179 |
--------------------------------------------------------------------------------
/pyfuncol/set.py:
--------------------------------------------------------------------------------
1 | from collections import defaultdict
2 | from typing import Callable, Dict, Optional, TypeVar, Set, cast, Iterator
3 | import functools
4 | import dask
5 |
6 | A = TypeVar("A")
7 | B = TypeVar("B")
8 | K = TypeVar("K")
9 | U = TypeVar("U")
10 |
11 |
12 | def map(self: Set[A], f: Callable[[A], B]) -> Set[B]:
13 | """
14 | Builds a new set by applying a function to all elements of this set.
15 |
16 | Args:
17 | f: The function to apply to all elements.
18 |
19 | Returns:
20 | The new set.
21 | """
22 | return cast(Set[B], type(self)(f(x) for x in self))
23 |
24 |
25 | def filter(self: Set[A], p: Callable[[A], bool]) -> Set[A]:
26 | """
27 | Selects all elements of this set which satisfy a predicate.
28 |
29 | Args:
30 | p: The predicate to satisfy.
31 |
32 | Returns:
33 | The filtered set.
34 | """
35 | return type(self)(x for x in self if p(x))
36 |
37 |
38 | def filter_not(self: Set[A], p: Callable[[A], bool]) -> Set[A]:
39 | """
40 | Selects all elements of this set which do not satisfy a predicate.
41 |
42 | Args:
43 | p: The predicate to not satisfy.
44 |
45 | Returns:
46 | The filtered set.
47 | """
48 | return type(self)(x for x in self if not p(x))
49 |
50 |
51 | def flat_map(self: Set[A], f: Callable[[A], Set[B]]) -> Set[B]:
52 | """
53 | Builds a new set by applying a function to all elements of this set and using the elements of the resulting collections.
54 |
55 | Args:
56 | f: The function to apply to all elements.
57 |
58 | Returns:
59 | The new set.
60 | """
61 | return cast(Set[B], type(self)(y for x in self for y in f(x)))
62 |
63 |
64 | def contains(self: Set[A], elem: A) -> bool:
65 | """
66 | Tests whether this set contains a given value as element.
67 |
68 | Args:
69 | elem: The element to look for.
70 |
71 | Returns:
72 | True if the set contains the element, False otherwise.
73 | """
74 | return elem in self
75 |
76 |
77 | def foreach(self: Set[A], f: Callable[[A], U]) -> None:
78 | """
79 | Apply f to each element of the set for its side effects.
80 |
81 | Args:
82 | f: The function to apply to all elements for its side effects.
83 | """
84 | for x in self:
85 | f(x)
86 |
87 |
88 | def group_by(self: Set[A], f: Callable[[A], K]) -> Dict[K, Set[A]]:
89 | """
90 | Partitions this set into a dict of sets according to some discriminator function.
91 |
92 | Args:
93 | f: The grouping function.
94 |
95 | Returns:
96 | A dictionary where elements are grouped according to the grouping function.
97 | """
98 | # frozenset does not have `add`
99 | d = defaultdict(set if isinstance(self, frozenset) else type(self))
100 | for x in self:
101 | k = f(x)
102 | d[k].add(x)
103 | return d
104 |
105 |
106 | def is_empty(self: Set[A]) -> bool:
107 | """
108 | Tests whether the set is empty.
109 |
110 | Returns:
111 | True if the set is empty, False otherwise.
112 | """
113 | return len(self) == 0
114 |
115 |
116 | def size(self: Set[A]) -> int:
117 | """
118 | Computes the size of this set.
119 |
120 | Returns:
121 | The size of the set.
122 | """
123 | return len(self)
124 |
125 |
126 | def find(self: Set[A], p: Callable[[A], bool]) -> Optional[A]:
127 | """
128 | Finds the first element of the set satisfying a predicate, if any.
129 |
130 | Args:
131 | p: The predicate to satisfy.
132 |
133 | Returns:
134 | The first element satisfying the predicate, otherwise None.
135 | """
136 | for x in self:
137 | if p(x):
138 | return x
139 | return None
140 |
141 |
142 | def fold_left(self: Set[A], z: B, op: Callable[[B, A], B]) -> B:
143 | """
144 | Applies a binary operator to a start value and all elements of this set, going left to right.
145 |
146 | Note: might return different results for different runs, unless the underlying collection type is ordered or the operator is associative and commutative.
147 |
148 | Args:
149 | z: The start value.
150 | op: The binary operation.
151 |
152 | Returns:
153 | The result of inserting op between consecutive elements of this set, going left to right with the start value z on the left:
154 | op(...op(z, x_1), x_2, ..., x_n)
155 | where x1, ..., xn are the elements of this set. Returns z if this set is empty.
156 | """
157 | acc = z
158 | for x in self:
159 | acc = op(acc, x)
160 | return acc
161 |
162 |
163 | def fold_right(self: Set[A], z: B, op: Callable[[A, B], B]) -> B:
164 | """
165 | Applies a binary operator to all elements of this set and a start value, going right to left.
166 |
167 | Note: might return different results for different runs, unless the underlying collection type is ordered or the operator is associative and commutative.
168 |
169 | Args:
170 | z: The start value.
171 | op: The binary operation.
172 |
173 | Returns:
174 | The result of inserting op between consecutive elements of this set, going right to left with the start value z on the right:
175 | op(x_1, op(x_2, ... op(x_n, z)...))
176 | where x1, ..., xn are the elements of this set. Returns z if this set is empty.
177 | """
178 |
179 | acc = z
180 | for x in self:
181 | acc = op(x, acc)
182 | return acc
183 |
184 |
185 | def forall(self: Set[A], p: Callable[[A], bool]) -> bool:
186 | """
187 | Tests whether a predicate holds for all elements of this set.
188 |
189 | Args:
190 | p: The predicate used to test elements.
191 |
192 | Returns:
193 | True if this set is empty or the given predicate p holds for all elements of this set, otherwise False.
194 | """
195 | for x in self:
196 | if not p(x):
197 | return False
198 |
199 | return True
200 |
201 |
202 | def length(self: Set[A]) -> int:
203 | """
204 | Returns the length (number of elements) of the set. `size` is an alias for length.
205 |
206 | Returns:
207 | The length of the set
208 | """
209 | return len(self)
210 |
211 |
212 | def to_iterator(self: Set[A]) -> Iterator[A]:
213 | """
214 | Converts this set to an iterator.
215 |
216 | Returns:
217 | An iterator
218 | """
219 | return (x for x in self)
220 |
221 | # Parallel operations
222 |
223 |
224 | def par_map(self: Set[A], f: Callable[[A], B]) -> Set[B]:
225 | """
226 | Builds a new set by applying in parallel a function to all elements of this set.
227 |
228 | Args:
229 | f: The function to apply to all elements.
230 |
231 | Returns:
232 | The new set.
233 | """
234 | return cast(Set[B], type(self)((dask.compute(*(dask.delayed(f)(x) for x in self)))))
235 |
236 |
237 | def par_filter(self: Set[A], p: Callable[[A], bool]) -> Set[A]:
238 | """
239 | Selects in parallel all elements of this set which satisfy a predicate.
240 |
241 | Args:
242 | p: The predicate to satisfy.
243 |
244 | Returns:
245 | The filtered set.
246 | """
247 | preds = dask.compute(*(dask.delayed(p)(x) for x in self))
248 | return type(self)(x for i, x in enumerate(self) if preds[i])
249 |
250 |
251 | def par_filter_not(self: Set[A], p: Callable[[A], bool]) -> Set[A]:
252 | """
253 | Selects in parallel all elements of this set which do not satisfy a predicate.
254 |
255 | Args:
256 | p: The predicate to not satisfy.
257 |
258 | Returns:
259 | The filtered set.
260 | """
261 | preds = dask.compute(*(dask.delayed(p)(x) for x in self))
262 | return type(self)(x for i, x in enumerate(self) if not preds[i])
263 |
264 |
265 | def par_flat_map(self: Set[A], f: Callable[[A], Set[B]]) -> Set[B]:
266 | """
267 | Builds a new set by applying in parallel a function to all elements of this set and using the elements of the resulting collections.
268 |
269 | Args:
270 | f: The function to apply to all elements.
271 |
272 | Returns:
273 | The new set.
274 | """
275 | applications = dask.compute(*(dask.delayed(f)(x) for x in self))
276 | return cast(Set[B], type(self)(x for y in applications for x in y))
277 |
278 |
279 | # Pure operations
280 |
281 |
282 | def pure_map(self: Set[A], f: Callable[[A], B]) -> Set[B]:
283 | """
284 | Builds a new set by applying a function to all elements of this set using memoization to improve performance.
285 |
286 | WARNING: f must be a PURE function i.e., calling f on the same input must always lead to the same result!
287 |
288 | Type A must be hashable using `hash()` function.
289 |
290 | Args:
291 | f: The PURE function to apply to all elements.
292 |
293 | Returns:
294 | The new set.
295 | """
296 | f_cache = functools.cache(f)
297 | return cast(Set[B], type(self)(f_cache(x) for x in self))
298 |
299 |
300 | def pure_flat_map(self: Set[A], f: Callable[[A], Set[B]]) -> Set[B]:
301 | """
302 | Builds a new set by applying a function to all elements of this set and using the elements of the resulting collections using memoization to improve performance.
303 |
304 | WARNING: f must be a PURE function i.e., calling f on the same input must always lead to the same result!
305 |
306 | Type A must be hashable using `hash()` function.
307 |
308 | Args:
309 | f: The function to apply to all elements.
310 |
311 | Returns:
312 | The new set.
313 | """
314 | f_cache = functools.cache(f)
315 | return cast(Set[B], type(self)(y for x in self for y in f_cache(x)))
316 |
317 |
318 | def pure_filter(self: Set[A], p: Callable[[A], bool]) -> Set[A]:
319 | """
320 | Selects all elements of this set which satisfy a predicate using memoization to improve performance.
321 |
322 | WARNING: p must be a PURE function i.e., calling p on the same input must always lead to the same result!
323 |
324 | Type A must be hashable using `hash()` function.
325 |
326 | Args:
327 | p: The predicate to satisfy.
328 |
329 | Returns:
330 | The filtered set.
331 | """
332 | p_cache = functools.cache(p)
333 | return type(self)(x for x in self if p_cache(x))
334 |
335 |
336 | def pure_filter_not(self: Set[A], p: Callable[[A], bool]) -> Set[A]:
337 | """
338 | Selects all elements of this set which do not satisfy a predicate using memoization to improve performance.
339 |
340 | WARNING: p must be a PURE function i.e., calling p on the same input must always lead to the same result!
341 |
342 | Type A must be hashable using `hash()` function.
343 |
344 |
345 | Args:
346 | p: The predicate not to satisfy.
347 |
348 | Returns:
349 | The filtered set.
350 | """
351 | p_cache = functools.cache(p)
352 | return type(self)(x for x in self if not p_cache(x))
353 |
354 |
355 | def lazy_map(self: Set[A], f: Callable[[A], B]) -> Iterator[B]:
356 | """
357 | Builds a new set by applying a function to all elements of this set, lazily.
358 |
359 | Args:
360 | f: The function to apply to all elements.
361 |
362 | Returns:
363 | The new lazy set, as an iterator.
364 | """
365 | for x in self:
366 | yield f(x)
367 |
368 |
369 | def lazy_filter(self: Set[A], p: Callable[[A], bool]) -> Iterator[A]:
370 | """
371 | Selects all elements of this set which satisfy a predicate, lazily.
372 |
373 | Args:
374 | p: The predicate to satisfy.
375 |
376 | Returns:
377 | The filtered lazy set, as an iterator.
378 | """
379 | for x in self:
380 | if p(x):
381 | yield x
382 |
383 |
384 | def lazy_filter_not(self: Set[A], p: Callable[[A], bool]) -> Iterator[A]:
385 | """
386 | Selects all elements of this set which do not satisfy a predicate, lazily.
387 |
388 | Args:
389 | p: The predicate to not satisfy.
390 |
391 | Returns:
392 | The filtered lazy set, as an iterator.
393 | """
394 | for x in self:
395 | if not p(x):
396 | yield x
397 |
398 |
399 | def lazy_flat_map(self: Set[A], f: Callable[[A], Set[B]]) -> Iterator[B]:
400 | """
401 | Builds a new lazy set by applying a function to all elements of this set and using the elements of the resulting collections.
402 |
403 | Args:
404 | f: The function to apply to all elements.
405 |
406 | Returns:
407 | The new lazy set, as an iterator.
408 | """
409 | return (y for x in self for y in f(x))
410 |
--------------------------------------------------------------------------------
/pyfuncol/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didactic-meme/pyfuncol/60af7017629b7578fe04aefeecbcdfbd2b2ecb49/pyfuncol/tests/__init__.py
--------------------------------------------------------------------------------
/pyfuncol/tests/test_dict.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 | import pyfuncol
3 |
4 | d = {"a": 1, "b": 2, "c": 3}
5 | di: OrderedDict[str, int] = OrderedDict(d)
6 |
7 |
8 | def test_contains():
9 | assert d.contains("a") == True
10 | assert d.contains("z") == False
11 |
12 |
13 | def test_size():
14 | assert d.size() == 3
15 |
16 |
17 | def test_filter():
18 | assert d.filter(lambda kv: kv[1] > 1) == {"b": 2, "c": 3}
19 |
20 | # Test that type is preserved
21 | di: OrderedDict[str, int] = OrderedDict(d)
22 | assert di.filter(lambda kv: True) == di
23 |
24 |
25 | def test_filter_not():
26 | assert d.filter_not(lambda kv: kv[1] > 1) == {"a": 1}
27 |
28 | # Test that type is preserved
29 | di: OrderedDict[str, int] = OrderedDict(d)
30 | assert di.filter_not(lambda kv: False) == di
31 |
32 |
33 | def test_flat_map():
34 | assert d.flat_map(lambda kv: {kv[0]: kv[1] ** 2}) == {"a": 1, "b": 4, "c": 9}
35 |
36 | # Test that type is preserved
37 | di: OrderedDict[str, int] = OrderedDict(d)
38 | assert di.flat_map(lambda kv: {kv[0]: kv[1]}) == di
39 |
40 |
41 | def test_foreach():
42 | tester = []
43 | d.foreach(lambda kv: tester.append(kv))
44 | assert tester == [("a", 1), ("b", 2), ("c", 3)]
45 |
46 |
47 | def test_is_empty():
48 | assert d.is_empty() == False
49 | assert {}.is_empty() == True
50 |
51 |
52 | def test_map():
53 | assert d.map(lambda kv: (kv[0], kv[1] ** 2)) == {"a": 1, "b": 4, "c": 9}
54 |
55 | # Test that type is preserved
56 | di: OrderedDict[str, int] = OrderedDict(d)
57 | assert di.map(lambda kv: kv) == di
58 |
59 |
60 | def test_to_list():
61 | assert d.to_list() == [("a", 1), ("b", 2), ("c", 3)]
62 |
63 |
64 | def test_to_iterator():
65 | it = d.to_iterator()
66 | assert next(it) == ("a", 1)
67 | assert list(it) == [("b", 2), ("c", 3)]
68 |
69 |
70 | def test_count():
71 | assert d.count(lambda kv: (kv[0] == "a" or kv[0] == "b") and kv[1] <= 3) == 2
72 |
73 |
74 | def test_fold_left():
75 | assert d.fold_left("", lambda acc, kv: acc + kv[0] + str(kv[1])) == "a1b2c3"
76 |
77 |
78 | def test_fold_right():
79 | assert d.fold_right("", lambda kv, acc: acc + kv[0] + str(kv[1])) == "c3b2a1"
80 |
81 |
82 | def test_forall():
83 | assert d.forall(lambda kv: kv[1] <= 3) == True
84 |
85 |
86 | def test_forall_false():
87 | assert d.forall(lambda kv: kv[1] < 2) == False
88 |
89 |
90 | def test_find():
91 | assert d.find(lambda kv: kv[1] == 2) == ("b", 2)
92 |
93 |
94 | def test_find_none():
95 | assert d.find(lambda kv: kv[1] == 5) == None
96 |
97 |
98 | # Parallel operations
99 |
100 |
101 | def test_par_filter():
102 | assert d.par_filter(lambda kv: kv[1] > 1) == {"b": 2, "c": 3}
103 |
104 | # Test that type is preserved
105 | assert di.par_filter(lambda kv: True) == di
106 |
107 |
108 | def test_par_filter_not():
109 | assert d.par_filter_not(lambda kv: kv[1] <= 1) == {"b": 2, "c": 3}
110 |
111 | # Test that type is preserved
112 | assert di.par_filter_not(lambda kv: False) == di
113 |
114 |
115 | def test_par_flat_map():
116 | assert d.par_flat_map(lambda kv: {kv[0]: kv[1] ** 2}) == {"a": 1, "b": 4, "c": 9}
117 |
118 | # Test that type is preserved
119 | assert di.par_flat_map(lambda kv: {kv[0]: kv[1]}) == di
120 |
121 |
122 | def test_par_map():
123 | assert d.par_map(lambda kv: (kv[0], kv[1] ** 2)) == {"a": 1, "b": 4, "c": 9}
124 |
125 | # Test that type is preserved
126 | assert di.par_map(lambda kv: kv) == di
127 |
128 |
129 | # Pure operations
130 |
131 |
132 | def test_pure_flat_map():
133 | assert d.pure_flat_map(lambda kv: {kv[0]: kv[1] ** 2}) == {"a": 1, "b": 4, "c": 9}
134 |
135 | # Test that type is preserved
136 | assert di.pure_flat_map(lambda kv: {kv[0]: kv[1] ** 2}) == {"a": 1, "b": 4, "c": 9}
137 |
138 |
139 | def test_pure_map():
140 | assert d.pure_map(lambda kv: (kv[0], kv[1] ** 2)) == {"a": 1, "b": 4, "c": 9}
141 |
142 | # Test that type is preserved
143 | assert di.pure_map(lambda kv: (kv[0], kv[1] ** 2)) == {"a": 1, "b": 4, "c": 9}
144 |
145 |
146 | def test_pure_filter():
147 | assert d.pure_filter(lambda kv: kv[1] > 1) == {"b": 2, "c": 3}
148 |
149 | # Test that type is preserved
150 | assert di.pure_filter(lambda kv: kv[1] > 1) == {"b": 2, "c": 3}
151 |
152 |
153 | def test_pure_filter_not():
154 | assert d.pure_filter_not(lambda kv: kv[1] > 1) == {"a": 1}
155 |
156 | # Test that type is preserved
157 | assert d.pure_filter_not(lambda kv: kv[1] > 1) == {"a": 1}
158 |
159 |
160 | # Lazy operations
161 |
162 |
163 | def test_lazy_flat_map():
164 | res = d.lazy_flat_map(lambda kv: {kv[0]: kv[1] ** 2})
165 | assert next(res) == ("a", 1)
166 | assert list(res) == [("b", 4), ("c", 9)]
167 |
168 |
169 | def test_lazy_map():
170 | res = d.lazy_map(lambda kv: (kv[0], kv[1] ** 2))
171 | assert next(res) == ("a", 1)
172 | assert list(res) == [("b", 4), ("c", 9)]
173 |
174 |
175 | def test_lazy_filter():
176 | res = d.lazy_filter(lambda kv: kv[1] > 1)
177 | assert next(res) == ("b", 2)
178 | assert list(res) == [("c", 3)]
179 |
180 |
181 | def test_lazy_filter_not():
182 | res = d.lazy_filter_not(lambda kv: kv[1] <= 1)
183 | assert next(res) == ("b", 2)
184 | assert list(res) == [("c", 3)]
185 |
--------------------------------------------------------------------------------
/pyfuncol/tests/test_list.py:
--------------------------------------------------------------------------------
1 | import pyfuncol
2 | import pytest
3 |
4 |
5 | class Lst(list):
6 | pass
7 |
8 |
9 | l = [1, 2, 3]
10 | lst = Lst(l)
11 |
12 |
13 | def test_map():
14 | assert l.map(lambda x: x * 2) == [2, 4, 6]
15 |
16 | # Test that type is preserved
17 | assert lst.map(lambda x: x) == lst
18 |
19 |
20 | def test_filter():
21 | assert l.filter(lambda x: x >= 2) == [2, 3]
22 |
23 | # Test that type is preserved
24 | assert lst.filter(lambda x: True) == lst
25 |
26 |
27 | def test_filter_not():
28 | assert l.filter_not(lambda x: x < 2) == [2, 3]
29 |
30 | # Test that type is preserved
31 | assert lst.filter_not(lambda x: False) == lst
32 |
33 |
34 | def test_flat_map():
35 | assert l.flat_map(lambda x: [x ** 2]) == [1, 4, 9]
36 |
37 | # Test that type is preserved
38 | assert lst.flat_map(lambda x: [x]) == lst
39 |
40 |
41 | def test_flatten():
42 | assert [[1, 2], [3]].flatten() == l
43 |
44 | # Test that type is preserved
45 | lst = Lst([[1, 2], [3]])
46 | assert lst.flatten() == Lst(l)
47 |
48 |
49 | def test_contains():
50 | assert l.contains(2) == True
51 |
52 |
53 | def test_distinct():
54 | assert [1, 1, 2, 2, 3].distinct() == l
55 |
56 | # Test that type is preserved
57 | lst = Lst([1, 1, 2, 2, 3])
58 | assert lst.distinct() == Lst(l)
59 |
60 |
61 | def test_group_by():
62 | assert ["abc", "def", "e"].group_by(lambda s: len(s)) == {
63 | 3: ["abc", "def"],
64 | 1: ["e"],
65 | }
66 |
67 |
68 | def test_is_empty():
69 | assert l.is_empty() == False
70 | assert [].is_empty() == True
71 |
72 |
73 | def test_size():
74 | assert l.size() == 3
75 |
76 |
77 | def test_find():
78 | assert l.find(lambda x: x >= 3) == 3
79 | assert l.find(lambda x: x < 0) == None
80 |
81 |
82 | def test_index_of():
83 | assert l.index_of(3) == 2
84 | assert l.index_of(42) == -1
85 |
86 |
87 | def test_foreach():
88 | tester = []
89 | l.foreach(lambda x: tester.append(x))
90 | assert tester == l
91 |
92 |
93 | def test_fold_left_plus():
94 | a = l.fold_left(0, lambda acc, n: acc + n)
95 | assert a == 6
96 |
97 |
98 | def test_fold_left_concat():
99 | a = l.fold_left("", lambda acc, n: acc + str(n))
100 | assert a == "123"
101 |
102 |
103 | def test_fold_right_plus():
104 | a = l.fold_right(0, lambda n, acc: acc + n)
105 | assert a == 6
106 |
107 |
108 | def test_fold_right_concat():
109 | a = l.fold_right("", lambda n, acc: acc + str(n))
110 | assert a == "321"
111 |
112 |
113 | def test_forall_gt_zero():
114 | a = l.forall(lambda n: n > 0)
115 | assert a == True
116 |
117 |
118 | def test_forall_gt_two():
119 | a = l.forall(lambda n: n > 2)
120 | assert a == False
121 |
122 |
123 | def test_head():
124 | h = l.head()
125 | assert h == 1
126 |
127 |
128 | def test_head_empty():
129 | l = []
130 | with pytest.raises(IndexError):
131 | l.head()
132 |
133 |
134 | def test_tail():
135 | t = l.tail()
136 | assert t == [2, 3]
137 |
138 |
139 | def test_tail_empty():
140 | l = []
141 | with pytest.raises(IndexError):
142 | l.tail()
143 |
144 |
145 | def test_take_neg():
146 | a = l.take(-1)
147 | assert a == []
148 |
149 | # Test that type is preserved
150 | assert lst.take(-1) == Lst()
151 |
152 |
153 | def test_take_greater_len():
154 | a = l.take(4)
155 | assert a == l
156 |
157 | # Test that type is preserved
158 | assert lst.take(4) == lst
159 |
160 |
161 | def test_take_smaller_len():
162 | a = l.take(2)
163 | assert a == [1, 2]
164 |
165 | # Test that type is preserved
166 | assert lst.take(2) == Lst(l.take(2))
167 |
168 |
169 | def test_length():
170 | a = l.length()
171 | assert a == 3
172 |
173 |
174 | def test_length_equal_size():
175 | assert l.size() == l.length()
176 |
177 |
178 | def test_to_iterator():
179 | it = l.to_iterator()
180 | assert next(it) == l[0]
181 | assert list(it) == l[1:]
182 |
183 |
184 | # Parallel operations
185 |
186 |
187 | def test_par_map():
188 | assert l.par_map(lambda x: x * 2) == [2, 4, 6]
189 |
190 | # Test that type is preserved
191 | assert lst.par_map(lambda x: x) == lst
192 |
193 |
194 | def test_par_filter():
195 | assert l.par_filter(lambda x: x >= 2) == [2, 3]
196 |
197 | # Test that type is preserved
198 | assert lst.par_filter(lambda x: True) == lst
199 |
200 |
201 | def test_par_filter_not():
202 | assert l.par_filter_not(lambda x: x < 2) == [2, 3]
203 |
204 | # Test that type is preserved
205 | assert lst.par_filter_not(lambda x: False) == lst
206 |
207 |
208 | def test_par_flat_map():
209 | assert l.par_flat_map(lambda x: [x ** 2]) == [1, 4, 9]
210 |
211 | # Test that type is preserved
212 | assert lst.par_flat_map(lambda x: [x]) == lst
213 |
214 |
215 | # Pure operations
216 |
217 |
218 | def test_pure_map():
219 | assert l.pure_map(lambda x: x * 2) == [2, 4, 6]
220 |
221 | # Test that type is preserved
222 | assert lst.pure_map(lambda x: x * 2) == [2, 4, 6]
223 |
224 |
225 | def test_pure_flat_map():
226 | assert l.pure_flat_map(lambda x: [x ** 2]) == [1, 4, 9]
227 |
228 | # Test that type is preserved
229 | assert lst.pure_flat_map(lambda x: [x ** 2]) == [1, 4, 9]
230 |
231 |
232 | def test_pure_filter():
233 | assert l.pure_filter(lambda x: x >= 2) == [2, 3]
234 |
235 | # Test that type is preserved
236 | assert lst.pure_filter(lambda x: x >= 2) == [2, 3]
237 |
238 |
239 | def test_pure_filter_not():
240 | assert l.pure_filter_not(lambda x: x >= 2) == [1]
241 |
242 | # Test that type is preserved
243 | assert lst.pure_filter_not(lambda x: x >= 2) == [1]
244 |
245 |
246 | # Lazy operations
247 |
248 |
249 | def test_lazy_map():
250 | res = l.lazy_map(lambda x: x * 2)
251 | assert next(res) == 2
252 | assert list(res) == [4, 6]
253 |
254 |
255 | def test_lazy_flat_map():
256 | res = l.lazy_flat_map(lambda x: [x * 2])
257 | assert next(res) == 2
258 | assert list(res) == [4, 6]
259 |
260 |
261 | def test_lazy_filter():
262 | res = l.lazy_filter(lambda x: x >= 2)
263 | assert next(res) == 2
264 | assert list(res) == [3]
265 |
266 |
267 | def test_lazy_filter_not():
268 | res = l.lazy_filter_not(lambda x: x < 2)
269 | assert next(res) == 2
270 | assert list(res) == [3]
271 |
272 |
273 | def test_lazy_flatten():
274 | res = [[1, 2], [3]].lazy_flatten()
275 | assert next(res) == 1
276 | assert list(res) == [2, 3]
277 |
278 |
279 | def test_lazy_distinct():
280 | res = [1, 1, 2, 2, 3].lazy_distinct()
281 | assert next(res) == 1
282 | assert list(res) == [2, 3]
283 |
284 |
285 | def test_lazy_take_neg():
286 | a = l.lazy_take(-1)
287 | assert list(a) == []
288 |
289 |
290 | def test_lazy_take_greater_len():
291 | a = l.lazy_take(4)
292 | assert next(a) == l[0]
293 | assert list(a) == l[1:]
294 |
295 |
296 | def test_lazy_take_smaller_len():
297 | a = l.lazy_take(2)
298 | assert next(a) == 1
299 | assert list(a) == [2]
300 |
--------------------------------------------------------------------------------
/pyfuncol/tests/test_no_forbiddenfruit.py:
--------------------------------------------------------------------------------
1 | from .. import dict as pfcdict
2 | from .. import list as pfclist
3 | from .. import set as pfcset
4 |
5 | def test_dict():
6 | assert pfcdict.map({'a': 1, 'b': 2, 'c': 3}, lambda x: (x[0], x[1] * 2)) == {'a': 2, 'b': 4, 'c': 6}
7 |
8 | def test_list():
9 | assert pfclist.map([1, 2, 3], lambda x: x * 2) == [2, 4, 6]
10 |
11 | def test_set():
12 | assert pfcset.map({1, 2, 3}, lambda x: x * 2) == {2, 4, 6}
--------------------------------------------------------------------------------
/pyfuncol/tests/test_set.py:
--------------------------------------------------------------------------------
1 | from operator import ne
2 | import pyfuncol
3 |
4 | s = {1, 2, 3}
5 | st = frozenset(s)
6 |
7 |
8 | def test_map():
9 | s = {1, 2, 3}
10 | st = frozenset(s)
11 | assert s.map(lambda x: x * 2) == {2, 4, 6}
12 | assert st.map(lambda x: x * 2) == frozenset({2, 4, 6})
13 |
14 |
15 | def test_filter():
16 | s = {1, 2, 3}
17 | st = frozenset(s)
18 | assert s.filter(lambda x: x >= 2) == {2, 3}
19 | assert st.filter(lambda x: x >= 2) == frozenset({2, 3})
20 |
21 |
22 | def test_filter_not():
23 | s = {1, 2, 3}
24 | st = frozenset(s)
25 | assert s.filter_not(lambda x: x < 2) == {2, 3}
26 | assert st.filter_not(lambda x: x < 2) == frozenset({2, 3})
27 |
28 |
29 | def test_flat_map():
30 | s = {1, 2, 3}
31 | st = frozenset(s)
32 | assert s.flat_map(lambda x: {x**2}) == {1, 4, 9}
33 | assert st.flat_map(lambda x: {x**2}) == frozenset({1, 4, 9})
34 |
35 |
36 | def test_contains():
37 | s = {1, 2, 3}
38 | st = frozenset(s)
39 | assert s.contains(2) == True
40 | assert st.contains(2) == True
41 |
42 |
43 | def test_group_by():
44 | s = {1, 2, 3}
45 | st = frozenset(s)
46 | assert {"abc", "def", "e"}.group_by(lambda s: len(s)) == {
47 | 3: {"abc", "def"},
48 | 1: {"e"},
49 | }
50 | assert frozenset({"abc", "def", "e"}).group_by(lambda s: len(s)) == {
51 | 3: {"abc", "def"},
52 | 1: {"e"},
53 | }
54 |
55 |
56 | def test_is_empty():
57 | s = {1, 2, 3}
58 | st = frozenset(s)
59 | assert s.is_empty() == False
60 | empty = set()
61 | assert empty.is_empty() == True
62 |
63 | assert st.is_empty() == False
64 | frozen_empty = frozenset()
65 | assert frozen_empty.is_empty() == True
66 |
67 |
68 | def test_size():
69 | s = {1, 2, 3}
70 | st = frozenset(s)
71 | assert s.size() == 3
72 | assert st.size() == 3
73 |
74 |
75 | def test_find():
76 | s = {1, 2, 3}
77 | st = frozenset(s)
78 | assert s.find(lambda x: x >= 3) == 3
79 | assert s.find(lambda x: x < 0) == None
80 |
81 | assert st.find(lambda x: x >= 3) == 3
82 | assert st.find(lambda x: x < 0) == None
83 |
84 |
85 | def test_foreach():
86 | s = {1, 2, 3}
87 | st = frozenset(s)
88 | tester = set()
89 | s.foreach(lambda x: tester.add(x))
90 | assert tester == s
91 |
92 | frozen_tester = set()
93 | st.foreach(lambda x: frozen_tester.add(x))
94 | assert frozen_tester == s
95 |
96 |
97 | def test_fold_left_plus():
98 | s = {1, 2, 3}
99 | st = frozenset(s)
100 | assert s.fold_left(0, lambda acc, n: acc + n) == 6
101 | assert st.fold_left(0, lambda acc, n: acc + n) == 6
102 |
103 |
104 | def test_fold_left_concat():
105 | s = {1, 2, 3}
106 | st = frozenset(s)
107 | a = s.fold_left("", lambda acc, n: acc + str(n))
108 | assert (
109 | a == "123" or a == "321" or a == "132" or a == "213" or a == "231" or a == "312"
110 | )
111 |
112 | frozen_a = st.fold_left("", lambda acc, n: acc + str(n))
113 | assert (
114 | frozen_a == "123"
115 | or frozen_a == "321"
116 | or frozen_a == "132"
117 | or frozen_a == "213"
118 | or frozen_a == "231"
119 | or frozen_a == "312"
120 | )
121 |
122 |
123 | def test_fold_right_plus():
124 | s = {1, 2, 3}
125 | st = frozenset(s)
126 | assert s.fold_right(0, lambda n, acc: acc + n) == 6
127 | assert st.fold_right(0, lambda n, acc: acc + n) == 6
128 |
129 |
130 | def test_fold_right_concat():
131 | s = {1, 2, 3}
132 | st = frozenset(s)
133 | a = s.fold_right("", lambda n, acc: acc + str(n))
134 | assert (
135 | a == "321" or a == "123" or a == "132" or a == "213" or a == "231" or a == "312"
136 | )
137 |
138 | frozen_a = st.fold_right("", lambda n, acc: acc + str(n))
139 | assert (
140 | frozen_a == "321"
141 | or frozen_a == "123"
142 | or frozen_a == "132"
143 | or frozen_a == "213"
144 | or frozen_a == "231"
145 | or frozen_a == "312"
146 | )
147 |
148 |
149 | def test_forall_gt_zero():
150 | s = {1, 2, 3}
151 | st = frozenset(s)
152 | assert s.forall(lambda n: n > 0) == True
153 | assert st.forall(lambda n: n > 0) == True
154 |
155 |
156 | def test_forall_gt_two():
157 | s = {1, 2, 3}
158 | st = frozenset(s)
159 | assert s.forall(lambda n: n > 2) == False
160 | assert st.forall(lambda n: n > 2) == False
161 |
162 |
163 | def test_length():
164 | s = {1, 2, 3}
165 | st = frozenset(s)
166 | assert s.length() == 3
167 | assert st.length() == 3
168 |
169 |
170 | def test_length_equal_size():
171 | s = {1, 2, 3}
172 | st = frozenset(s)
173 | assert s.size() == s.length()
174 | assert st.size() == st.length()
175 |
176 |
177 | def test_to_iterator():
178 | s = {1, 2, 3}
179 | st = frozenset(s)
180 | sbis = s.copy()
181 |
182 | it = s.to_iterator()
183 | sit = st.to_iterator()
184 |
185 | n = next(it)
186 | ns = next(sit)
187 | assert n in s
188 | assert ns in st
189 |
190 | remaining = set(it)
191 | remainings = set(sit)
192 | s.remove(n)
193 | sbis.remove(ns)
194 | assert remaining == s
195 | assert remainings == sbis
196 |
197 |
198 | # Parallel operations
199 |
200 |
201 | def test_par_map():
202 | s = {1, 2, 3}
203 | st = frozenset(s)
204 | assert s.par_map(lambda x: x * 2) == {2, 4, 6}
205 | assert st.par_map(lambda x: x * 2) == frozenset({2, 4, 6})
206 |
207 |
208 | def test_par_filter():
209 | s = {1, 2, 3}
210 | st = frozenset(s)
211 | assert s.par_filter(lambda x: x >= 2) == {2, 3}
212 | assert st.par_filter(lambda x: x >= 2) == frozenset({2, 3})
213 |
214 |
215 | def test_par_filter_not():
216 | s = {1, 2, 3}
217 | st = frozenset(s)
218 | assert s.par_filter_not(lambda x: x < 2) == {2, 3}
219 | assert st.par_filter_not(lambda x: x < 2) == frozenset({2, 3})
220 |
221 |
222 | def test_par_flat_map():
223 | s = {1, 2, 3}
224 | st = frozenset(s)
225 | assert s.par_flat_map(lambda x: [x**2]) == {1, 4, 9}
226 | assert st.par_flat_map(lambda x: {x**2}) == frozenset({1, 4, 9})
227 |
228 |
229 | # Pure operations
230 |
231 |
232 | def test_pure_map():
233 | s = {1, 2, 3}
234 | st = frozenset(s)
235 | assert s.pure_map(lambda x: x * 2) == {2, 4, 6}
236 | assert st.pure_map(lambda x: x * 2) == {2, 4, 6}
237 |
238 |
239 | def test_pure_flat_map():
240 | s = {1, 2, 3}
241 | st = frozenset(s)
242 | assert s.pure_flat_map(lambda x: [x**2]) == {1, 4, 9}
243 | assert st.pure_flat_map(lambda x: [x**2]) == {1, 4, 9}
244 |
245 |
246 | def test_pure_filter():
247 | s = {1, 2, 3}
248 | st = frozenset(s)
249 | assert s.pure_filter(lambda x: x >= 2) == {2, 3}
250 | assert st.pure_filter(lambda x: x >= 2) == {2, 3}
251 |
252 |
253 | def test_pure_filter_not():
254 | s = {1, 2, 3}
255 | st = frozenset(s)
256 | assert s.pure_filter_not(lambda x: x >= 2) == {1}
257 | assert st.pure_filter_not(lambda x: x >= 2) == {1}
258 |
259 |
260 | # Lazy operations
261 |
262 |
263 | def test_lazy_map():
264 | s = {1, 2, 3}
265 | st = frozenset(s)
266 | res = s.lazy_map(lambda x: x * 2)
267 |
268 | assert next(res) == 2
269 | assert set(res) == {4, 6}
270 |
271 |
272 | def test_lazy_flat_map():
273 | s = {1, 2, 3}
274 | st = frozenset(s)
275 | res = s.lazy_flat_map(lambda x: [x * 2])
276 |
277 | assert next(res) == 2
278 | assert set(res) == {4, 6}
279 |
280 |
281 | def test_lazy_filter():
282 | s = {1, 2, 3}
283 | st = frozenset(s)
284 | res = s.lazy_filter(lambda x: x >= 2)
285 |
286 | assert next(res) == 2
287 | assert set(res) == {3}
288 |
289 |
290 | def test_lazy_filter_not():
291 | s = {1, 2, 3}
292 | st = frozenset(s)
293 | res = s.lazy_filter_not(lambda x: x < 2)
294 | assert next(res) == 2
295 | assert set(res) == {3}
296 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | dask==2023.3.1
2 | forbiddenfruit==0.1.4
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
3 |
4 | [wheel]
5 | universal = 0
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | setup(
4 | name="pyfuncol",
5 | description="Functional collections extension functions for Python",
6 | long_description=open("README.md").read(),
7 | long_description_content_type="text/markdown",
8 | url="https://github.com/didactic-meme/pyfuncol",
9 | author="Andrea Veneziano",
10 | author_email="andrea.veneziano@icloud.com",
11 | maintainer="Andrea Veneziano",
12 | maintainer_email="andrea.veneziano@icloud.com",
13 | license="MIT",
14 | keywords="functional pipeline data collection chain parallel",
15 | packages=find_packages(exclude=["contrib", "docs", "*tests*", "test"]),
16 | version="1.3.1",
17 | install_requires=["forbiddenfruit", "dask"],
18 | extras_requires={
19 | "dev": [
20 | "pytest",
21 | "pytest-cov",
22 | "myst-parser",
23 | "black",
24 | "Sphinx",
25 | "sphinx-rtd-theme",
26 | ]
27 | },
28 | classifiers=[
29 | "Development Status :: 4 - Beta",
30 | "Intended Audience :: Developers",
31 | "License :: OSI Approved :: MIT License",
32 | "Programming Language :: Python :: 3.6",
33 | "Programming Language :: Python :: 3.7",
34 | "Programming Language :: Python :: 3.8",
35 | "Programming Language :: Python :: 3.9",
36 | "Programming Language :: Python :: Implementation :: CPython",
37 | "Natural Language :: English",
38 | "Operating System :: OS Independent",
39 | "Topic :: Software Development :: Libraries :: Python Modules",
40 | ],
41 | )
42 |
--------------------------------------------------------------------------------