pythonic-fp.containers 4.0.1__tar.gz → 4.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. pythonic_fp_containers-4.1.0/.github/workflows/static.yml +64 -0
  2. {pythonic_fp_containers-4.0.1 → pythonic_fp_containers-4.1.0}/.gitignore +2 -0
  3. {pythonic_fp_containers-4.0.1 → pythonic_fp_containers-4.1.0}/CHANGELOG.rst +18 -0
  4. {pythonic_fp_containers-4.0.1 → pythonic_fp_containers-4.1.0}/PKG-INFO +13 -13
  5. {pythonic_fp_containers-4.0.1 → pythonic_fp_containers-4.1.0}/README.rst +6 -6
  6. pythonic_fp_containers-4.1.0/docs/Makefile +78 -0
  7. pythonic_fp_containers-4.1.0/docs/gen_conf.py +110 -0
  8. pythonic_fp_containers-4.1.0/docs/requirements.txt +2 -0
  9. pythonic_fp_containers-4.1.0/docs/source/_static/custom.css +3 -0
  10. pythonic_fp_containers-4.1.0/docs/source/api/functional_tuple.rst +6 -0
  11. pythonic_fp_containers-4.1.0/docs/source/api/immutable_list.rst +6 -0
  12. pythonic_fp_containers-4.1.0/docs/source/api/index.rst +19 -0
  13. pythonic_fp_containers-4.1.0/docs/source/changelog.rst +7 -0
  14. pythonic_fp_containers-4.1.0/docs/source/description.rst +7 -0
  15. pythonic_fp_containers-4.1.0/docs/source/index.rst +30 -0
  16. pythonic_fp_containers-4.1.0/docs/source/releases.rst +22 -0
  17. pythonic_fp_containers-4.1.0/docs/source/usage.rst +21 -0
  18. {pythonic_fp_containers-4.0.1 → pythonic_fp_containers-4.1.0}/pyproject.toml +22 -18
  19. {pythonic_fp_containers-4.0.1 → pythonic_fp_containers-4.1.0}/src/pythonic_fp/containers/__init__.py +8 -9
  20. pythonic_fp_containers-4.1.0/src/pythonic_fp/containers/__init__.pyi +0 -0
  21. {pythonic_fp_containers-4.0.1 → pythonic_fp_containers-4.1.0}/src/pythonic_fp/containers/functional_tuple.py +77 -36
  22. {pythonic_fp_containers-4.0.1 → pythonic_fp_containers-4.1.0}/src/pythonic_fp/containers/immutable_list.py +146 -38
  23. {pythonic_fp_containers-4.0.1 → pythonic_fp_containers-4.1.0}/src/pythonic_fp/containers/immutable_list.pyi +1 -1
  24. pythonic_fp_containers-4.1.0/src/pythonic_fp/containers/py.typed +0 -0
  25. {pythonic_fp_containers-4.0.1 → pythonic_fp_containers-4.1.0}/LICENSE +0 -0
  26. /pythonic_fp_containers-4.0.1/src/pythonic_fp/containers/__init__.pyi → /pythonic_fp_containers-4.1.0/docs/source/_static/.gitkeep +0 -0
  27. /pythonic_fp_containers-4.0.1/src/pythonic_fp/containers/py.typed → /pythonic_fp_containers-4.1.0/docs/source/_templates/.gitkeep +0 -0
  28. {pythonic_fp_containers-4.0.1 → pythonic_fp_containers-4.1.0}/src/pythonic_fp/containers/functional_tuple.pyi +0 -0
  29. {pythonic_fp_containers-4.0.1 → pythonic_fp_containers-4.1.0}/tests/test_functional_tuple.py +0 -0
  30. {pythonic_fp_containers-4.0.1 → pythonic_fp_containers-4.1.0}/tests/test_immutable_list.py +0 -0
  31. {pythonic_fp_containers-4.0.1 → pythonic_fp_containers-4.1.0}/tests/test_immutable_list_fp.py +0 -0
@@ -0,0 +1,64 @@
1
+ name: Build and Deploy Furo Themed Sphinx Documentation
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ workflow_dispatch:
7
+
8
+ env:
9
+ RELEASE: '4.1.0'
10
+ DEVEL: '5.0.0'
11
+ PYTHON: '3.14'
12
+
13
+ permissions:
14
+ contents: read
15
+ pages: write
16
+ id-token: write
17
+
18
+ concurrency:
19
+ group: "pages"
20
+ cancel-in-progress: false
21
+
22
+ jobs:
23
+ deploy:
24
+ runs-on: ubuntu-latest
25
+ environment:
26
+ name: github-pages
27
+ url: ${{ steps.deployment.outputs.page_url }}
28
+ steps:
29
+ - name: Checkout
30
+ uses: actions/checkout@v6
31
+
32
+ - name: Select Python
33
+ uses: actions/setup-python@v6
34
+ with:
35
+ python-version: ${{ env.PYTHON }}
36
+
37
+ - name: Install Sphinx Dependencies
38
+ run: |
39
+ pip install -r docs/requirements.txt
40
+ sudo apt-get install -y graphviz
41
+
42
+ - name: Build Release Docs
43
+ run: |
44
+ pip install pythonic-fp-circulararray
45
+ docs/gen_conf.py release ${{ env.RELEASE }} Containers containers > docs/source/conf.py
46
+ sphinx-build -M html docs/source docs/build/release
47
+
48
+ - name: Build Development Docs
49
+ run: |
50
+ pip install -e .
51
+ docs/gen_conf.py devel ${{ env.DEVEL }} Containers containers > docs/source/conf.py
52
+ sphinx-build -M html docs/source docs/build/development
53
+
54
+ - name: Setup Pages
55
+ uses: actions/configure-pages@v6
56
+
57
+ - name: Upload Artifact
58
+ uses: actions/upload-pages-artifact@v5
59
+ with:
60
+ path: docs/build
61
+
62
+ - name: Deploy to GitHub Pages
63
+ uses: actions/deploy-pages@v5
64
+ id: deployment
@@ -1,6 +1,8 @@
1
1
  # Minimal version - only add to when necessary
