codeupipe 0.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 (108) hide show
  1. codeupipe-0.1.0/LICENSE +190 -0
  2. codeupipe-0.1.0/PKG-INFO +216 -0
  3. codeupipe-0.1.0/README.md +190 -0
  4. codeupipe-0.1.0/codeupipe/__init__.py +39 -0
  5. codeupipe-0.1.0/codeupipe/cli.py +1502 -0
  6. codeupipe-0.1.0/codeupipe/converter/__init__.py +9 -0
  7. codeupipe-0.1.0/codeupipe/converter/config.py +119 -0
  8. codeupipe-0.1.0/codeupipe/converter/filters/__init__.py +21 -0
  9. codeupipe-0.1.0/codeupipe/converter/filters/analyze.py +60 -0
  10. codeupipe-0.1.0/codeupipe/converter/filters/classify.py +52 -0
  11. codeupipe-0.1.0/codeupipe/converter/filters/classify_files.py +61 -0
  12. codeupipe-0.1.0/codeupipe/converter/filters/generate_export.py +187 -0
  13. codeupipe-0.1.0/codeupipe/converter/filters/generate_import.py +229 -0
  14. codeupipe-0.1.0/codeupipe/converter/filters/parse_config.py +26 -0
  15. codeupipe-0.1.0/codeupipe/converter/filters/scan_project.py +52 -0
  16. codeupipe-0.1.0/codeupipe/converter/pipelines/__init__.py +8 -0
  17. codeupipe-0.1.0/codeupipe/converter/pipelines/export_pipeline.py +40 -0
  18. codeupipe-0.1.0/codeupipe/converter/pipelines/import_pipeline.py +40 -0
  19. codeupipe-0.1.0/codeupipe/converter/taps/__init__.py +7 -0
  20. codeupipe-0.1.0/codeupipe/converter/taps/conversion_log.py +46 -0
  21. codeupipe-0.1.0/codeupipe/core/__init__.py +20 -0
  22. codeupipe-0.1.0/codeupipe/core/filter.py +27 -0
  23. codeupipe-0.1.0/codeupipe/core/hook.py +34 -0
  24. codeupipe-0.1.0/codeupipe/core/payload.py +94 -0
  25. codeupipe-0.1.0/codeupipe/core/pipeline.py +231 -0
  26. codeupipe-0.1.0/codeupipe/core/state.py +78 -0
  27. codeupipe-0.1.0/codeupipe/core/stream_filter.py +33 -0
  28. codeupipe-0.1.0/codeupipe/core/tap.py +27 -0
  29. codeupipe-0.1.0/codeupipe/core/valve.py +52 -0
  30. codeupipe-0.1.0/codeupipe/linter/__init__.py +57 -0
  31. codeupipe-0.1.0/codeupipe/linter/assemble_doc_report.py +97 -0
  32. codeupipe-0.1.0/codeupipe/linter/assemble_report.py +140 -0
  33. codeupipe-0.1.0/codeupipe/linter/check_bundle.py +47 -0
  34. codeupipe-0.1.0/codeupipe/linter/check_index.py +88 -0
  35. codeupipe-0.1.0/codeupipe/linter/check_naming.py +47 -0
  36. codeupipe-0.1.0/codeupipe/linter/check_protocols.py +73 -0
  37. codeupipe-0.1.0/codeupipe/linter/check_structure.py +41 -0
  38. codeupipe-0.1.0/codeupipe/linter/check_symbols.py +116 -0
  39. codeupipe-0.1.0/codeupipe/linter/check_tests.py +48 -0
  40. codeupipe-0.1.0/codeupipe/linter/coverage_pipeline.py +39 -0
  41. codeupipe-0.1.0/codeupipe/linter/detect_drift.py +44 -0
  42. codeupipe-0.1.0/codeupipe/linter/detect_orphans.py +106 -0
  43. codeupipe-0.1.0/codeupipe/linter/doc_check_pipeline.py +28 -0
  44. codeupipe-0.1.0/codeupipe/linter/git_history.py +130 -0
  45. codeupipe-0.1.0/codeupipe/linter/lint_pipeline.py +51 -0
  46. codeupipe-0.1.0/codeupipe/linter/map_coverage.py +84 -0
  47. codeupipe-0.1.0/codeupipe/linter/report_gaps.py +68 -0
  48. codeupipe-0.1.0/codeupipe/linter/report_pipeline.py +49 -0
  49. codeupipe-0.1.0/codeupipe/linter/resolve_refs.py +48 -0
  50. codeupipe-0.1.0/codeupipe/linter/scan_components.py +95 -0
  51. codeupipe-0.1.0/codeupipe/linter/scan_directory.py +123 -0
  52. codeupipe-0.1.0/codeupipe/linter/scan_docs.py +62 -0
  53. codeupipe-0.1.0/codeupipe/linter/scan_tests.py +104 -0
  54. codeupipe-0.1.0/codeupipe/py.typed +1 -0
  55. codeupipe-0.1.0/codeupipe/testing.py +344 -0
  56. codeupipe-0.1.0/codeupipe/utils/__init__.py +10 -0
  57. codeupipe-0.1.0/codeupipe/utils/error_handling.py +68 -0
  58. codeupipe-0.1.0/codeupipe.egg-info/PKG-INFO +216 -0
  59. codeupipe-0.1.0/codeupipe.egg-info/SOURCES.txt +106 -0
  60. codeupipe-0.1.0/codeupipe.egg-info/dependency_links.txt +1 -0
  61. codeupipe-0.1.0/codeupipe.egg-info/entry_points.txt +2 -0
  62. codeupipe-0.1.0/codeupipe.egg-info/top_level.txt +1 -0
  63. codeupipe-0.1.0/pyproject.toml +54 -0
  64. codeupipe-0.1.0/setup.cfg +4 -0
  65. codeupipe-0.1.0/tests/test_assemble_doc_report.py +137 -0
  66. codeupipe-0.1.0/tests/test_assemble_report.py +255 -0
  67. codeupipe-0.1.0/tests/test_check_bundle.py +77 -0
  68. codeupipe-0.1.0/tests/test_check_index.py +155 -0
  69. codeupipe-0.1.0/tests/test_check_naming.py +61 -0
  70. codeupipe-0.1.0/tests/test_check_protocols.py +123 -0
  71. codeupipe-0.1.0/tests/test_check_structure.py +74 -0
  72. codeupipe-0.1.0/tests/test_check_symbols.py +119 -0
  73. codeupipe-0.1.0/tests/test_check_tests.py +90 -0
  74. codeupipe-0.1.0/tests/test_cli.py +942 -0
  75. codeupipe-0.1.0/tests/test_cli_smoke.py +81 -0
  76. codeupipe-0.1.0/tests/test_core_edge_cases.py +581 -0
  77. codeupipe-0.1.0/tests/test_coverage_pipeline.py +123 -0
  78. codeupipe-0.1.0/tests/test_detect_drift.py +78 -0
  79. codeupipe-0.1.0/tests/test_detect_orphans.py +201 -0
  80. codeupipe-0.1.0/tests/test_doc_check_cli.py +110 -0
  81. codeupipe-0.1.0/tests/test_doc_check_pipeline.py +104 -0
  82. codeupipe-0.1.0/tests/test_docs_examples.py +609 -0
  83. codeupipe-0.1.0/tests/test_error_handling.py +310 -0
  84. codeupipe-0.1.0/tests/test_filter.py +254 -0
  85. codeupipe-0.1.0/tests/test_git_history.py +172 -0
  86. codeupipe-0.1.0/tests/test_hook.py +172 -0
  87. codeupipe-0.1.0/tests/test_lint_pipeline.py +127 -0
  88. codeupipe-0.1.0/tests/test_map_coverage.py +98 -0
  89. codeupipe-0.1.0/tests/test_mixed_stream_pipeline.py +268 -0
  90. codeupipe-0.1.0/tests/test_payload.py +178 -0
  91. codeupipe-0.1.0/tests/test_pipeline.py +352 -0
  92. codeupipe-0.1.0/tests/test_real_world_pipelines.py +1811 -0
  93. codeupipe-0.1.0/tests/test_report_gaps.py +94 -0
  94. codeupipe-0.1.0/tests/test_report_pipeline.py +111 -0
  95. codeupipe-0.1.0/tests/test_resolve_refs.py +63 -0
  96. codeupipe-0.1.0/tests/test_scan_components.py +121 -0
  97. codeupipe-0.1.0/tests/test_scan_directory.py +136 -0
  98. codeupipe-0.1.0/tests/test_scan_docs.py +103 -0
  99. codeupipe-0.1.0/tests/test_scan_tests.py +118 -0
  100. codeupipe-0.1.0/tests/test_state.py +93 -0
  101. codeupipe-0.1.0/tests/test_stream_filter_run_protection.py +175 -0
  102. codeupipe-0.1.0/tests/test_streaming.py +413 -0
  103. codeupipe-0.1.0/tests/test_sync_support.py +201 -0
  104. codeupipe-0.1.0/tests/test_tap.py +94 -0
  105. codeupipe-0.1.0/tests/test_testing.py +350 -0
  106. codeupipe-0.1.0/tests/test_typed.py +233 -0
  107. codeupipe-0.1.0/tests/test_unintended_usage.py +1286 -0
  108. codeupipe-0.1.0/tests/test_valve.py +96 -0
