You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: resolve issues #10, #15, #18 and improve test coverage
This commit resolves multiple issues with enhanced functionality, better error
handling, and comprehensive test coverage.
## Issues Resolved
### #10: BufferReader.__next__() compatibility
- Modified __next__() to raise StopIteration when stream stopped and no data
- Updated __iter__() to catch StopIteration (prevents RuntimeError in Python 3.7+)
- Now fully compatible with Python's iterator protocol
- Works with builtin next(reader, default) and for loops
### #15: Custom exception classes
- Created stream2py/exceptions.py with exception hierarchy
- Implemented Stream2PyError base class and specific exceptions
- Updated StreamBuffer to use StreamNotStartedError with helpful messages
- Exported exceptions module from main package
### #18: Reader started checks
- Documented that checks already exist in StreamBuffer.mk_reader()
- Enhanced error messages with custom exceptions
- Added comprehensive tests demonstrating correct usage patterns
## Tests Added
- test_buffer_reader_stopiteration.py (4 tests for #10)
- Test StopIteration raised when stream stopped
- Test None returned when no data but running
- Test builtin next() with default values
- Test for loops terminate correctly
- test_reader_without_context.py (5 tests for #18)
- Test mk_reader() requires started buffer
- Test readers work without context manager
- Test context manager ensures cleanup
- Test recommended usage patterns
## Documentation
- RESOLVED_ISSUES.md - comprehensive summary of resolved issues
- Updated ISSUE_COMMENTS.md with resolution details for #9, #10, #13, #15, #18
- Clear documentation of when to use context managers
## Test Results
- All 28 tests passing
- +9 new tests in this commit
- 100% passing rate maintained
## Related Issues
- #9: Investigated CI - appears correct, needs clarification
- #13: Applies to external keyboardstream2py package
- #17, #14: Analyzed, documented for future work
@@ -109,59 +109,63 @@ This would be a simple enhancement on top of the existing blocking implementatio
109
109
110
110
## Issue #10: Make BufferReader.__next__() more compatible with builtin next()
111
111
112
-
📋**NEEDS WORK**
112
+
✅**RESOLVED** in this PR
113
113
114
-
**Status:**Relevant enhancement
114
+
**Status:**Implemented
115
115
116
-
**Issue:** The Python `next()` function should either raise `StopIteration` or yield a given default value instead of always returning None while the next value is unavailable.
117
-
118
-
**Current behavior:**
119
-
`BufferReader.__next__()` returns `None` while next value is unavailable, which doesn't follow the standard Python iterator protocol.
120
-
121
-
**Expected behavior:**
122
-
Should raise `StopIteration` when iterator is exhausted, or return a default value if provided to `next()`.
123
-
124
-
**Recommendation:**
125
-
Review the current implementation in `buffer_reader.py:153-155` and adjust to match Python's iterator protocol. However, note that stream2py's use case is different from typical finite iterators - streams are potentially infinite and have "no data yet" as a valid state distinct from "exhausted".
116
+
**Implementation:**
117
+
Modified `BufferReader.__next__()` to properly raise `StopIteration` when the stream is stopped and no data is available, while still returning `None` when temporarily no data is available but the stream is still running.
126
118
127
-
**Considerations:**
128
-
- Stream iterators are unbounded, so `StopIteration` should only be raised when the stream is actually stopped/closed
129
-
- May need to distinguish between "no data yet" vs "stream ended"
130
-
- The `is_stopped` property could be used to determine when to raise `StopIteration`
119
+
**Changes Made:**
120
+
-`buffer_reader.py:153-169` - Updated `__next__()` to raise `StopIteration` when `result is None and self.is_stopped`
121
+
-`buffer_reader.py:143-156` - Updated `__iter__()` to catch `StopIteration` (prevents RuntimeError in Python 3.7+)
122
+
- Added comprehensive tests in `test_buffer_reader_stopiteration.py`:
123
+
- Test that `StopIteration` is raised when stream stopped
124
+
- Test that `None` is returned when no data but stream still running
125
+
- Test that builtin `next()` with default value works correctly
126
+
- Test that for loops stop correctly
131
127
132
-
**Effort:** Simple, but needs careful design consideration
128
+
**Benefits:**
129
+
- Now fully compatible with Python's iterator protocol
130
+
- Works correctly with builtin `next(reader, default)`
131
+
- For loops terminate properly when stream stops
132
+
- Distinguishes between "no data yet" (returns None) vs "stream exhausted" (raises StopIteration)
## Issue #18: What should happen if we start to read without starting the reader?
139
139
140
-
📋**NEEDS DECISION**
140
+
✅**ADDRESSED** in this PR
141
141
142
-
**Status:**Design question
142
+
**Status:**Checks already exist, now documented and tested
143
143
144
-
**Question:** Should `reader.read()` raise an exception if called before the reader context is entered or the buffer is started?
144
+
**Resolution:**
145
+
The necessary checks already exist! `StreamBuffer.mk_reader()` raises a clear error if called before the buffer is started. Additionally, the BufferReader doesn't require being in a `with` block to function - the context manager is only for cleanup.
145
146
146
-
**Current behavior:**
147
-
Unclear - may fail with confusing errors depending on internal state.
147
+
**Current Implementation:**
148
+
-`StreamBuffer.mk_reader()` raises `StreamNotStartedError` (formerly `RuntimeError`) if buffer not started
"BufferReader must be used within a started StreamBuffer context. "
156
-
"Call StreamBuffer.start() or use 'with stream_buffer:' before reading."
157
-
)
158
-
```
159
-
160
-
**Effort:** Simple (add validation check)
161
+
**Documented Behavior:**
162
+
1.**StreamBuffer must be started** before creating readers - this is enforced
163
+
2.**BufferReader context manager is optional** - used for cleanup, not required for reading
164
+
3.**Recommended pattern**: Use `with StreamBuffer(...) as buffer:` for automatic cleanup
161
165
162
166
**Related:**
163
167
- See wiki: https://github.com/i2mint/stream2py/wiki/Forwarding-context-management
164
-
-This was partially addressed by `contextualize_with_instance` utility
168
+
-Enhanced by `contextualize_with_instance` utility
165
169
166
170
---
167
171
@@ -236,104 +240,114 @@ Then `BufferReader.range()` could use these methods to return precisely trimmed
236
240
237
241
## Issue #15: Review exception objects raised and figure out custom ones
238
242
239
-
📋**NEEDS WORK** (Medium effort)
243
+
✅**RESOLVED** (Foundation implemented in this PR)
240
244
241
-
**Status:**Relevant - code quality enhancement
245
+
**Status:**Core exception hierarchy created and integrated
242
246
243
-
**Description:**
244
-
Currently, the codebase raises generic exceptions (`ValueError`, `RuntimeError`, `TypeError`, etc.). Custom exception classes would provide better error handling and clearer semantics.
247
+
**Implementation:**
248
+
Created comprehensive exception hierarchy in `stream2py/exceptions.py` and integrated it into the codebase.
245
249
246
-
**Recommendation:**
247
-
1. Audit all exception raising in the codebase
248
-
2. Create custom exception hierarchy, e.g.:
249
-
```python
250
-
classStream2PyError(Exception):
251
-
"""Base exception for stream2py"""
250
+
**Exception Classes Created:**
251
+
```python
252
+
classStream2PyError(Exception):
253
+
"""Base exception for all stream2py errors"""
252
254
253
-
classStreamNotStartedError(Stream2PyError):
254
-
"""Raised when operations require a started stream"""
255
+
classStreamNotStartedError(Stream2PyError):
256
+
"""Raised when operations require a started stream"""
255
257
256
-
classBufferOverflowError(Stream2PyError):
257
-
"""Raised when buffer is full and auto_drop=False"""
258
+
classStreamAlreadyStoppedError(Stream2PyError):
259
+
"""Raised when stream has been stopped"""
258
260
259
-
classNoDataAvailableError(Stream2PyError):
260
-
"""Raised when no data is available and ignore_no_item_found=False"""
261
-
```
261
+
classBufferError(Stream2PyError):
262
+
"""Base exception for buffer-related errors"""
262
263
263
-
3. Replace generic exceptions with custom ones where appropriate
264
-
4. Update documentation
264
+
classBufferOverflowError(BufferError):
265
+
"""Raised when buffer is full and auto_drop=False"""
265
266
266
-
**Benefits:**
267
-
- Easier to catch specific stream2py errors
268
-
- Better error messages
269
-
- Clearer API semantics
267
+
classNoDataAvailableError(BufferError):
268
+
"""Raised when no data is available"""
269
+
270
+
classInvalidDataError(Stream2PyError):
271
+
"""Raised when data doesn't meet expected format"""
272
+
273
+
classConfigurationError(Stream2PyError):
274
+
"""Raised when objects are misconfigured"""
275
+
```
270
276
271
-
**Effort:** Medium (requires codebase audit)
277
+
**Integration Completed:**
278
+
- Updated `StreamBuffer.mk_reader()` to raise `StreamNotStartedError` with helpful message
279
+
- Updated `StreamBuffer.attach_reader()` to raise `StreamNotStartedError`
280
+
- Updated tests to expect custom exceptions
281
+
- Exported exceptions module from `stream2py.__init__`
282
+
283
+
**Benefits Realized:**
284
+
- ✅ More informative error messages (e.g., suggests using `with StreamBuffer(...)`)
285
+
- ✅ Easier to catch specific stream2py errors
286
+
- ✅ Clearer API semantics
287
+
- ✅ Better IDE autocomplete support
288
+
289
+
**Future Work:**
290
+
Continue replacing generic exceptions throughout codebase with appropriate custom exceptions. The foundation is now in place.
272
291
273
292
---
274
293
275
294
## Issue #13: keyboard_and_audio not working in notebook
276
295
277
-
📋 **NEEDS WORK** (Medium effort)
278
-
279
-
**Status:** Relevant bug
296
+
🔧 **EXTERNAL PACKAGE** - Applies to keyboardstream2py
280
297
281
-
**Issue:**Terminal-based keyboard input using `termios` doesn't work in Jupyter notebooks.
298
+
**Status:**Not applicable to core stream2py
282
299
283
-
**Error:**
284
-
```
285
-
termios.error: (25, 'Inappropriate ioctl for device')
286
-
```
300
+
**Investigation:**
301
+
The `getch.py` file and keyboard functionality referenced in this issue **do not exist in core stream2py**. This functionality has been moved to the separate `keyboardstream2py` package as part of stream2py's plugin architecture.
287
302
288
-
**Root cause:**
289
-
The `getch.py` utility tries to use terminal control (`termios.tcgetattr`) which doesn't work in notebook environments where there's no proper terminal.
303
+
**Resolution:**
304
+
This issue applies to the **[keyboardstream2py](https://github.com/i2mint/keyboardstream2py)** package, not core stream2py.
290
305
291
306
**Recommendation:**
292
-
1. Add conditional imports and environment detection
293
-
2. Provide alternative input methods for notebook environments:
294
-
- Use `ipywidgets` for notebook input
295
-
- Fall back to `input()` for basic keyboard reading
296
-
- Document the limitation clearly
297
-
298
-
**Example approach:**
299
-
```python
300
-
def_get_input_method():
301
-
try:
302
-
# Try to detect if we're in a notebook
303
-
get_ipython()
304
-
# Use ipywidgets
305
-
from ipywidgets import Button, Output
306
-
return NotebookInputReader()
307
-
exceptNameError:
308
-
# Not in notebook, use terminal
309
-
from stream2py.utility.getch import getch
310
-
return TerminalInputReader()
311
-
```
307
+
1.**Move this issue** to the keyboardstream2py repository, OR
308
+
2.**Close this issue** with a comment directing users to report keyboard-specific issues in the appropriate repository
312
309
313
-
**Effort:** Medium
310
+
**For keyboardstream2py maintainers:**
311
+
If this issue is moved to keyboardstream2py, the solution would involve:
312
+
1. Detecting notebook environments
313
+
2. Providing alternative input methods (e.g., `ipywidgets`)
314
+
3. Documenting limitations clearly
314
315
315
-
**Alternative:**Document that keyboard input examples only work in terminal, not notebooks.
316
+
**Note:**This follows stream2py's design philosophy of keeping core dependency-free and moving specific functionality to plugin packages.
316
317
317
318
---
318
319
319
320
## Issue #9: Not skipping tests and linting
320
321
321
-
❓ **NEEDS CLARIFICATION**
322
+
❓ **NEEDS CLARIFICATION** - CI appears correct
323
+
324
+
**Status:** Investigated - current setup looks appropriate
325
+
326
+
**Investigation Results:**
327
+
Reviewed `.github/workflows/ci.yml` thoroughly:
322
328
323
-
**Status:** Unclear what the specific issue is
329
+
**Tests ARE running:**
330
+
- Line 43: `pytest -s --doctest-modules -v $PROJECT_NAME` - runs all tests including doctests
331
+
- Executes in the "validation" job on every push/PR
324
332
325
-
**Current state:**
326
-
- CI configuration in `.github/workflows/ci.yml` runs tests on line 43: `pytest -s --doctest-modules -v $PROJECT_NAME`
327
-
- Linting is run on line 40: `pylint ./$PROJECT_NAME --ignore=tests,examples,scrap --disable=all --enable=C0114`
333
+
**Linting IS running:**
334
+
- Line 40: `pylint ./$PROJECT_NAME --ignore=tests,examples,scrap --disable=all --enable=C0114`
335
+
- Checks for missing module docstrings
336
+
- Executes in the "validation" job
328
337
329
-
**Question:** What specifically should not be skipped?
338
+
**The `--bypass-tests` flag (line 96):**
339
+
- Only used in the "publish" job's `pack check-in` command
340
+
- This is APPROPRIATE because:
341
+
1. Tests already ran successfully in the validation job (line 47: `needs: validation`)
342
+
2. This step only commits automated formatting/documentation changes
343
+
3. No code logic changes happen in this step
344
+
4. Bypassing here prevents redundant test runs
330
345
331
-
**Possible interpretations:**
332
-
1. CI is currently skipping tests/linting when it shouldn't?
333
-
2. Tests are being skipped within the test suite?
334
-
3. Certain files/directories should not be ignored by linting?
346
+
**Conclusion:**
347
+
The CI configuration appears correct. Tests and linting run on every push/PR. The bypass flags are only used appropriately for automated commits.
335
348
336
-
**Recommendation:** Need clarification from issue author on what the problem is. The CI workflow appears to run both tests and linting.
349
+
**Question for Issue Author:**
350
+
What specifically is being inappropriately skipped? The current setup follows CI best practices. If there's a specific concern, please provide details so we can address it.
0 commit comments