2
2
  **/__pycache__/
3
3
  dist/
4
+ docs/build/
5
+ docs/source/conf.py
4
6
  **/.dmypy.json
5
7
  **/.mypy_cache/
6
8
  **/.pytest_cache/
@@ -17,6 +17,24 @@ See `Semantic Versioning 2.0.0 <https://semver.org>`_.
17
17
  Releases and Important Milestones
18
18
  ---------------------------------
19
19
 
20
+ PyPI 4.1.0 - 2026-05-14
21
+ ~~~~~~~~~~~~~~~~~~~~~~~
22
+
23
+ Documentation now in maintenance mode.
24
+
25
+ Intend to make these containers final instead of inheritable.
26
+
27
+ Development Status Reappraisal - 2026-05-05
28
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29
+
30
+ Maintainer appraised the Development Status for
31
+ pythonic-fp-containers to be ``"3 - Alpha"``.
32
+
33
+ PyPI 4.0.1 - 2026-01-19
34
+ ~~~~~~~~~~~~~~~~~~~~~~~
35
+
36
+ Docstring improvements.
37
+
20
38
  PyPI 4.0.0 - 2025-09-09
21
39
  ~~~~~~~~~~~~~~~~~~~~~~~
22
40
 
@@ -1,26 +1,26 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonic-fp-containers
3
- Version: 4.0.1
4
- Summary: Container with FP interfaces
3
+ Version: 4.1.0
4
+ Summary: Containers with FP interfaces
5
5
  Keywords: functional_containers,functional_tuple,immutable_list
6
6
  Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
7
- Requires-Python: >=3.12
7
+ Requires-Python: >=3.13
8
8
  Description-Content-Type: text/x-rst
9
9
  Classifier: Development Status :: 3 - Alpha
10
10
  Classifier: Framework :: Pytest
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: Apache Software License
13
13
  Classifier: Operating System :: OS Independent
14
- Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
15
  Classifier: Typing :: Typed
16
16
  License-File: LICENSE
17
- Requires-Dist: pythonic-fp-circulararray>=6.0.1
17
+ Requires-Dist: pythonic-fp-circulararray>=6.0.4
18
18
  Requires-Dist: pythonic-fp-fptools>=5.2.0
19
19
  Requires-Dist: pythonic-fp-iterables>=5.1.3
20
20
  Requires-Dist: pytest>=8.4.1 ; extra == "test"
21
21
  Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-containers/blob/main/CHANGELOG.rst
22
- Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/containers/development/build/html/
23
- Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/homepage/build/html/
22
+ Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/projects/containers.html
23
+ Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/
24
24
  Project-URL: Source, https://github.com/grscheller/pythonic-fp-containers
25
25
  Provides-Extra: test
26
26
 
@@ -28,8 +28,8 @@ Pythonic FP - Containers
28
28
  ========================
29
29
 
30
30
  PyPI project
31
- `pythonic-fp.containers
32
- <https://pypi.org/project/pythonic-fp.containers>`_.
31
+ `pythonic-fp-containers
32
+ <https://pypi.org/project/pythonic-fp-containers>`_.
33
33
 
34
34
  Python package of container like data structures with functional
35
35
  programming inspired tweaks.
@@ -44,19 +44,19 @@ programming inspired tweaks.
44
44
 
45
45
  Part of the
46
46
  `pythonic-fp
47
- <https://grscheller.github.io/pythonic-fp>`_
47
+ <https://grscheller.github.io/pythonic-fp/>`_
48
48
  PyPI projects.
49
49
 
50
50
  Documentation
51
51
  -------------
52
52
 
53
- Documentation for this project is hosted on
53
+ Documentation and other links for this project are hosted on
54
54
  `GitHub Pages
55
- <https://grscheller.github.io/pythonic-fp/containers>`_.
55
+ <https://grscheller.github.io/pythonic-fp/projects/containers.html>`_.
56
56
 
57
57
  Copyright and License
58
58
  ---------------------
59
59
 
60
- Copyright (c) 2023-2025 Geoffrey R. Scheller. Licensed under the Apache
60
+ Copyright (c) 2023-2026 Geoffrey R. Scheller. Licensed under the Apache
61
61
  License, Version 2.0. See the LICENSE file for details.
62
62
 
@@ -2,8 +2,8 @@ Pythonic FP - Containers
2
2
  ========================
3
3
 
4
4
  PyPI project
5
- `pythonic-fp.containers
6
- <https://pypi.org/project/pythonic-fp.containers>`_.
5
+ `pythonic-fp-containers
6
+ <https://pypi.org/project/pythonic-fp-containers>`_.
7
7
 
8
8
  Python package of container like data structures with functional
9
9
  programming inspired tweaks.
@@ -18,18 +18,18 @@ programming inspired tweaks.
18
18
 
19
19
  Part of the
20
20
  `pythonic-fp
21
- <https://grscheller.github.io/pythonic-fp>`_
21
+ <https://grscheller.github.io/pythonic-fp/>`_
22
22
  PyPI projects.
23
23
 
24
24
  Documentation
25
25
  -------------
26
26
 
27
- Documentation for this project is hosted on
27
+ Documentation and other links for this project are hosted on
28
28
  `GitHub Pages
29
- <https://grscheller.github.io/pythonic-fp/containers>`_.
29
+ <https://grscheller.github.io/pythonic-fp/projects/containers.html>`_.
30
30
 
31
31
  Copyright and License
32
32
  ---------------------
33
33
 
34
- Copyright (c) 2023-2025 Geoffrey R. Scheller. Licensed under the Apache
34
+ Copyright (c) 2023-2026 Geoffrey R. Scheller. Licensed under the Apache
35
35
  License, Version 2.0. See the LICENSE file for details.