@@ -0,0 +1,190 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity granting the License,
13
+ including any individual or entity that controls, is controlled by, or is
14
+ under common control with such entity. For the purposes of this License,
15
+ "control" means (i) the power, direct or indirect, to cause the direction
16
+ or management of such entity, whether by contract or otherwise, or (ii)
17
+ ownership of fifty percent (50%) or more of the outstanding shares, or
18
+ (iii) beneficial ownership of such entity.
19
+
20
+ "You" (or "Your") shall mean an individual or legal entity exercising
21
+ permissions granted by this License.
22
+
23
+ "Source" form shall mean the preferred form for making modifications,
24
+ including but not limited to software source code, documentation source,
25
+ and configuration files.
26
+
27
+ "Object" form shall mean any form resulting from mechanical transformation
28
+ or translation of a Source form, including but not limited to compiled
29
+ object code, generated documentation, and conversions to other media types.
30
+
31
+ "Work" shall mean the work of authorship, whether in Source or Object form,
32
+ made available under the terms of this License, as indicated by a copyright
33
+ notice that is included in or attached to the work (which, for the purposes
34
+ of this License, shall not be modified except as required by this License).
35
+
36
+ "Derivative Works" shall mean any work, whether in Source or Object form,
37
+ that is based upon (or derived from) the Work and for which the editorial
38
+ revisions, annotations, elaborations, or other modifications represent, as
39
+ a whole, an original work of authorship. For the purposes of this License,
40
+ Derivative Works shall not include works that remain separable from, or
41
+ merely link (or bind by name) to the interfaces of, the Work and derivative
42
+ works thereof.
43
+
44
+ "Contribution" shall mean any work of authorship, including the original
45
+ version of the Work and any modifications or additions to that Work or
46
+ Derivative Works thereof, that is intentionally submitted to Licensor for
47
+ inclusion in the Work by the copyright owner or by an individual or legal
48
+ entity authorized to submit on behalf of the copyright owner. For the
49
+ purposes of this definition, "submitted" means any form of electronic,
50
+ verbal, or written communication sent to the Licensor or its
51
+ representatives, including but not limited to communication on electronic
52
+ mailing lists, source code control systems, and issue tracking systems that
53
+ are managed by, or on behalf of, the Licensor for the purpose of discussing
54
+ and improving the Work, but excluding communication that is conspicuously
55
+ marked or otherwise designated in writing by the copyright owner as "Not a
56
+ Contribution."
57
+
58
+ "Contributor" shall mean Licensor and any individual or legal entity on
59
+ behalf of whom a Contribution has been received by Licensor and subsequently
60
+ incorporated within the Work.
61
+
62
+ 2. Grant of Copyright License. Subject to the terms and conditions of this
63
+ License, each Contributor hereby grants to You a perpetual, worldwide,
64
+ non-exclusive, no-charge, royalty-free, irrevocable copyright license to
65
+ use, reproduce, modify, merge, publish, distribute, sublicense, and/or
66
+ sell copies of the Work, and to permit persons to whom the Work is
67
+ furnished to do so, subject to the following conditions:
68
+
69
+ The above copyright notice and this permission notice shall be included in
70
+ all copies or substantial portions of the Work.
71
+
72
+ 3. Grant of Patent License. Subject to the terms and conditions of this
73
+ License, each Contributor hereby grants to You a perpetual, worldwide,
74
+ non-exclusive, no-charge, royalty-free, irrevocable (except as stated in
75
+ this section) patent license to make, have made, use, offer to sell, sell,
76
+ import, and otherwise transfer the Work, where such license applies only to
77
+ those patent claims licensable by such Contributor that are necessarily
78
+ infringed by their Contribution(s) alone or by combination of their
79
+ Contribution(s) with the Work to which such Contribution(s) was submitted.
80
+ If You institute patent litigation against any entity (including a
81
+ cross-claim or counterclaim in a lawsuit) alleging that the Work or a
82
+ Contribution incorporated within the Work constitutes direct or
83
+ contributory patent infringement, then any patent licenses granted to You
84
+ under this License for that Work shall terminate as of the date such
85
+ litigation is filed.
86
+
87
+ 4. Redistribution. You may reproduce and distribute copies of the Work or
88
+ Derivative Works thereof in any medium, with or without modifications, and
89
+ in Source or Object form, provided that You meet the following conditions:
90
+
91
+ (a) You must give any other recipients of the Work or Derivative Works a
92
+ copy of this License; and
93
+
94
+ (b) You must cause any modified files to carry prominent notices stating
95
+ that You changed the files; and
96
+
97
+ (c) You must retain, in the Source form of any Derivative Works that You
98
+ distribute, all copyright, trademark, patent, attribution and other
99
+ notices from the Source form of the Work, excluding those notices that
100
+ do not pertain to any part of the Derivative Works; and
101
+
102
+ (d) If the Work includes a "NOTICE" file as part of its distribution, then
103
+ any Derivative Works that You distribute must include a readable copy
104
+ of the attribution notices contained within such NOTICE file, excluding
105
+ those notices that do not pertain to any part of the Derivative Works,
106
+ in at least one of the following places: within a NOTICE file
107
+ distributed as part of the Derivative Works; within the Source form or
108
+ documentation, if provided along with the Derivative Works; or, within
109
+ a display generated by the Derivative Works, if and wherever such
110
+ third-party notices normally appear. The contents of the NOTICE file
111
+ are for informational purposes only and do not modify the License. You
112
+ may add Your own attribution notices within Derivative Works that You
113
+ distribute, alongside or as an addendum to the NOTICE text from the
114
+ Work, provided that such additional attribution notices cannot be
115
+ construed as modifying the License.
116
+
117
+ You may add Your own copyright notice to Your modifications and may provide
118
+ additional or different license terms and conditions for use, reproduction,
119
+ or distribution of Your modifications, or for any such Derivative Works as a
120
+ whole, provided Your use, reproduction, and distribution of the Work
121
+ otherwise complies with the conditions stated in this License.
122
+
123
+ 5. Submission of Contributions. Unless You explicitly state otherwise, any
124
+ Contribution intentionally submitted for inclusion in the Work by You to
125
+ the Licensor shall be under the terms and conditions of this License,
126
+ without any additional terms or conditions. Notwithstanding the above,
127
+ nothing herein shall supersede or modify the terms of any separate license
128
+ agreement you may have executed with Licensor regarding such Contributions.
129
+
130
+ 6. Trademarks. This License does not grant permission to use the trade names,
131
+ trademarks, service marks, or product names of the Licensor, except as
132
+ required for reasonable and customary use in describing the origin of the
133
+ Work and reproducing the content of the NOTICE file.
134
+
135
+ 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in
136
+ writing, Licensor provides the Work (and each Contributor provides its
137
+ Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
138
+ KIND, either express or implied, including, without limitation, any
139
+ warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or
140
+ FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for
141
+ determining the appropriateness of using or redistributing the Work and
142
+ assume any risks associated with Your exercise of permissions under this
143
+ License.
144
+
145
+ 8. Limitation of Liability. In no event and under no legal theory, whether in
146
+ tort (including negligence), contract, or otherwise, unless required by
147
+ applicable law (such as deliberate and grossly negligent acts) or agreed
148
+ to in writing, shall any Contributor be liable to You for damages,
149
+ including any direct, indirect, special, incidental, or consequential
150
+ damages of any character arising as a result of this License or out of the
151
+ use or inability to use the Work (including but not limited to damages for
152
+ loss of goodwill, work stoppage, computer failure or malfunction, or any
153
+ and all other commercial damages or losses), even if such Contributor has
154
+ been advised of the possibility of such damages.
155
+
156
+ 9. Accepting Support, Warranty or Additional Liability. While redistributing
157
+ the Work or Derivative Works thereof, You may choose to offer, and charge
158
+ a fee for, acceptance of support, warranty, indemnity, or other liability
159
+ obligations and/or rights consistent with this License. However, in
160
+ accepting such obligations, You may act only on Your own behalf and on Your
161
+ sole responsibility, not on behalf of any other Contributor, and only if
162
+ You agree to indemnify, defend, and hold each Contributor harmless for any
163
+ liability incurred by, or claims asserted against, such Contributor by
164
+ reason of your accepting any such warranty or additional liability.
165
+
166
+ END OF TERMS AND CONDITIONS
167
+
168
+ APPENDIX: How to apply the Apache License to your work.
169
+
170
+ To apply the Apache License to your work, attach the following boilerplate
171
+ notice, with the fields enclosed by brackets "[]" replaced with your own
172
+ identifying information. (Don't include the brackets!) The text should be
173
+ enclosed in the appropriate comment syntax for the file format. We also
174
+ recommend that a file or class name and description of purpose be included
175
+ on the same "page" as the copyright notice for easier identification within
176
+ third-party archives.
177
+
178
+ Copyright 2025 Orchestrate LLC (Joshua @orchestrate.solutions)
179
+
180
+ Licensed under the Apache License, Version 2.0 (the "License");
181
+ you may not use this file except in compliance with the License.
182
+ You may obtain a copy of the License at
183
+
184
+ http://www.apache.org/licenses/LICENSE-2.0
185
+
186
+ Unless required by applicable law or agreed to in writing, software
187
+ distributed under the License is distributed on an "AS IS" BASIS,
188
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
+ See the License for the specific language governing permissions and
190
+ limitations under the License.
@@ -0,0 +1,216 @@
1
+ Metadata-Version: 2.4
2
+ Name: codeupipe
3
+ Version: 0.1.0
4
+ Summary: Python pipeline framework — composable Payload-Filter-Pipeline pattern
5
+ Author: Joshua Wink
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/codeuchain/codeupipe
8
+ Project-URL: Repository, https://github.com/codeuchain/codeupipe
9
+ Project-URL: Issues, https://github.com/codeuchain/codeupipe/issues
10
+ Keywords: pipeline,filter,payload,etl,workflow,streaming
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Dynamic: license-file
26
+
27
+ # codeupipe
28
+
29
+ <!-- cup:ref file=codeupipe/__init__.py hash=afd4ab5 -->
30
+
31
+ Python pipeline framework — composable **Payload → Filter → Pipeline** pattern with streaming support. Zero external dependencies.
32
+
33
+ Experimental successor to [codeuchain](https://github.com/codeuchain/codeuchain) (Python only).
34
+
35
+ <!-- /cup:ref -->
36
+
37
+ ## Core Concepts
38
+
39
+ <!-- cup:ref file=codeupipe/core/__init__.py hash=e3e2418 -->
40
+ | Concept | Role |
41
+ |---|---|
42
+ | **Payload** | Immutable data container flowing through the pipeline |
43
+ | **MutablePayload** | Mutable sibling for performance-critical bulk edits |
44
+ | **Filter** | Processing unit — takes a Payload in, returns a transformed Payload out (sync or async) |
45
+ | **StreamFilter** | Streaming processing unit — receives one chunk, yields 0, 1, or N output chunks |
46
+ | **Pipeline** | Orchestrator — `.run()` for batch, `.stream()` for streaming |
47
+ | **Valve** | Conditional flow control — gates a Filter with a predicate |
48
+ | **Tap** | Non-modifying observation point — inspect without changing (sync or async) |
49
+ | **State** | Pipeline execution metadata — tracks what ran, what was skipped, errors, chunk counts |
50
+ | **Hook** | Lifecycle hooks — before/after/on_error for pipeline execution (sync or async) |
51
+ | **RetryFilter** | Resilience wrapper — retries a Filter up to N times before giving up |
52
+ <!-- /cup:ref -->
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ pip install -e .
58
+ ```
59
+
60
+ ## Quick Start
61
+
62
+ ```python
63
+ import asyncio
64
+ from codeupipe import Payload, Pipeline
65
+
66
+ # Filters can be sync or async — both work
67
+ class CleanInput:
68
+ def call(self, payload):
69
+ return payload.insert("text", payload.get("text", "").strip())
70
+
71
+ class Validate:
72
+ def call(self, payload):
73
+ if not payload.get("text"):
74
+ raise ValueError("Empty input")
75
+ return payload
76
+
77
+ # Build and run
78
+ pipeline = Pipeline()
79
+ pipeline.add_filter(CleanInput(), name="clean")
80
+ pipeline.add_filter(Validate(), name="validate")
81
+
82
+ result = asyncio.run(pipeline.run(Payload({"text": " hello "})))
83
+ print(result.get("text")) # "hello"
84
+ ```
85
+
86
+ ## Valve (Conditional Flow)
87
+
88
+ ```python
89
+ from codeupipe import Valve
90
+
91
+ class DiscountFilter:
92
+ def call(self, payload):
93
+ price = payload.get("price", 0)
94
+ return payload.insert("price", price * 0.9)
95
+
96
+ # Only applies when predicate returns True
97
+ pipeline.add_filter(
98
+ Valve("discount", DiscountFilter(), lambda p: p.get("tier") == "premium"),
99
+ name="discount",
100
+ )
101
+ ```
102
+
103
+ ## Tap (Observation)
104
+
105
+ ```python
106
+ class AuditTap:
107
+ async def observe(self, payload):
108
+ print(f"Payload at this point: {payload.to_dict()}")
109
+
110
+ pipeline.add_tap(AuditTap(), name="audit")
111
+ ```
112
+
113
+ ## Streaming
114
+
115
+ Process an async stream of chunks through the same pipeline at constant memory.
116
+
117
+ ```python
118
+ from codeupipe import Payload, Pipeline
119
+
120
+ class UppercaseFilter:
121
+ def call(self, payload):
122
+ return payload.insert("name", payload.get("name", "").upper())
123
+
124
+ async def names():
125
+ for n in ["alice", "bob", "charlie"]:
126
+ yield Payload({"name": n})
127
+
128
+ async def main():
129
+ pipeline = Pipeline()
130
+ pipeline.add_filter(UppercaseFilter(), name="upper")
131
+
132
+ async for result in pipeline.stream(names()):
133
+ print(result.get("name")) # ALICE, BOB, CHARLIE
134
+
135
+ asyncio.run(main())
136
+ ```
137
+
138
+ Use `StreamFilter` to drop, fan-out, or batch:
139
+
140
+ ```python
141
+ from typing import AsyncIterator
142
+
143
+ class DropEmpty:
144
+ async def stream(self, chunk: Payload) -> AsyncIterator[Payload]:
145
+ if chunk.get("line", "").strip():
146
+ yield chunk
147
+
148
+ class SplitWords:
149
+ async def stream(self, chunk: Payload) -> AsyncIterator[Payload]:
150
+ for word in chunk.get("text", "").split():
151
+ yield Payload({"word": word})
152
+ ```
153
+
154
+ ## Execution State
155
+
156
+ ```python
157
+ result = await pipeline.run(payload)
158
+ print(pipeline.state.executed) # ['clean', 'validate']
159
+ print(pipeline.state.skipped) # ['admin_only']
160
+ print(pipeline.state.chunks_processed) # {'upper': 3} (streaming mode)
161
+ ```
162
+
163
+ ## Docs
164
+
165
+ | Document | Purpose |
166
+ |----------|---------|
167
+ | [INDEX.md](INDEX.md) | Project structure map (verified by `cup doc-check`) |
168
+ | [CONCEPTS.md](CONCEPTS.md) | Full API reference with runnable examples |
169
+ | [BEST_PRACTICES.md](BEST_PRACTICES.md) | Project structure, naming, testing strategy |
170
+ | [SKILL.md](SKILL.md) | Agent skill reference — types, patterns, conversion |
171
+
172
+ ## CLI (`cup`)
173
+
174
+ <!-- cup:ref file=codeupipe/cli.py symbols=main,scaffold,bundle,lint,coverage,report,doc_check hash=1e63d0e -->
175
+ The `cup` command-line tool scaffolds, lints, and analyzes CUP projects:
176
+
177
+ ```bash
178
+ cup new filter validate_email src/signup # Scaffold a filter + test
179
+ cup new pipeline signup src/signup --steps validate_email hash_password
180
+ cup list # Show available component types
181
+ cup bundle src/signup # Generate __init__.py re-exports
182
+ cup lint src/signup # Check CUP conventions (CUP000–CUP008)
183
+ cup coverage src/signup # Map component↔test coverage gaps
184
+ cup report src/signup # Health report with scores, orphans, staleness
185
+ cup doc-check . # Verify doc freshness (cup:ref markers)
186
+ ```
187
+ <!-- /cup:ref -->
188
+
189
+ ## Testing Utilities
190
+
191
+ <!-- cup:ref file=codeupipe/testing.py symbols=run_filter,assert_payload,mock_filter hash=c119f9c -->
192
+ `codeupipe.testing` provides zero-boilerplate test helpers:
193
+
194
+ ```python
195
+ from codeupipe.testing import run_filter, assert_payload, mock_filter
196
+
197
+ def test_my_filter():
198
+ result = run_filter(MyFilter(), {"input": "data"})
199
+ assert_payload(result, output="expected")
200
+
201
+ def test_with_mock():
202
+ f = mock_filter(status="ok")
203
+ result = run_filter(f, {"x": 1})
204
+ assert f.call_count == 1
205
+ ```
206
+ <!-- /cup:ref -->
207
+
208
+ ## Test
209
+
210
+ ```bash
211
+ pytest # 909 tests
212
+ ```
213
+
214
+ ## License
215
+
216
+ Apache 2.0
@@ -0,0 +1,190 @@
1
+ # codeupipe
2
+
3
+ <!-- cup:ref file=codeupipe/__init__.py hash=afd4ab5 -->
4
+
5
+ Python pipeline framework — composable **Payload → Filter → Pipeline** pattern with streaming support. Zero external dependencies.
6
+
7
+ Experimental successor to [codeuchain](https://github.com/codeuchain/codeuchain) (Python only).
8
+
9
+ <!-- /cup:ref -->
10
+
11
+ ## Core Concepts
12
+
13
+ <!-- cup:ref file=codeupipe/core/__init__.py hash=e3e2418 -->
14
+ | Concept | Role |
15
+ |---|---|
16
+ | **Payload** | Immutable data container flowing through the pipeline |
17
+ | **MutablePayload** | Mutable sibling for performance-critical bulk edits |
18
+ | **Filter** | Processing unit — takes a Payload in, returns a transformed Payload out (sync or async) |
19
+ | **StreamFilter** | Streaming processing unit — receives one chunk, yields 0, 1, or N output chunks |
20
+ | **Pipeline** | Orchestrator — `.run()` for batch, `.stream()` for streaming |
21
+ | **Valve** | Conditional flow control — gates a Filter with a predicate |
22
+ | **Tap** | Non-modifying observation point — inspect without changing (sync or async) |
23
+ | **State** | Pipeline execution metadata — tracks what ran, what was skipped, errors, chunk counts |
24
+ | **Hook** | Lifecycle hooks — before/after/on_error for pipeline execution (sync or async) |
25
+ | **RetryFilter** | Resilience wrapper — retries a Filter up to N times before giving up |
26
+ <!-- /cup:ref -->
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install -e .
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ```python
37
+ import asyncio
38
+ from codeupipe import Payload, Pipeline
39
+
40
+ # Filters can be sync or async — both work
41
+ class CleanInput:
42
+ def call(self, payload):
43
+ return payload.insert("text", payload.get("text", "").strip())
44
+
45
+ class Validate:
46
+ def call(self, payload):
47
+ if not payload.get("text"):
48
+ raise ValueError("Empty input")
49
+ return payload
50
+
51
+ # Build and run
52
+ pipeline = Pipeline()
53
+ pipeline.add_filter(CleanInput(), name="clean")
54
+ pipeline.add_filter(Validate(), name="validate")
55
+
56
+ result = asyncio.run(pipeline.run(Payload({"text": " hello "})))
57
+ print(result.get("text")) # "hello"
58
+ ```
59
+
60
+ ## Valve (Conditional Flow)
61
+
62
+ ```python
63
+ from codeupipe import Valve
64
+
65
+ class DiscountFilter:
66
+ def call(self, payload):
67
+ price = payload.get("price", 0)
68
+ return payload.insert("price", price * 0.9)
69
+
70
+ # Only applies when predicate returns True
71
+ pipeline.add_filter(
72
+ Valve("discount", DiscountFilter(), lambda p: p.get("tier") == "premium"),
73
+ name="discount",
74
+ )
75
+ ```
76
+
77
+ ## Tap (Observation)
78
+
79
+ ```python
80
+ class AuditTap:
81
+ async def observe(self, payload):
82
+ print(f"Payload at this point: {payload.to_dict()}")
83
+
84
+ pipeline.add_tap(AuditTap(), name="audit")
85
+ ```
86
+
87
+ ## Streaming
88
+
89
+ Process an async stream of chunks through the same pipeline at constant memory.
90
+
91
+ ```python
92
+ from codeupipe import Payload, Pipeline
93
+
94
+ class UppercaseFilter:
95
+ def call(self, payload):
96
+ return payload.insert("name", payload.get("name", "").upper())
97
+
98
+ async def names():
99
+ for n in ["alice", "bob", "charlie"]:
100
+ yield Payload({"name": n})
101
+
102
+ async def main():
103
+ pipeline = Pipeline()
104
+ pipeline.add_filter(UppercaseFilter(), name="upper")
105
+
106
+ async for result in pipeline.stream(names()):
107
+ print(result.get("name")) # ALICE, BOB, CHARLIE
108
+
109
+ asyncio.run(main())
110
+ ```
111
+
112
+ Use `StreamFilter` to drop, fan-out, or batch:
113
+
114
+ ```python
115
+ from typing import AsyncIterator
116
+
117
+ class DropEmpty:
118
+ async def stream(self, chunk: Payload) -> AsyncIterator[Payload]:
119
+ if chunk.get("line", "").strip():
120
+ yield chunk
121
+
122
+ class SplitWords:
123
+ async def stream(self, chunk: Payload) -> AsyncIterator[Payload]:
124
+ for word in chunk.get("text", "").split():
125
+ yield Payload({"word": word})
126
+ ```
127
+
128
+ ## Execution State
129
+
130
+ ```python
131
+ result = await pipeline.run(payload)
132
+ print(pipeline.state.executed) # ['clean', 'validate']
133
+ print(pipeline.state.skipped) # ['admin_only']
134
+ print(pipeline.state.chunks_processed) # {'upper': 3} (streaming mode)
135
+ ```
136
+
137
+ ## Docs
138
+
139
+ | Document | Purpose |
140
+ |----------|---------|
141
+ | [INDEX.md](INDEX.md) | Project structure map (verified by `cup doc-check`) |
142
+ | [CONCEPTS.md](CONCEPTS.md) | Full API reference with runnable examples |
143
+ | [BEST_PRACTICES.md](BEST_PRACTICES.md) | Project structure, naming, testing strategy |
144
+ | [SKILL.md](SKILL.md) | Agent skill reference — types, patterns, conversion |
145
+
146
+ ## CLI (`cup`)
147
+
148
+ <!-- cup:ref file=codeupipe/cli.py symbols=main,scaffold,bundle,lint,coverage,report,doc_check hash=1e63d0e -->
149
+ The `cup` command-line tool scaffolds, lints, and analyzes CUP projects:
150
+
151
+ ```bash
152
+ cup new filter validate_email src/signup # Scaffold a filter + test
153
+ cup new pipeline signup src/signup --steps validate_email hash_password
154
+ cup list # Show available component types
155
+ cup bundle src/signup # Generate __init__.py re-exports
156
+ cup lint src/signup # Check CUP conventions (CUP000–CUP008)
157
+ cup coverage src/signup # Map component↔test coverage gaps
158
+ cup report src/signup # Health report with scores, orphans, staleness
159
+ cup doc-check . # Verify doc freshness (cup:ref markers)
160
+ ```
161
+ <!-- /cup:ref -->
162
+
163
+ ## Testing Utilities
164
+
165
+ <!-- cup:ref file=codeupipe/testing.py symbols=run_filter,assert_payload,mock_filter hash=c119f9c -->
166
+ `codeupipe.testing` provides zero-boilerplate test helpers:
167
+
168
+ ```python
169
+ from codeupipe.testing import run_filter, assert_payload, mock_filter
170
+
171
+ def test_my_filter():
172
+ result = run_filter(MyFilter(), {"input": "data"})
173
+ assert_payload(result, output="expected")
174
+
175
+ def test_with_mock():
176
+ f = mock_filter(status="ok")
177
+ result = run_filter(f, {"x": 1})
178
+ assert f.call_count == 1
179
+ ```
180
+ <!-- /cup:ref -->
181
+
182
+ ## Test
183
+
184
+ ```bash
185
+ pytest # 909 tests
186
+ ```
187
+
188
+ ## License
189
+
190
+ Apache 2.0
@@ -0,0 +1,39 @@
1
+ """
2
+ codeupipe: Pipeline framework for Python
3
+
4
+ Composable Payload-Filter-Pipeline pattern with Valves, Taps, Hooks, and Streaming.
5
+ Filters and Taps can be sync or async. Zero external dependencies.
6
+
7
+ Core concepts:
8
+ - Payload: Immutable data container flowing through the pipeline
9
+ - MutablePayload: Mutable sibling for performance-critical bulk edits
10
+ - Filter: Processing unit that transforms payloads (sync or async)
11
+ - StreamFilter: Streaming unit — yields 0, 1, or N output chunks per input
12
+ - Pipeline: Orchestrator — .run() for batch, .stream() for streaming
13
+ - Valve: Conditional flow control — gates a filter with a predicate
14
+ - Tap: Non-modifying observation point (sync or async)
15
+ - State: Pipeline execution metadata — tracks executed, skipped, errors, chunk counts
16
+ - Hook: Lifecycle hooks — before / after / on_error (sync or async)
17
+ """
18
+
19
+ from .core import Payload, MutablePayload, Filter, StreamFilter, Pipeline, Valve, Tap, State, Hook
20
+ from .utils import ErrorHandlingMixin, RetryFilter
21
+ from .converter import load_config, DEFAULT_CONFIG, PATTERN_DEFAULTS
22
+ from .converter.pipelines import build_export_pipeline, build_import_pipeline
23
+
24
+ from importlib.metadata import version as _pkg_version, PackageNotFoundError
25
+ try:
26
+ __version__ = _pkg_version("codeupipe")
27
+ except PackageNotFoundError:
28
+ __version__ = "0.1.0" # fallback for editable / non-installed usage
29
+ __all__ = [
30
+ # Core
31
+ "Payload", "MutablePayload",
32
+ "Filter", "StreamFilter", "Pipeline", "Valve", "Tap",
33
+ "State", "Hook",
34
+ # Utils
35
+ "ErrorHandlingMixin", "RetryFilter",
36
+ # Converter
37
+ "load_config", "DEFAULT_CONFIG", "PATTERN_DEFAULTS",
38
+ "build_export_pipeline", "build_import_pipeline",
39
+ ]