Repository navigation
Conversation
_activate_project now stamps last_used in cache/projects.json, so the home page's "Last used" caption and sort reflect opens rather than only creation, and tools managing the cache can tell which project is open. A registry that can't be written is logged and does not block opening.
|
| try: | ||
| projects = load_projects() | ||
| if project_id not in projects: | ||
| return | ||
| projects[project_id]["last_used"] = _now_str() | ||
| _save_projects(projects) | ||
| except (OSError, ValueError): | ||
| logger.warning("Could not record last_used for project %s", project_id) |
There was a problem hiding this comment.
🔴 Opening a project can now wipe the project registry, leaving the home page unable to load. Before this change, opening a project never wrote cache/projects.json. _record_last_used rewrites the file on every open through _save_projects (start_view.py:118-122), which truncates it with open(..., "w") before writing. If json.dump fails partway, the warning at line 195 swallows the error and the file is left empty or partly written. Fix: make _save_projects write atomically: dump to a temp file in the same directory, then os.replace it over the registry. That covers this path and the existing create and delete writers.
Why this was flagged
Trigger: a user opens any project from the home page, calling _activate_project (start_view.py:198), which now calls _record_last_used (line 201) and then _save_projects (line 193). _save_projects (lines 120-122) opens projects.json with mode "w", so the file is truncated before json.dump writes anything. If the write then fails with OSError (disk full or quota on the cache volume, or an I/O error on a synced or mounted cache), the except (OSError, ValueError) at line 194 logs a warning and the project still opens. The registry is left empty or holding truncated JSON. On the next home-page render, load_projects() at line 593 calls json.load with no guard, so json.JSONDecodeError crashes the page, or every project disappears from the list. On the base branch, opening a project only set st.session_state.st_project_id and never wrote the registry, so this exposure was limited to the rare create and delete actions. The new catch-and-continue hides the damage until the next home-page load.
Verification: _activate_project (start_view.py:201) now calls _record_last_used on every open, and _save_projects (lines 118-122) truncates with open(projects_file, "w"). The except at lines 194-195 only logs a warning, leaving cache/projects.json empty or partly written. load_projects() at line 593 then raises JSONDecodeError. Before this change, opening a project never wrote the registry.
| projects = load_projects() | ||
| if project_id not in projects: | ||
| return | ||
| projects[project_id]["last_used"] = _now_str() | ||
| _save_projects(projects) |
There was a problem hiding this comment.
🟡 (optional) Two sessions sharing one cache can now lose a newly created or deleted project when the other session opens a project. _record_last_used reads the full registry, changes one entry and writes the whole file back, with no lock. Another session can save or delete a project between that read and that write. Fix: make the read and write one locked step (for example a file lock around load and save), or update only the one entry under that lock. An atomic file replace alone does not prevent this lost update.
Why this was flagged
Trigger: two Streamlit sessions (browser tabs) share one cache/ directory, as in a hosted per-user container with several tabs open. Tab A opens project Y. _record_last_used calls load_projects() (start_view.py:189) and gets a snapshot without project X. Meanwhile tab B's save_project (start_view.py:150-157) writes X into the registry. Tab A then calls _save_projects(projects) (start_view.py:193) with its old snapshot, which overwrites the file and drops X. X's directory still exists on disk, but it no longer shows on the home page. A deletion in tab B can come back the same way. On the base branch, opening a project did not write the registry, so this race only existed between two create or delete calls. Now every project open is a writer. Nothing in the module locks the registry.
Verification: _record_last_used (start_view.py:188-193) calls projects = load_projects(), changes one entry and then calls _save_projects(projects). That function (lines 118-122) truncates the file and dumps the whole dict, with no lock and no re-read. The new project's directory exists but the registry no longer lists it, so it disappears from the home page.



Pull Request Summary 🚀
What does this PR do? 📝
Opening a project from the home page now stamps
last_usedfor that projectin
cache/projects.json. A new helper,start_view._record_last_used, iscalled from
_activate_project. If the registry can't be written, thefailure is logged and the project still opens.
Why is this change needed? 🤔
last_usedis only written when a project iscreated, so the home page's "Last used" caption and its "Last used" sort
show creation time, not when the project was last opened.
datasurepackage from PyPI for IPA's multi-user pilot: one Linux container per signed-in user, with project state synced to Azure Blob Storage and a per-project write lock so only one person edits a project at a time. It's one of three small, independent PRs that let the hosting layer stop working around DataSure (the others: read-only notice feat: show a read-only notice when the host marks a project read-only #413, headless keyring guidance feat: explain unusable keyring backends on headless servers #414)._activate_projectonly setsst.session_state.st_project_id,which leaves nothing on disk to show which project is open. DataSure Cloud
therefore holds the write lock on every project a user can edit for their
whole session, which locks colleagues out of projects nobody is working on.
With this change the lock can follow the project that is actually open.
How was this implemented? 🛠️
_record_last_used(project_id)loads the registry with the existingload_projects, updateslast_usedwith_now_str(), and saves with_save_projects. Unknown project ids are left untouched.OSErrorandValueError(for example a read-only cache or a corruptprojects.json) are logged as a warning, so opening a project never failsbecause of the registry.
docs/ARCHITECTURE.md(whatprojects.jsonholds),CHANGELOG.mdunder
[Unreleased].How to test or reproduce ? 🧪
uv run python -m pytest tests/views/test_start_view.py. NewTestRecordLastUsed: activation updateslast_usedand keepscreated_at; an unknown id leaves the registry unchanged; an unwritableregistry still opens the project.
shows a fresh "Last used" and moves to the top of the "Last used" sort.
just test(4580 passed, 6 skipped);just pre-commit-runclean.
Screenshots (if applicable) 📷
N/A
Checklist ✅
Reviewer Emoji Legend
:code::smiley::+1::100:...and I want the author to know it! This is a way to highlight positive parts of a code review.
:star: :star: :star:And I am providing reasons why it needs to be addressed as well as suggested improvements.
:star: :star:And I am providing suggestions where it could be improved either in this PR or later.
:star:...and consider this a suggestion, not a requirement.
:question:This should be a fully formed question with sufficient information and context that requires a response.
:memo::pick:This does not require any changes and is often better left unsaid. This may include stylistic, formatting, or organization suggestions and should likely be prevented/enforced by linting if they really matter
:recycle:Should include enough context to be actionable and not be considered a nitpick.