@@ -0,0 +1,78 @@
1
+ # Makefile for Sphinx documentation
2
+
3
+ # Last and future releases
4
+ # - former needs to agree with PyPI
5
+ # - later needs to agree with pyproject.toml
6
+ PROJECT_NAME = Containers
7
+ PYPI_NAME = containers
8
+ RELEASE_VERSION = 4.1.0
9
+ DEVEL_VERSION = 5.0.0
10
+ CUSTOM_VERSION = 0.0.0
11
+
12
+ SPHINXOPTS ?=
13
+ SPHINXBUILD ?= sphinx-build
14
+ SOURCEDIR = source
15
+ BUILDDIR = build
16
+
17
+ STATICDIR = source/_static
18
+ TEMPLATEDIR = source/_templates
19
+
20
+ # Put help first so that "make" without argument is like "make help".
21
+ help:
22
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS)
23
+
24
+ clean_source:
25
+ @rm -f "$(SOURCEDIR)"/conf.py
26
+
27
+ clean_devel:
28
+ @rm -rf "$(BUILDDIR)"/development
29
+
30
+ clean_release:
31
+ @rm -rf "$(BUILDDIR)"/release
32
+
33
+ clean_custom:
34
+ @rm -rf "$(BUILDDIR)"/custom
35
+
36
+ clean: clean_devel clean_release clean_custom clean_source
37
+ @rm -rf $(BUILDDIR)
38
+ @echo Removed all doc build artifacts.
39
+
40
+ setup_devel: clean_devel
41
+ @./gen_conf.py devel "$(DEVEL_VERSION)" "$(PROJECT_NAME)" "$(PYPI_NAME)" > "$(SOURCEDIR)"/conf.py
42
+
43
+ setup_release: clean_release
44
+ @./gen_conf.py release "$(RELEASE_VERSION)" "$(PROJECT_NAME)" "$(PYPI_NAME)" > "$(SOURCEDIR)"/conf.py
45
+
46
+ setup_custom: clean_custom
47
+ @./gen_conf.py custom "$(CUSTOM_VERSION)" "$(PROJECT_NAME)" "$(PYPI_NAME)" > "$(SOURCEDIR)"/conf.py
48
+
49
+ setup_redo: clean_custom
50
+ @echo Redoing custom build with a possibly reeditted conf.py
51
+ @./gen_conf.py redo "" "" ""
52
+
53
+ # Build development labeled html docs.
54
+ devel: Makefile setup_devel
55
+ @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)"/development $(SPHINXOPTS)
56
+
57
+ # Build release labeled html docs.
58
+ release: Makefile setup_release
59
+ @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)"/release $(SPHINXOPTS)
60
+
61
+ # Build custom labeled html docs.
62
+ custom: Makefile setup_custom
63
+ @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)"/custom $(SPHINXOPTS)
64
+
65
+ # Build HTML docs with previous generated (possibly hand edited) source/conf.py,
66
+ # put results in $(BUILDDIR)/custom.
67
+ redo: Makefile setup_redo
68
+ @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)"/custom $(SPHINXOPTS)
69
+
70
+ # Catch-all target: route all unknown targets to Sphinx and save to $(BUILDDIR)/custom.
71
+ # Reuse a previously generated source/conf.py.
72
+ %: Makefile setup_redo
73
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)"/custom $(SPHINXOPTS)
74
+
75
+ .PHONY: help Makefile
76
+ .PHONY: setup_devel setup_release setup_custom
77
+ .PHONY: devel release
78
+ .PHONY: clean_source clean_devel clean_release clean_custom clean
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env python
2
+ # Generate conf.py Sphinx configuration executable
3
+
4
+ import sys
5
+ import os
6
+
7
+ project_prefix = 'Pythonic FP'
8
+ pypi_prefix = 'pythonic-fp'
9
+ author = 'Geoffrey R. Scheller'
10
+ copyright = f'2023-2026, {author}'
11
+
12
+ args = sys.argv[1:]
13
+ num_args = len(args)
14
+
15
+ if num_args == 4:
16
+ build_type, version, project_name, pypi_name = args
17
+ project = project_prefix + ' - ' + project_name
18
+ pypi_project_repo = pypi_prefix + '-' + pypi_name
19
+ project_url_rst = f'`{project} <https://pypi.org/project/{pypi_project_repo}/>`_'
20
+ homepage_url_rst = f'`{project_prefix} <https://grscheller.github.io/{pypi_prefix}/>`_'
21
+
22
+ else:
23
+ sys.exit('Error: gen_conf.py takes exactly 4 arguments')
24
+
25
+ match build_type:
26
+ case 'custom':
27
+ release = f'{version}'
28
+ release_string = f'**Custom PyPI Non-release version {release}**'
29
+ case 'devel':
30
+ release = f'{version}'
31
+ release_string = f'**Proposed PyPI release version {release}**'
32
+ case 'release':
33
+ release = f'{version}'
34
+ release_string = f'**PyPI release version {release}**'
35
+ case 'redo':
36
+ if not os.path.isfile('source/conf.py'):
37
+ sys.exit('Error: cannot redo without source/conf.py')
38
+ sys.exit()
39
+ case _:
40
+ sys.exit(f'Error: unknown built_type {build_type} given')
41
+
42
+ print(f'''# Generated conf.py Sphinx configuration executable
43
+
44
+ from typing import Any
45
+ from sphinx.application import Sphinx
46
+
47
+ project = '{project}'
48
+ author = '{author}'
49
+ copyright = '{copyright}'
50
+ release = '{release}'
51
+ release_string = '{release_string}'
52
+
53
+ def skip_abc_methods(
54
+ app: Any, what: str, name: str, obj: Any, skip: bool, options: Any
55
+ ) -> bool:
56
+ if name in [
57
+ '__init_subclass__',
58
+ '__subclasshook__',
59
+ '__class_getitem__',
60
+ '__weakref__',
61
+ ]:
62
+ return True # Skip these members
63
+ return skip
64
+
65
+
66
+ def setup(app: Sphinx) -> None:
67
+ app.connect('autodoc-skip-member', skip_abc_methods)
68
+
69
+
70
+ extensions = [
71
+ 'sphinx.ext.autodoc',
72
+ 'sphinx.ext.graphviz',
73
+ ]
74
+
75
+ autodoc_default_options = {{
76
+ 'members': True,
77
+ 'private-members': True,
78
+ 'special-members': True,
79
+ 'inherited-members': False,
80
+ 'show-inheritance': True,
81
+ }}
82
+ autodoc_member_order = 'bysource'
83
+ autoclass_content = 'class'
84
+ autodoc_class_signature = 'separated'
85
+ autodoc_typehints_format = 'short'
86
+ autodoc_use_type_comments = True
87
+ autodoc_docstring_signature = False
88
+ autodoc_preserve_defaults = True
89
+ autodoc_warningiserror = False
90
+
91
+ templates_path = ['_templates']
92
+ exclude_patterns: list[str] = []
93
+
94
+ html_theme_options = {{
95
+ 'light_css_variables': {{
96
+ 'color-link--visited': 'var(--color-link)',
97
+ }},
98
+ 'dark_css_variables': {{
99
+ 'color-link--visited': 'var(--color-link)',
100
+ }},
101
+ }}
102
+ html_theme = 'furo'
103
+ html_static_path = ['_static']
104
+ html_css_files = ['custom.css']
105
+
106
+ rst_epilog = """
107
+ .. |RELEASE_STRING| replace:: {release_string}
108
+
109
+ """
110
+ ''')
@@ -0,0 +1,2 @@
1
+ sphinx>=9.1
2
+ furo
@@ -0,0 +1,3 @@
1
+ dl.field-list > dt {
2
+ text-transform: none !important;
3
+ }
@@ -0,0 +1,6 @@
1
+ module functional_tuple
2
+ =======================
3
+
4
+ .. automodule:: pythonic_fp.containers.functional_tuple
5
+ :members:
6
+ :special-members:
@@ -0,0 +1,6 @@
1
+ module immutable_list
2
+ =====================
3
+
4
+ .. automodule:: pythonic_fp.containers.immutable_list
5
+ :members:
6
+ :special-members:
@@ -0,0 +1,19 @@
1
+ Containers
2
+ ==========
3
+
4
+ .. automodule:: pythonic_fp.containers
5
+ :no-members:
6
+ :ignore-module-all:
7
+ :no-index:
8
+
9
+ .. toctree::
10
+ :caption: Functional Tuple
11
+ :maxdepth: 1
12
+
13
+ functional_tuple
14
+
15
+ .. toctree::
16
+ :caption: Immutable List
17
+ :maxdepth: 1
18
+
19
+ immutable_list
@@ -0,0 +1,7 @@
1
+ Changelog
2
+ =========
3
+
4
+ Change log for the
5
+ `pythonic-fp-containers
6
+ <https://github.com/grscheller/pythonic-fp-containers/blob/main/CHANGELOG.rst>`_
7
+ PyPI project.
@@ -0,0 +1,7 @@
1
+ Description
2
+ -----------
3
+
4
+ .. automodule:: pythonic_fp.containers
5
+ :synopsis:
6
+ :noindex:
7
+
@@ -0,0 +1,30 @@
1
+ pythonic-fp-containers
2
+ ======================
3
+
4
+ Project
5
+ `Pythonic FP - Containers <https://pypi.org/project/pythonic-fp-containers/>`_
6
+ one of the
7
+ `Pythonic FP <https://grscheller.github.io/pythonic-fp/>`_
8
+ PyPI projects.
9
+
10
+ |RELEASE_STRING|
11
+
12
+ .. toctree::
13
+ :caption: Overview
14
+ :hidden:
15
+
16
+ self
17
+
18
+ .. toctree::
19
+ :caption: User Documentation
20
+
21
+ description
22
+ usage
23
+ releases
24
+ changelog
25
+
26
+ .. toctree::
27
+ :caption: API Documentation
28
+ :maxdepth: 1
29
+
30
+ api/index
@@ -0,0 +1,22 @@
1
+ PyPI releases
2
+ =============
3
+
4
+ **pythonic-fp-containers:** pythonic_fp.containers
5
+
6
+ +----------------------------------------------------------------------------------------+--------------+
7
+ | PyPI Release | Release date |
8
+ +========================================================================================+==============+
9
+ | `development <https://grscheller.github.io/pythonic-fp-containers/development/html/>`_ | |
10
+ +----------------------------------------------------------------------------------------+--------------+
11
+ | `v4.1.0 <https://grscheller.github.io/pythonic-fp-containers/release/html/>`_ | 2026-05-14 |
12
+ +----------------------------------------------------------------------------------------+--------------+
13
+ | v4.0.1 | 2026-01-18 |
14
+ +----------------------------------------------------------------------------------------+--------------+
15
+ | v4.0.0 | 2025-09-28 |
16
+ +----------------------------------------------------------------------------------------+--------------+
17
+ | v3.0.1 | 2025-09-09 |
18
+ +----------------------------------------------------------------------------------------+--------------+
19
+ | v3.0.0 | 2025-08-02 |
20
+ +----------------------------------------------------------------------------------------+--------------+
21
+ | v2.0.0 | 2025-07-06 |
22
+ +----------------------------------------------------------------------------------------+--------------+
@@ -0,0 +1,21 @@
1
+ Usage
2
+ =====
3
+
4
+ How to install the package
5
+ --------------------------
6
+
7
+ Install the project into your Python environment:
8
+
9
+ .. code:: console
10
+
11
+ $ pip install pythonic-fp.containers
12
+
13
+ Importing the package
14
+ ---------------------
15
+
16
+ Import the functional container classes into your code.
17
+
18
+ .. code:: python
19
+
20
+ from pythonic_fp.containers.functional_tuple import FTuple
21
+ from pythonic_fp.containers.immutable_list import IList
@@ -1,10 +1,15 @@
1
+ [build-system]
2
+ requires = ["flit_core>=3.12,<4"]
3
+ build-backend = "flit_core.buildapi"
4
+
1
5
  [project]
