refactor: rename sessions submodule to _sessions to avoid shadowing sessions()

wabot/__init__.py defines def sessions(...) and also had a sessions.py
submodule, so wabot.sessions (attribute) resolved to the function,
shadowing the module for import wabot.sessions as m style access. This
is the same class of collision that browser.py -> _browser.py already
fixed. Renaming to _sessions.py removes the ambiguity and lets
docs/api.rst drop the :no-index: Sphinx workaround in favor of plain
autoclass directives on the public SessionRecord/SessionStore
re-exports.
This commit is contained in:
Mathew Sir Guest the best 2026-07-12 15:37:56 -06:00
parent 68978a34e7
commit 154344e94a
8 changed files with 26 additions and 21 deletions

@ -16,9 +16,11 @@ API reference
.. automodule:: wabot.pacing .. automodule:: wabot.pacing
:members: :members:
.. automodule:: wabot.sessions .. autoclass:: wabot.SessionRecord
:members:
.. autoclass:: wabot.SessionStore
:members: :members:
:no-index:
.. automodule:: wabot.hosts .. automodule:: wabot.hosts
:members: ExternalServer, ManagedService, attach, service_alive :members: ExternalServer, ManagedService, attach, service_alive

@ -44,7 +44,7 @@ wabot/ (repo root, branch feature/modernization
├── src/wabot/ ├── src/wabot/
│ ├── __init__.py # public API: browser(), sessions(), destroy(), re-exports │ ├── __init__.py # public API: browser(), sessions(), destroy(), re-exports
│ ├── pacing.py # NoPacing / HumanPacing │ ├── pacing.py # NoPacing / HumanPacing
│ ├── sessions.py # SessionRecord, SessionStore │ ├── _sessions.py # SessionRecord, SessionStore (underscored to avoid shadowing sessions())
│ ├── hosts.py # build_options, service_alive, ExternalServer, │ ├── hosts.py # build_options, service_alive, ExternalServer,
│ │ # ManagedService, find_driver_binary, stop_service, │ │ # ManagedService, find_driver_binary, stop_service,
│ │ # ReattachingRemote, attach │ │ # ReattachingRemote, attach
@ -291,10 +291,10 @@ git commit -m "feat: pacing policies (HumanPacing default behavior, NoPacing for
``` ```
--- ---
### Task 3: `sessions.py` — SessionRecord + SessionStore ### Task 3: `_sessions.py` — SessionRecord + SessionStore
**Files:** **Files:**
- Create: `src/wabot/sessions.py` - Create: `src/wabot/_sessions.py`
- Test: `tests/unit/test_sessions.py` - Test: `tests/unit/test_sessions.py`
- [ ] **Step 1: Write the failing tests** - [ ] **Step 1: Write the failing tests**
@ -304,7 +304,7 @@ git commit -m "feat: pacing policies (HumanPacing default behavior, NoPacing for
```python ```python
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from wabot.sessions import SessionRecord, SessionStore from wabot._sessions import SessionRecord, SessionStore
def make_record(name="scraper1", created_at=None): def make_record(name="scraper1", created_at=None):
@ -367,7 +367,7 @@ class TestSessionStore:
assert store.get("scraper1") is not None assert store.get("scraper1") is not None
def test_default_path_is_under_platformdirs(self): def test_default_path_is_under_platformdirs(self):
from wabot.sessions import default_store_path from wabot._sessions import default_store_path
assert default_store_path().name == "sessions.json" assert default_store_path().name == "sessions.json"
assert "wabot" in str(default_store_path()) assert "wabot" in str(default_store_path())
@ -432,11 +432,11 @@ class TestSessionStore:
- [ ] **Step 2: Run tests to verify they fail** - [ ] **Step 2: Run tests to verify they fail**
Run: `uv run pytest tests/unit/test_sessions.py -v` Run: `uv run pytest tests/unit/test_sessions.py -v`
Expected: FAIL — `ModuleNotFoundError: No module named 'wabot.sessions'` Expected: FAIL — `ModuleNotFoundError: No module named 'wabot._sessions'`
- [ ] **Step 3: Write the implementation** - [ ] **Step 3: Write the implementation**
`src/wabot/sessions.py`: `src/wabot/_sessions.py`:
```python ```python
"""Persistence of reattachable browser sessions as plain JSON. """Persistence of reattachable browser sessions as plain JSON.
@ -573,7 +573,7 @@ Expected: 14 passed
- [ ] **Step 5: Commit** - [ ] **Step 5: Commit**
```bash ```bash
git add src/wabot/sessions.py tests/unit/test_sessions.py git add src/wabot/_sessions.py tests/unit/test_sessions.py
git commit -m "feat: JSON SessionStore with stale eviction (replaces pickle persistence)" git commit -m "feat: JSON SessionStore with stale eviction (replaces pickle persistence)"
``` ```
@ -2773,7 +2773,7 @@ from unittest.mock import MagicMock
import pytest import pytest
import wabot import wabot
from wabot.sessions import SessionRecord, SessionStore from wabot import SessionRecord, SessionStore
@pytest.fixture @pytest.fixture
@ -3042,7 +3042,7 @@ from .hosts import (
) )
from .page import Page from .page import Page
from .pacing import HumanPacing, NoPacing from .pacing import HumanPacing, NoPacing
from .sessions import SessionRecord, SessionStore from ._sessions import SessionRecord, SessionStore
__all__ = [ __all__ = [
"Browser", "Browser",
@ -3282,7 +3282,7 @@ def site_url():
@pytest.fixture @pytest.fixture
def store(tmp_path): def store(tmp_path):
from wabot.sessions import SessionStore from wabot import SessionStore
return SessionStore(path=tmp_path / "sessions.json") return SessionStore(path=tmp_path / "sessions.json")
``` ```
@ -3422,7 +3422,7 @@ REATTACH_SCRIPT = textwrap.dedent(
""" """
import json, sys import json, sys
import wabot import wabot
from wabot.sessions import SessionStore from wabot import SessionStore
store = SessionStore(path=sys.argv[1]) store = SessionStore(path=sys.argv[1])
bot = wabot.browser(session=sys.argv[2], pacing=wabot.NoPacing(), store=store) bot = wabot.browser(session=sys.argv[2], pacing=wabot.NoPacing(), store=store)
@ -3807,7 +3807,10 @@ API reference
.. automodule:: wabot.pacing .. automodule:: wabot.pacing
:members: :members:
.. automodule:: wabot.sessions .. autoclass:: wabot.SessionRecord
:members:
.. autoclass:: wabot.SessionStore
:members: :members:
.. automodule:: wabot.hosts .. automodule:: wabot.hosts

@ -19,6 +19,7 @@ from selenium import webdriver
from selenium.common.exceptions import WebDriverException from selenium.common.exceptions import WebDriverException
from ._browser import Browser from ._browser import Browser
from ._sessions import SessionRecord, SessionStore
from .fields import CheckField, NullField, PageObject, SelectField, TextField from .fields import CheckField, NullField, PageObject, SelectField, TextField
from .hosts import ( from .hosts import (
ExternalServer, ExternalServer,
@ -31,7 +32,6 @@ from .hosts import (
) )
from .pacing import HumanPacing, NoPacing from .pacing import HumanPacing, NoPacing
from .page import Page from .page import Page
from .sessions import SessionRecord, SessionStore
__all__ = [ __all__ = [
"Browser", "Browser",

@ -36,6 +36,6 @@ def site_url():
@pytest.fixture @pytest.fixture
def store(tmp_path): def store(tmp_path):
from wabot.sessions import SessionStore from wabot import SessionStore
return SessionStore(path=tmp_path / "sessions.json") return SessionStore(path=tmp_path / "sessions.json")

@ -15,7 +15,7 @@ REATTACH_SCRIPT = textwrap.dedent(
""" """
import json, sys import json, sys
import wabot import wabot
from wabot.sessions import SessionStore from wabot import SessionStore
store = SessionStore(path=sys.argv[1]) store = SessionStore(path=sys.argv[1])
bot = wabot.browser(session=sys.argv[2], pacing=wabot.NoPacing(), store=store) bot = wabot.browser(session=sys.argv[2], pacing=wabot.NoPacing(), store=store)

@ -3,7 +3,7 @@ from unittest.mock import MagicMock
import pytest import pytest
import wabot import wabot
from wabot.sessions import SessionRecord, SessionStore from wabot import SessionRecord, SessionStore
@pytest.fixture @pytest.fixture

@ -1,6 +1,6 @@
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from wabot.sessions import SessionRecord, SessionStore from wabot._sessions import SessionRecord, SessionStore
def make_record(name="scraper1", created_at=None): def make_record(name="scraper1", created_at=None):
@ -63,7 +63,7 @@ class TestSessionStore:
assert store.get("scraper1") is not None assert store.get("scraper1") is not None
def test_default_path_is_under_platformdirs(self): def test_default_path_is_under_platformdirs(self):
from wabot.sessions import default_store_path from wabot._sessions import default_store_path
assert default_store_path().name == "sessions.json" assert default_store_path().name == "sessions.json"
assert "wabot" in str(default_store_path()) assert "wabot" in str(default_store_path())