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
:members:
.. automodule:: wabot.sessions
.. autoclass:: wabot.SessionRecord
:members:
.. autoclass:: wabot.SessionStore
:members:
:no-index:
.. automodule:: wabot.hosts
:members: ExternalServer, ManagedService, attach, service_alive

@ -44,7 +44,7 @@ wabot/ (repo root, branch feature/modernization
├── src/wabot/
│ ├── __init__.py # public API: browser(), sessions(), destroy(), re-exports
│ ├── pacing.py # NoPacing / HumanPacing
│ ├── sessions.py # SessionRecord, SessionStore
│ ├── _sessions.py # SessionRecord, SessionStore (underscored to avoid shadowing sessions())
│ ├── hosts.py # build_options, service_alive, ExternalServer,
│ │ # ManagedService, find_driver_binary, stop_service,
│ │ # 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:**
- Create: `src/wabot/sessions.py`
- Create: `src/wabot/_sessions.py`
- Test: `tests/unit/test_sessions.py`
- [ ] **Step 1: Write the failing tests**
@ -304,7 +304,7 @@ git commit -m "feat: pacing policies (HumanPacing default behavior, NoPacing for
```python
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):
@ -367,7 +367,7 @@ class TestSessionStore:
assert store.get("scraper1") is not None
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 "wabot" in str(default_store_path())
@ -432,11 +432,11 @@ class TestSessionStore:
- [ ] **Step 2: Run tests to verify they fail**
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**
`src/wabot/sessions.py`:
`src/wabot/_sessions.py`:
```python
"""Persistence of reattachable browser sessions as plain JSON.
@ -573,7 +573,7 @@ Expected: 14 passed
- [ ] **Step 5: Commit**
```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)"
```
@ -2773,7 +2773,7 @@ from unittest.mock import MagicMock
import pytest
import wabot
from wabot.sessions import SessionRecord, SessionStore
from wabot import SessionRecord, SessionStore
@pytest.fixture
@ -3042,7 +3042,7 @@ from .hosts import (
)
from .page import Page
from .pacing import HumanPacing, NoPacing
from .sessions import SessionRecord, SessionStore
from ._sessions import SessionRecord, SessionStore
__all__ = [
"Browser",
@ -3282,7 +3282,7 @@ def site_url():
@pytest.fixture
def store(tmp_path):
from wabot.sessions import SessionStore
from wabot import SessionStore
return SessionStore(path=tmp_path / "sessions.json")
```
@ -3422,7 +3422,7 @@ REATTACH_SCRIPT = textwrap.dedent(
"""
import json, sys
import wabot
from wabot.sessions import SessionStore
from wabot import SessionStore
store = SessionStore(path=sys.argv[1])
bot = wabot.browser(session=sys.argv[2], pacing=wabot.NoPacing(), store=store)
@ -3807,7 +3807,10 @@ API reference
.. automodule:: wabot.pacing
:members:
.. automodule:: wabot.sessions
.. autoclass:: wabot.SessionRecord
:members:
.. autoclass:: wabot.SessionStore
:members:
.. automodule:: wabot.hosts

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

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

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

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

@ -1,6 +1,6 @@
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):
@ -63,7 +63,7 @@ class TestSessionStore:
assert store.get("scraper1") is not None
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 "wabot" in str(default_store_path())