2
6
  name = "pythonic-fp-containers"
3
- version = "4.0.1"
7
+ version = "4.1.0"
4
8
  readme = "README.rst"
5
- requires-python = ">=3.12"
6
- license = { file = "LICENSE" }
7
- authors = [{ name = "Geoffrey R. Scheller", email = "geoffrey@scheller.com" }]
9
+ requires-python = ">=3.13"
10
+ authors = [
11
+ { name = "Geoffrey R. Scheller", email = "geoffrey@scheller.com" },
12
+ ]
8
13
  keywords = [
9
14
  "functional_containers",
10
15
  "functional_tuple",
@@ -16,11 +21,11 @@ classifiers = [
16
21
  "Intended Audience :: Developers",
17
22
  "License :: OSI Approved :: Apache Software License",
18
23
  "Operating System :: OS Independent",
19
- "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
20
25
  "Typing :: Typed",
21
26
  ]
22
27
  dependencies = [
23
- "pythonic-fp-circulararray>=6.0.1",
28
+ "pythonic-fp-circulararray>=6.0.4",
24
29
  "pythonic-fp-fptools>=5.2.0",
25
30
  "pythonic-fp-iterables>=5.1.3",
26
31
  ]
@@ -28,8 +33,8 @@ dynamic = ["description"]
28
33
 
29
34
  [project.urls]
30
35
  Changelog = "https://github.com/grscheller/pythonic-fp-containers/blob/main/CHANGELOG.rst"
31
- Documentation = "https://grscheller.github.io/pythonic-fp/containers/development/build/html/"
32
- Homepage = "https://grscheller.github.io/pythonic-fp/homepage/build/html/"
36
+ Documentation = "https://grscheller.github.io/pythonic-fp/projects/containers.html"
37
+ Homepage = "https://grscheller.github.io/pythonic-fp/"
33
38
  Source = "https://github.com/grscheller/pythonic-fp-containers"
34
39
 
35
40
  [project.optional-dependencies]
@@ -37,17 +42,11 @@ test = [
37
42
  "pytest>=8.4.1",
38
43
  ]
39
44
 
40
- [build-system]
41
- requires = ["flit_core>=3.12,<4"]
42
- build-backend = "flit_core.buildapi"
43
-
44
45
  [tool.flit.module]
45
46
  name = "pythonic_fp.containers"
46
47
 
47
48
  [tool.mypy]
48
- enable_incomplete_feature = "NewGenericSyntax"
49
49
  explicit_package_bases = true
50
- implicit_reexport = false
51
50
  local_partial_types = true
52
51
  namespace_packages = true
53
52
  warn_redundant_casts = true
@@ -61,19 +60,24 @@ dmypy = false
61
60
  strict = true
62
61
  report_progress = true
63
62
 
64
- [tool.pytest.ini_options]
65
- addopts = "-ra"
63
+ [tool.pytest]
64
+ addopts = [
65
+ "-ra",
66
+ ]
66
67
  consider_namespace_packages = true
67
68
  testpaths = ["tests/"]
68
69
 
69
70
  [tool.ruff]
70
- target-version = "py313"
71
+ target-version = "py314"
71
72
 
72
73
  [tool.ruff.lint.flake8-quotes]
73
74
  docstring-quotes = "double"
74
75
 
75
76
  [tool.ruff.lint.per-file-ignores]
76
- "tests/*" = ["C901"]
77
+ "tests/**/*.py" = [
78
+ "E731",
79
+ "C901",
80
+ ]
77
81
  "**/*.py" = ["E741"]
78
82
 
79
83
  [tool.ruff.format]
@@ -13,16 +13,15 @@
13
13
  # limitations under the License.
14
14
 
15
15
  """
16
- Container with FP interfaces
17
- ----------------------------
16
+ Containers with FP interfaces
17
+ -----------------------------
18
18
 
19
- +---------------------------------------------+-----------+------------------+
20
- | module | class | description |
21
- +=============================================+===========+==================+
22
- | ``pythonic_fp.containers.functional_tuple`` | FTuple | Functional Tuple |
23
- +---------------------------------------------+-----------+------------------+
24
- | ``pythonic_fp.containers.immutable_list`` | IList | Immutable List |
25
- +---------------------------------------------+-----------+------------------+
19
+ .. admonition:: Functional variants of Python builtins
20
+
21
+ Not sure the direction this should take.
22
+
23
+ - class **FTuple:** A tuple-like object with a functional interface.
24
+ - class **IList:** An immutable, hashable list-like object.
26
25
 
27
26
  """
28
27
 
@@ -13,8 +13,6 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- """Pythonic FP - Functional Tuple"""
17
-
18
16
  from collections.abc import Callable, Iterator
19
17
  from typing import cast, Never, overload, SupportsIndex
20
18
  from pythonic_fp.iterables.folding import accumulate
@@ -25,26 +23,42 @@ __all__ = ['FTuple']
25
23
 
26
24
  class FTuple[D](tuple[D, ...]):
27
25
  """
28
- .. admonition:: Functional Tuple suitable for inheritance.
26
+ .. admonition:: Functional Tuple
29
27
 
28
+ - intended for further inheritance
30
29
  - supports both indexing and slicing
31
30
  - int multiplication and FTuple addition supported
32
31
  - addition concatenates results, resulting in a Union type
33
32
  - both left and right int multiplication supported
34
33
  - homogeneous in its data type
35
- - supports being further inherited from
36
34
  - unslotted
37
35
 
36
+ .. note::
37
+
38
+ Example of code very early in project. Different ways
39
+ to use __dunders__ than what I do lately.
40
+
38
41
  """
39
42
 
40
43
  def __reversed__(self) -> Iterator[D]:
44
+ """
45
+ .. admonition:: reverse iter
46
+
47
+ :yields: the tuples elements in reverse order
48
+
49
+ """
41
50
  for ii in range(len(self) - 1, -1, -1):
42
51
  yield (self[ii])
43
52
 
44
- def __repr__(self) -> str:
45
- return f'{self.__class__.__name__}(' + ', '.join(map(repr, self)) + ')'
46
-
47
53
  def __eq__(self, other: object, /) -> bool:
54
+ """
55
+ .. admonition:: equality comparison
56
+
57
+ :param other: The ``object`` to which to be compared.
58
+ :returns: ``True`` if ``other`` is the same type and
59
+ corresponding elements compare as equal.
60
+
61
+ """
48
62
  if not isinstance(other, self.__class__):
49
63
  return False
50
64
  if (length := len(self)) != len(other):
@@ -67,6 +81,9 @@ class FTuple[D](tuple[D, ...]):
67
81
  else:
68
82
  return super().__getitem__(idx)
69
83
 
84
+ def __repr__(self) -> str:
85
+ return f'{self.__class__.__name__}(' + ', '.join(map(repr, self)) + ')'
86
+
70
87
  def foldl[L](
71
88
  self,
72
89
  f: Callable[[L, D], L],
@@ -74,16 +91,20 @@ class FTuple[D](tuple[D, ...]):
74
91
  start: L | None = None,
75
92
  default: L | None = None,
76
93
  ) -> L:
77
- """Fold Left with optional starting and default values.
94
+ """
95
+ .. admonition:: fold left
78
96
 
79
- - fold left with an optional starting value
80
- - first argument of function ``f`` is for the accumulated value
97
+ Reduce left with optional starting and default values.
81
98
 
82
- :param f: The folding function, first argument is for the accumulated value.
83
- :param start: An optional starting value.
84
- :param default: An optional default value if fold does not exist.
85
- :raises ValueError: When ``FTuple`` empty and a start value not given.
86
- :returns: The folded value if it exists, otherwise the default value.
99
+ :param f: The folding function, first argument is for
100
+ the accumulated value.
101
+ :param start: An optional starting value.
102
+ :param default: An optional default value
103
+ if fold does not exist.
104
+ :raises ValueError: When ``FTuple`` empty and a start value
105
+ was not not given.
106
+ :returns: Folded value if it exists, otherwise
107
+ the default value if given.
87
108
 
88
109
  """
89
110
  it_self = iter(self)
@@ -107,13 +128,20 @@ class FTuple[D](tuple[D, ...]):
107
128
  start: R | None = None,
108
129
  default: R | None = None,
109
130
  ) -> R:
110
- """Fold Right with optional starting and default values.
131
+ """
132
+ .. admonition:: fold right
111
133
 
112
- :param f: The folding function, second argument is for the accumulated value.
113
- :param start: An optional starting value.
114
- :param default: An optional default value if fold does not exist.
115
- :raises ValueError: when FTuple empty and a start value not given
116
- :returns: The folded value if it exists, otherwise the default value.
134
+ Reduce right with optional starting and default values.
135
+
136
+ :param f: The folding function, second argument is for
137
+ the accumulated value.
138
+ :param start: An optional starting value.
139
+ :param default: An optional default value
140
+ if fold does not exist.
141
+ :raises ValueError: when ``FTuple`` empty and a start value
142
+ was not given
143
+ :returns: The folded value if it exists, otherwise
144
+ the default value if given.
117
145
 
118
146
  """
119
147
  it_self = reversed(self)
@@ -131,9 +159,13 @@ class FTuple[D](tuple[D, ...]):
131
159
  return acc
132
160
 
133
161
  def copy(self) -> 'FTuple[D]':
134
- """Return a shallow copy of ``FTuple`` in O(1) time & space complexity.
162
+ """
163
+ .. admonition:: copy
135
164
 
136
- :returns: New ``FTuple``.
165
+ Return a shallow copy of ``FTuple`` in O(1)
166
+ time & space complexity.
167
+
168
+ :returns: A new ``FTuple`` instance.
137
169
 
138
170
  """
139
171
  return self.__class__(self)
@@ -153,14 +185,12 @@ class FTuple[D](tuple[D, ...]):
153
185
  def accummulate[L](
154
186
  self, f: Callable[[L, D], L], s: L | None = None, /
155
187
  ) -> 'FTuple[L]':
156
- """Accumulate partial folds.
157
-
158
- Accumulate partial fold with an optional starting value,
159
- results in an ``FTuple``.
188
+ """
189
+ .. admonition:: accumulate partial folds
160
190
 
161
- :param f: Folding function used to produce partial folds.
162
- :param s: Optional starting value.
163
- :returns: New ``FTuple`` of the partial folds.
191
+ :param f: Folding function used to produce partial folds.
192
+ :param s: Optional starting value.
193
+ :returns: New ``FTuple`` of the partial folds.
164
194
 
165
195
  """
166
196
  if s is None:
@@ -168,6 +198,13 @@ class FTuple[D](tuple[D, ...]):
168
198
  return FTuple(accumulate(self, f, s))
169
199
 
170
200
  def map[U](self, f: Callable[[D], U], /) -> 'FTuple[U]':
201
+ """
202
+ .. admonition:: map
203
+
204
+ :param f: Folding function used to produce partial folds.
205
+ :returns: A new ``FTuple`` instance.
206
+
207
+ """
171
208
  return FTuple(map(f, self))
172
209
 
173
210
  def bind[U](
@@ -176,13 +213,17 @@ class FTuple[D](tuple[D, ...]):
176
213
  merge_type: MergeEnum = MergeEnum.Concat,
177
214
  yield_partials: bool = False,
178
215
  ) -> 'FTuple[U] | Never':
179
- """Bind function ``f`` to the ``FTuple``.
216
+ """
217
+ .. admonition:: bind
218
+
219
+ Flat map function ``f`` with the ``FTuple`` with different
220
+ merging strategies.
180
221
 
181
- :param f: Function ``D -> FTuple[U]``
182
- :param merge_type: ``MergeEnum`` to determine how to merge the result.
183
- :param yield_partials: Yield unmatched values if ``MergeEnum`` given as merge type.
184
- :return: Resulting ``FTuple``.
185
- :raises ValueError: If given an unknown merge enumeration.
222
+ :param f: Function ``D -> FTuple[U]``
223
+ :param merge_type: ``MergeEnum`` to determine how to merge the result.
224
+ :param yield_partials: Yield unmatched values if ``MergeEnum`` given as merge type.
225
+ :returns: A new ``FTuple`` instance.
226
+ :raises ValueError: If given an unknown merge enumeration.
186
227
 
187
228
  """
188
229
  return FTuple(
@@ -12,8 +12,6 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- """Pythonic FP - Immutable guaranteed hashable lists."""
16
-
17
15
  from collections.abc import Callable, Iterable, Iterator, Hashable
18
16
  from typing import cast, Never, overload
19
17
  from pythonic_fp.iterables.folding import accumulate
@@ -24,7 +22,7 @@ __all__ = ['IList']
24
22
 
25
23
  class IList[D](Hashable):
26
24
  """
27
- .. admonition:: Immutable List like data structure.
25
+ .. admonition:: immutable list like data structure
28
26
 
29
27
  - hashability should be enforced by LSP tooling
30
28
  - hashability will be enforced at runtime
@@ -39,16 +37,18 @@ class IList[D](Hashable):
39
37
  __slots__ = ('_ds', '_len', '_hash')
40
38
  __match_args__ = ('_ds', '_len')
41
39
 
42
- def __init__(self, *dss: Iterable[D]) -> None:
40
+ def __init__(self, *ds: Iterable[D]) -> None:
43
41
  """
44
- :param dss: 0 or 1 iterables
42
+ .. admonition:: init
43
+
44
+ :param ds: One optional iterable.
45
45
 
46
46
  """
47
- if (size := len(dss)) > 1:
47
+ if (size := len(ds)) > 1:
48
48
  msg = f'IList expects at most 1 iterable argument, got {size}'
49
49
  raise ValueError(msg)
50
50
  else:
51
- self._ds: tuple[D, ...] = tuple(dss[0]) if size == 1 else tuple()
51
+ self._ds: tuple[D, ...] = tuple(ds[0]) if size == 1 else tuple()
52
52
  self._len = len(self._ds)
53
53
  try:
54
54
  self._hash = hash((self._len, 42) + self._ds)
@@ -57,26 +57,55 @@ class IList[D](Hashable):
57
57
  raise TypeError(msg)
58
58
 
59
59
  def __hash__(self) -> int:
60
+ """
61
+ .. admonition:: hash
62
+
63
+ Hashability enforced at runtime by initializer.
64
+
65
+ :returns: Hash value.
66
+
67
+ """
60
68
  return self._hash
61
69
 
62
70
  def __iter__(self) -> Iterator[D]:
71
+ """
72
+ .. admonition:: iterate
73
+
74
+ Forward iterate, left to right.
75
+
76
+ :yields: The contained values.
77
+
78
+ """
63
79
  return iter(self._ds)
64
80
 
65
81
  def __reversed__(self) -> Iterator[D]:
82
+ """
83
+ .. admonition:: iterate
84
+
85
+ Reverse iterate, left to right.
86
+
87
+ :yields: The contained values in reverse order.
88
+
89
+ """
66
90
  return reversed(self._ds)
67
91
 
68
92
  def __bool__(self) -> bool:
93
+ """
94
+ .. admonition:: bool
95
+
96
+ :returns: ``True`` if not empty, ``False`` if empty.
97
+
98
+ """
69
99
  return bool(self._ds)
70
100
 
71
101
  def __len__(self) -> int:
72
- return len(self._ds)
73
-
74
- def __repr__(self) -> str:
75
- return 'immutable_list(' + ', '.join(map(repr, self)) + ')'
102
+ """
103
+ .. admonition:: len
76
104
 
77
- def __str__(self) -> str:
78
- return '((' + ', '.join(map(repr, self)) + '))'
105
+ :returns: The number of items in the ``Ilist``.
79
106
 
107
+ """
108
+ return len(self._ds)
80
109
  def __eq__(self, other: object, /) -> bool:
81
110
  if not isinstance(other, IList):
82
111
  return NotImplemented # magic object
@@ -92,10 +121,37 @@ class IList[D](Hashable):
92
121
  def __getitem__(self, idx: slice, /) -> 'IList[D]': ...
93
122
 
94
123
  def __getitem__(self, idx: slice | int, /) -> 'IList[D] | D':
124
+ """
125
+ .. admonition:: getitem
126
+
127
+ Indexable and sliceable but otherwise immutable.
128
+
129
+ :param idx: Either a ``slice`` or and ``index``.
130
+ :returns: A slice or the value.
131
+
132
+ """
95
133
  if isinstance(idx, slice):
96
134
  return IList(self._ds[idx])
97
135
  return self._ds[idx]
98
136
 
137
+ def __repr__(self) -> str:
138
+ """
139
+ .. admonition:: repr string
140
+
141
+ :returns: A string to reproduce the value.
142
+
143
+ """
144
+ return 'immutable_list(' + ', '.join(map(repr, self)) + ')'
145
+
146
+ def __str__(self) -> str:
147
+ """
148
+ .. admonition:: user string
149
+
150
+ :returns: A string meaningful to an end user.
151
+
152
+ """
153
+ return '((' + ', '.join(map(repr, self)) + '))'
154
+
99
155
  def foldl[L](
100
156
  self,
101
157
  f: Callable[[L, D], L],
@@ -103,13 +159,16 @@ class IList[D](Hashable):
103
159
  start: L | None = None,
104
160
  default: L | None = None,
105
161
  ) -> L | None:
106
- """Fold Left.
162
+ """
163
+ .. admonition:: fold left
107
164
 
108
- :param f: Folding function, first argument is for the accumulated value.
109
- :param start: Optional starting value.
110
- :param default: Optional default value if fold does not exist.
111
- :returns: Folded value.
112
- :raises ValueError: When empty and a start value not given.
165
+ :param f: Folding function, first argument is for
166
+ the accumulated value.
167
+ :param start: Optional starting value.
168
+ :param default: Optional default value if fold does not exist.
169
+ :returns: Left folded value.
170
+ :raises ValueError: When empty and neither a start value or
171
+ default value not given.
113
172
 
114
173
  """
115
174
  it = iter(self._ds)
@@ -135,13 +194,16 @@ class IList[D](Hashable):
135
194
  start: R | None = None,
136
195
  default: R | None = None,
137
196
  ) -> R | None:
138
- """Fold Right.
197
+ """
198
+ .. admonition:: fold right
139
199
 
140
- :param f: Folding function, second argument is for the accumulated value.
141
- :param start: Optional starting value.
142
- :param default: Optional default value if fold does not exist.
143
- :returns: Folded value.
144
- :raises ValueError: When empty and a start value not given.
200
+ :param f: Folding function, second argument is for
201
+ the accumulated value.
202
+ :param start: Optional starting value.
203
+ :param default: Optional default value if fold does not exist.
204
+ :returns: Right folded value.
205
+ :raises ValueError: When empty and neither a start value or
206
+ default value not given.
145
207
 
146
208
  """
147
209
  it = reversed(self._ds)
@@ -161,6 +223,18 @@ class IList[D](Hashable):
161
223
  return acc
162
224
 
163
225
  def __add__(self, other: 'IList[D]', /) -> 'IList[D]':
226
+ """
227
+ .. admonition:: add by concatenation
228
+
229
+ :param other: The ``other`` same typed ``IList``.
230
+ :returns: New ``IList`` instance with concatenated values.
231
+ :raises ValueError: When ``other`` is not an ``IList``.
232
+
233
+ .. admonition:: TODO
234
+
235
+ Return ``NotImplemented`` if appropriate?
236
+
237
+ """
164
238
  if not isinstance(other, IList):
165
239
  msg = 'IList being added to something not an IList'
166
240
  raise ValueError(msg)
@@ -168,22 +242,45 @@ class IList[D](Hashable):
168
242
  return IList(concat(self, other))
169
243
 
170
244
  def __mul__(self, num: int, /) -> 'IList[D]':
245
+ """
246
+ .. admonition:: add by concatenation
247
+
248
+ :param other: The ``other`` same typed ``IList``.
249
+ :returns: New ``IList`` instance with concatenated values.
250
+ :raises ValueError: When ``other`` is not an ``IList``.
251
+
252
+ .. admonition:: TODO
253
+
254
+ Consider returning ``NotImplemented`` if appropriate?
255
+ Maybe define a "column vector" version?
256
+
257
+ """
171
258
  return IList(self._ds.__mul__(num if num > 0 else 0))
172
259
 
173
260
  def __rmul__(self, num: int, /) -> 'IList[D]':
261
+ """
262
+ .. admonition:: concatenation with itself
263
+
264
+ :param num: Number of times to concatenate ``IList``
265
+ with itself.
266
+ :returns: A new ``IList`` instance with concatenated values.
267
+ :raises ValueError: When ``other`` is not an ``IList``.
268
+
269
+ """
174
270
  return IList(self._ds.__mul__(num if num > 0 else 0))
175
271
 
176
272
  def accummulate[L](
177
273
  self, f: Callable[[L, D], L], s: L | None = None, /
178
274
  ) -> 'IList[L]':
179
- """Accumulate partial folds.
275
+ """
276
+ .. admonition:: accumulate partial folds
180
277
 
181
- Accumulate partial fold with an optional starting value,
182
- results in an ``IList``.
278
+ Accumulate partial fold with an optional starting value,
279
+ results in an ``IList``.
183
280
 
184
- :param f: Folding function used to produce partial folds.
185
- :param s: Optional starting value.
186
- :returns: New ``FTuple`` of the partial folds.
281
+ :param f: Folding function used to produce partial folds.
282
+ :param s: Optional starting value.
283
+ :returns: New ``IList`` of the partial folds.
187
284
 
188
285
  """
189
286
  if s is None:
@@ -191,6 +288,13 @@ class IList[D](Hashable):
191
288
  return IList(accumulate(self, f, s))
192
289
 
193
290
  def map[U](self, f: Callable[[D], U], /) -> 'IList[U]':
291
+ """
292
+ .. admonition:: map
293
+
294
+ :parameter f: Function to map over the ``IList``.
295
+ :returns: A new ``IList`` instance with the mapped values.
296
+
297
+ """
194
298
  return IList(map(f, self))
195
299
 
196
300
  def bind[U](
@@ -199,13 +303,17 @@ class IList[D](Hashable):
199
303
  merge_enum: MergeEnum = MergeEnum.Concat,
200
304
  yield_partials: bool = False,
201
305
  ) -> 'IList[U] | Never':
202
- """Bind function ``f`` to the ``IList``.
203
-
204
- :param f: Function ``D -> IList[U]``
205
- :param merge_type: ``MergeEnum`` to determine how to merge the result.
206
- :param yield_partials: Yield unmatched values if ``MergeEnum`` given as merge type.
207
- :return: Resulting ``IList``.
208
- :raises ValueError: If given an unknown merge enumeration.
306
+ """
307
+ .. admonition:: bind
308
+
309
+ :param f: Function ``D -> IList[U]`` to bind with different
310
+ merge strategies.
311
+ :param merge_type: ``MergeEnum`` to determine how to merge
312
+ the result.
313
+ :param yield_partials: Yield unmatched values if ``MergeEnum``
314
+ given as merge type.
315
+ :return: New ``IList`` instance.
316
+ :raises ValueError: If given an unknown merge enumeration.
209
317
 
210
318
  """
211
319
  return IList(
@@ -7,7 +7,7 @@ __all__ = ['IList']
7
7
 
8
8
  class IList[D](Hashable):
9
9
  __match_args__: Incomplete
10
- def __init__(self, *dss: Iterable[D]) -> None: ...
10
+ def __init__(self, *ds: Iterable[D]) -> None: ...
11
11
  def __hash__(self) -> int: ...
12
12
  def __iter__(self) -> Iterator[D]: ...
13
13
  def __reversed__(self) -> Iterator[D]: ...