Clean up viewmaster - #14
Conversation
…t setup; add documentation for running Viewmaster locally.
…ommand line testing setup.
… variable instead of reading HOLDINGS_PATHS in customized httpd conf to obtain the holdings path. Also remove the step of creating symlinks of the holdings under /Library/Webserver/Documents.
…_paths function in viewmaster.py and viewmaster-without-pause.py, streamlining the codebase further.
…repository and correcting the pip install command.
…d update comments in viewmaster.py to reflect the new method of obtaining HOLDINGS_PATHS from the PDS3_HOLDINGS environment variable.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Team Run ID: Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughCentralized logging and app initialization: added create_logger/get_or_create_logger, init_once/create_app and blueprints; threaded logger through page-rendering/navigation APIs; refactored link into a blueprint; added iterators and richer PdsGroup/PdsGroupTable APIs; removed wsgi_init; added Sphinx docs, README, and packaging-related changes. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
Full details: Linked Issues checkExplanation The PR addresses all three linked issues: Issue Full details: Out of Scope Changes checkExplanation All changes are in-scope with linked issues; no unrelated modifications detected. Changes include logger refactoring, Flask blueprints, documentation (README/Sphinx), removal of wsgi_init.py, and configuration updates for environment variables. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (18)
viewmaster/viewmaster-without-pause.py (6)
886-889: cgi.escape is removed in modern Python; switch to html.escape.This will raise AttributeError on Python 3.8+. Replace cgi.escape with html.escape and import html.
-import cgi +import html @@ - if params['selection']: - anchor_suffix = '#' + cgi.escape(params['selection'], quote=True) + if params['selection']: + anchor_suffix = '#' + html.escape(params['selection'], quote=True) @@ - if params['selection']: - selection_escaped = cgi.escape(params['selection'], quote=True) + if params['selection']: + selection_escaped = html.escape(params['selection'], quote=True)Also applies to: 1678-1693
1712-1714: Fix ValidationError import and reference.
wtforms.validators.ValidationErroris used butwtformsisn’t imported; this will NameError. Import the symbol directly and reference it.-from wtforms import StringField, HiddenField +from wtforms import StringField, HiddenField +from wtforms.validators import ValidationError @@ - if matchobj is None: - raise wtforms.validators.ValidationError("") + if matchobj is None: + raise ValidationError("")Also applies to: 3-5
1493-1505: Bug: misspelled key and wrong return value.
defaults[keuy]andreturn clean_query_params(params)are incorrect.- new_params = params.copy() + new_params = params.copy() @@ - for key in defaults: - if key not in new_params: - new_params[key] = defaults[keuy] + for key in defaults: + if key not in new_params: + new_params[key] = defaults[key] - return clean_query_params(params) + return clean_query_params(new_params)
522-526: Undefined variable row_pdsfile.Should copy the current query pdsfile, not an undefined name. This currently crashes on the nonexistent index-row path.
- row_pdsfile = row_pdsfile.copy() + row_pdsfile = query_pdsfile.copy()
297-305: Consider gating symlink logic and unused function.
create_holdings_symlinksremains but is no longer used. Either remove or clearly mark as legacy to avoid confusion.
203-205: Prefer LOGGER.warning over warn.If pdslogger mirrors stdlib, use
warning()to align with modern APIs.Also applies to: 297-305, 297-305
viewmaster/viewmaster_config.py (2)
32-39: Testing config overridden on Linux (memcached sockets).On Linux, the block unconditionally sets memcache sockets, overriding the earlier testing “0” settings. This enables memcache during tests unintentionally.
-if platform.system() == 'Linux': - VIEWMASTER_MEMCACHE_PORT = '/var/run/memcached/memcached.socket' - PDSFILE_MEMCACHE_PORT = '/var/run/memcached/memcached.socket' +if platform.system() == 'Linux': + if VIEWMASTER_TESTING: + VIEWMASTER_MEMCACHE_PORT = 0 + PDSFILE_MEMCACHE_PORT = 0 + else: + VIEWMASTER_MEMCACHE_PORT = '/var/run/memcached/memcached.socket' + PDSFILE_MEMCACHE_PORT = '/var/run/memcached/memcached.socket'Also applies to: 40-46
24-31: Re-evaluate MAKE_SYMLINKS defaults.If symlink creation is removed from startup, keeping MAKE_SYMLINKS=True in production may be misleading.
viewmaster/viewmaster.py (10)
31-33: Do not hard‑code Flask secret keys.Load from an environment variable and fail closed in non‑testing; hard‑coded secrets are a security risk.
-app = Flask(__name__) -app.secret_key = "Cassini Grand Finale!" # needed by flask_wtf +app = Flask(__name__) +# Load secret from env; in testing allow a default, but never check in real keys +app.config['SECRET_KEY'] = os.getenv('VIEWMASTER_SECRET_KEY', 'dev-only')
1-1: Remove duplicate import.
redirectis imported twice.-from flask import Flask, flash, redirect, render_template, redirect, request, send_file +from flask import Flask, flash, redirect, render_template, request, send_file
360-363: Remove stray print.Debug print leaks noise in logs/STDOUT.
- LOGGER.replace_root(HOLDINGS_PATHS) - print(VIEWMASTER_PREFIX_+ICON_URL_) + LOGGER.replace_root(HOLDINGS_PATHS)
933-936: Replace cgi.escape with html.escape.
cgi.escapeis removed in modern Python; this will break at runtime.-import cgi +import html @@ - if params['selection']: - anchor_suffix = '#' + cgi.escape(params['selection'], quote=True) + if params['selection']: + anchor_suffix = '#' + html.escape(params['selection'], quote=True) @@ - if params['selection']: - selection_escaped = cgi.escape(params['selection'], quote=True) + if params['selection']: + selection_escaped = html.escape(params['selection'], quote=True)Also applies to: 1779-1781, 1791-1793, 2151-2170
1818-1820: Fix ValidationError import and usage.Avoid NameError by importing the symbol directly.
-from wtforms import StringField, HiddenField +from wtforms import StringField, HiddenField +from wtforms.validators import ValidationError @@ - if matchobj is None: - raise wtforms.validators.ValidationError("") + if matchobj is None: + raise ValidationError("")Also applies to: 1804-1816
1599-1605: Bug: misspelled key and wrong return value.Fix
keuytypo and return the cleaned new_params.- for key in defaults: - if key not in new_params: - new_params[key] = defaults[keuy] - - return clean_query_params(params) + for key in defaults: + if key not in new_params: + new_params[key] = defaults[key] + + return clean_query_params(new_params)
560-566: Undefined variable row_pdsfile.This branch currently crashes; copy the current query file instead.
- row_pdsfile = row_pdsfile.copy() + row_pdsfile = query_pdsfile.copy()
1844-1851: Path traversal risk in test routes; gate by testing flag.Routes serve arbitrary filesystem paths without confinement. Restrict to a directory via send_from_directory and only register in testing.
-from flask import ... , send_file +from flask import ... , send_from_directory @@ -@app.route('/icons-local/<path:query_path>') -def return_icons_local(query_path): - return send_file(f'../icons/{query_path}', mimetype='image/png') +if VIEWMASTER_TESTING: + @app.route('/icons-local/<path:query_path>') + def return_icons_local(query_path): + return send_from_directory('../icons', query_path, mimetype='image/png') @@ -@app.route('/holdings/<path:query_path>') -def return_holdings_local(query_path): - return send_file(f'{HOLDINGS_PATHS[0]}/{query_path}', mimetype='text/plain') + @app.route('/holdings/<path:query_path>') + def return_holdings_local(query_path): + return send_from_directory(HOLDINGS_PATHS[0], query_path)Also applies to: 1848-1851
1714-1716: Duplicate FILTER_REGEX definitions.There are two different FILTER_REGEX patterns. Consolidate to one to avoid confusion.
Also applies to: 1802-1804
305-313: Single holdings path assertion.You assert a single holdings path. If future configs need multiple paths, this will break. Consider logging a warning instead.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (4)
viewmaster/doc/viewmaster_v1.md(1 hunks)viewmaster/viewmaster-without-pause.py(4 hunks)viewmaster/viewmaster.py(1 hunks)viewmaster/viewmaster_config.py(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
viewmaster/viewmaster.py (1)
viewmaster/viewmaster-without-pause.py (1)
get_holdings_paths(139-146)
🪛 markdownlint-cli2 (0.18.1)
viewmaster/doc/viewmaster_v1.md
1-1: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
2-2: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above
(MD022, blanks-around-headings)
8-8: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
8-8: Multiple headings with the same content
(MD024, no-duplicate-heading)
9-9: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above
(MD022, blanks-around-headings)
9-9: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
10-10: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above
(MD022, blanks-around-headings)
10-10: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
12-12: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
14-14: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
15-15: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
16-16: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
19-19: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
19-19: Multiple headings with the same content
(MD024, no-duplicate-heading)
20-20: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above
(MD022, blanks-around-headings)
20-20: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
22-22: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
🔇 Additional comments (1)
viewmaster/viewmaster.py (1)
1897-1905: Pause/resume caching commented out.Confirm updated Pds3File caching semantics. Consider removing or updating to the new API to avoid stale caches.
Also applies to: 1976-1979
| - `python -m venv myenv` (Replace `myenv` with the desired name for your virtual environment) | ||
| - `source myenv/bin/activate` | ||
| - `pip install -r requirements.txt` | ||
| - Create an environement variable `PDS3_HOLDINGS` to store the path of PDS3 holdings. |
There was a problem hiding this comment.
Typos and requirement clarity.
- “environement” → “environment”.
- Clarify the path must end with “/holdings” to pass validation.
🤖 Prompt for AI Agents
In viewmaster/doc/viewmaster_v1.md around line 17, fix the typo and clarify the
requirement: change “environement” to “environment” and update the sentence to
state that the PDS3_HOLDINGS environment variable should store the path to the
PDS3 holdings and that the path must end with “/holdings” to pass validation.
| - Execute viewmaster.py in the terminal: | ||
| - `sudo -E python viewmaster/viewmaster.py` | ||
| - Open your browser and enter `http://127.0.0.1:8080/` (this is the value assigned to VIEWMASTER_PREFIX_ in viewmaster_config.py). Now the testing viewmaster will be running on your browser. |
There was a problem hiding this comment.
❓ Verification inconclusive
Avoid sudo for local runs.
Port 8080 doesn’t require root; recommending sudo -E is unnecessary and risky. Use the regular interpreter; if env propagation is needed, export before running.
Remove sudo from local run instructions (viewmaster/doc/viewmaster_v1.md:21-23)
Use python viewmaster/viewmaster.py instead of sudo -E python…; export any needed environment variables beforehand.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
22-22: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
🤖 Prompt for AI Agents
In viewmaster/doc/viewmaster_v1.md around lines 21 to 23, remove the use of sudo
in the local run instructions: replace `sudo -E python viewmaster/viewmaster.py`
with `python viewmaster/viewmaster.py` and add a short note to export any
required environment variables beforehand (e.g., `export VAR=value`) so users
run the script without elevated privileges.
| # Test for a command line run, sudo python viewmaster/viewmaster.py | ||
| VIEWMASTER_TESTING = 'viewmaster.py' in sys.argv[0] | ||
|
|
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Make testing mode explicit and less brittle.
Relying on 'viewmaster.py' in sys.argv[0] is fragile. Consider an env var flag and/or CLI option.
-VIEWMASTER_TESTING = 'viewmaster.py' in sys.argv[0]
+VIEWMASTER_TESTING = os.getenv('VIEWMASTER_TESTING', '0') == '1'Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Leave as-is, this is for manually running viewmaster script in the command line during development
There was a problem hiding this comment.
@juzen2003, understood! I'll leave the testing detection as-is for your command-line development workflow.
(„• ֊ •„)
づ♡
…up instructions, adding an overview of key components, and detailing the core Python files and their functionalities. Update version information to October 2025.
…p instructions, and local running guidelines for Viewmaster.
…outlining the iterator classes, their purposes, key features, and design patterns for navigating PDS file structures.
There was a problem hiding this comment.
Actionable comments posted: 5
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
README.md(1 hunks)viewmaster/doc/viewmaster_v1.md(1 hunks)
🧰 Additional context used
🪛 LanguageTool
🪛 markdownlint-cli2 (0.18.1)
viewmaster/doc/viewmaster_v1.md
19-19: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
24-24: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
37-37: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
49-49: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
59-59: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
64-64: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
67-67: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
78-78: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
131-131: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
134-134: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
155-155: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
158-158: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
180-180: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
183-183: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
196-196: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
199-199: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
214-214: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
217-217: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
219-219: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
220-220: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
221-221: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
222-222: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
223-223: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
224-224: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
225-225: Unordered list indentation
Expected: 6; Actual: 12
(MD007, ul-indent)
226-226: Unordered list indentation
Expected: 6; Actual: 12
(MD007, ul-indent)
227-227: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
230-230: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
231-231: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
232-232: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
233-233: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
234-234: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
235-235: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
236-236: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
237-237: Unordered list indentation
Expected: 6; Actual: 12
(MD007, ul-indent)
238-238: Unordered list indentation
Expected: 6; Actual: 12
(MD007, ul-indent)
241-241: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
242-242: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
243-243: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
244-244: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
245-245: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
248-248: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
README.md
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
18-18: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
23-23: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
36-36: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
🔇 Additional comments (1)
README.md (1)
1-43: Content aligns well with PR objectives.The documentation correctly reflects the shift from Apache/httpd configuration parsing to environment-variable-based holdings path management (
PDS3_HOLDINGS), and removes references to symlink creation. The setup and local-run instructions are clear and actionable.
| # Viewmaster Help Guide | ||
|
|
||
| Version 1.0 | ||
| October 2025 | ||
|
|
||
| # Viewmaster | ||
|
|
||
| ## Overview | ||
|
|
||
| **Viewmaster** is a web-based file and document viewer and management tool. It is focused on handling and displaying various types of PDS documents and folders. | ||
|
|
||
| --- | ||
|
|
||
| # Running Viewmaster | ||
|
|
||
| ## Environment Setup (first-time only) | ||
|
|
||
| 1. Clone the repository: | ||
| ```bash | ||
| git clone https://github.com/SETI/rms-viewmaster.git | ||
| ``` | ||
|
|
||
| 2. Create a virtual environment and install dependencies in the `rms-viewmaster` directory: | ||
| ```bash | ||
| python -m venv myenv # Replace "myenv" with your preferred name | ||
| source myenv/bin/activate | ||
| pip install -r requirements.txt | ||
| ``` | ||
|
|
||
| 3. Set the environment variable `PDS3_HOLDINGS` to the path of your PDS3 holdings. | ||
|
|
||
| --- | ||
|
|
||
| ## Running Locally | ||
|
|
||
| 1. Start the server: | ||
| ```bash | ||
| sudo -E python viewmaster/viewmaster.py | ||
| ``` | ||
|
|
||
| 2. Open your browser and go to: | ||
| [http://127.0.0.1:8080/](http://127.0.0.1:8080/) | ||
| *(This corresponds to `VIEWMASTER_PREFIX_` in `viewmaster_config.py`.)* | ||
|
|
||
| --- |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Fix markdown lint violations in setup section.
The document contains multiple linting violations flagged in static analysis and a previous review. Key issues include missing blank lines around headings (MD022), incorrect code block spacing (MD031), and list indentation inconsistencies (MD007). These should be corrected throughout the document, particularly in the setup and running sections.
For example:
- Add blank lines before/after heading on line 1 and line 6
- Add blank lines before/after code blocks on lines 19, 24, 37
- Verify list indentation aligns with markdown standards
Generate a script to identify and fix the remaining markdown lint issues:
🏁 Script executed:
#!/bin/bash
# Description: Run markdownlint on the file to identify remaining issues
markdownlint-cli2 viewmaster/doc/viewmaster_v1.mdLength of output: 12709
Comprehensive markdown linting fixes required throughout the document.
The markdownlint scan identified 103 errors spanning the entire document, not just the setup sections. Address the following violations:
- MD025 (multiple h1 headings): Lines 6, 14, 47, 74 — restructure to single top-level heading
- MD031 (blanks around code blocks): Lines 19, 24, 37 — add blank lines before/after fenced code blocks
- MD022 (blanks around headings): Lines 49, 59, 64, 67, 78, 131, 134, 155, 158, 180, 183, 196, 199, 214, 217 — add blank lines before/after headings
- MD007 (list indentation): Lines 219–227, 230–238, 241–245, 248 — fix indentation to 2 spaces per level
- MD013 (line length): Many lines exceed 80 character limit — wrap or reformat
- MD032 (blanks around lists): Lines 50, 60, 65, 68, 135, 159, 184, 200, 218 — add blank lines before/after lists
- MD024 (duplicate headings): Multiple instances of "Overview," "Key Components" — use unique headings
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
19-19: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
24-24: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
37-37: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
🤖 Prompt for AI Agents
In viewmaster/doc/viewmaster_v1.md around lines 1–45, the document fails many
markdownlint rules (MD025, MD031, MD022, MD007, MD013, MD032, MD024) —
consolidate multiple H1s into a single top-level heading, ensure single blank
lines before and after all headings and fenced code blocks, add blank lines
around lists, normalize list indentation to 2 spaces per level, wrap or reflow
long lines to keep under ~80 characters, and rename or merge duplicate section
headings (e.g., "Overview", "Key Components") so each heading is unique; apply
these changes consistently across the file to resolve the reported lint
violations.
| pip install -r requirements.txt | ||
| ``` | ||
|
|
||
| 3. Set the environment variable `PDS3_HOLDINGS` to the path of your PDS3 holdings. |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Clarify that PDS3_HOLDINGS path must end with /holdings.
The instruction should explicitly state that the path must end with /holdings to pass validation. This requirement was noted in a previous review and should be included for clarity.
-3. Set the environment variable `PDS3_HOLDINGS` to the path of your PDS3 holdings.
+3. Set the environment variable `PDS3_HOLDINGS` to the absolute path of your PDS3 holdings directory (must end with `/holdings`):
+
+ ```bash
+ export PDS3_HOLDINGS=/absolute/path/to/holdings
+ ```🤖 Prompt for AI Agents
In viewmaster/doc/viewmaster_v1.md around line 30, clarify that the
PDS3_HOLDINGS environment variable must point to the absolute path that ends
with "/holdings" so validation will pass; update the text to explicitly state
the trailing "/holdings" requirement and adjust the example export line to show
an absolute path ending with "/holdings".
|
|
||
| 1. Start the server: | ||
| ```bash | ||
| sudo -E python viewmaster/viewmaster.py |
There was a problem hiding this comment.
🧩 Analysis chain
CRITICAL: Remove sudo -E from local run instructions.
Port 8080 does not require root privileges. Using sudo -E is unnecessary and introduces a security risk. This issue was flagged in a previous review and should be corrected before merge.
1. Start the server:
```bash
- sudo -E python viewmaster/viewmaster.py
+ python viewmaster/viewmaster.py
```If environment variable propagation is needed, export variables before executing:
export PDS3_HOLDINGS=/path/to/holdings
python viewmaster/viewmaster.pyI'll verify the claims in this review comment by examining the file and the underlying code.
---
🏁 Script executed:
```shell
#!/bin/bash
# Examine the file content around line 38
cat viewmaster/doc/viewmaster_v1.md | head -50 | tail -20
Length of output: 357
🏁 Script executed:
#!/bin/bash
# Search for port configuration in viewmaster.py
rg -n "port|8080|listen" viewmaster/viewmaster.py -C2Length of output: 1497
🏁 Script executed:
#!/bin/bash
# Check if viewmaster.py requires elevated privileges or uses ports < 1024
rg -n "sudo|root|privilege|:80\b|:443\b|:8080" viewmaster/viewmaster.pyLength of output: 530
---
🏁 Script executed:
```shell
#!/bin/bash
# Check for file system operations that might require elevated privileges
rg -n "open\(|mkdir|symlink|chmod|chown|mount|rmtree" viewmaster/viewmaster.py -C1
Length of output: 2577
🏁 Script executed:
#!/bin/bash
# Check viewmaster_config.py to see what environment setup is needed
cat viewmaster/viewmaster_config.py | head -100Length of output: 1802
🏁 Script executed:
#!/bin/bash
# Look for any explicit privilege checks or warnings in the documentation
rg -n "sudo|root|privilege|elevation" viewmaster/doc/viewmaster_v1.mdLength of output: 45
CRITICAL: Remove sudo -E from local run instructions.
Port 8080 does not require root privileges. Using sudo -E is unnecessary and introduces a security risk. This issue was flagged in a previous review and should be corrected before merge.
1. Start the server:
```bash
- sudo -E python viewmaster/viewmaster.py
+ python viewmaster/viewmaster.py
```If environment variable propagation is needed, export variables before executing:
export PDS3_HOLDINGS=/path/to/holdings
python viewmaster/viewmaster.py🤖 Prompt for AI Agents
In viewmaster/doc/viewmaster_v1.md around line 38, the local run instruction
uses "sudo -E python viewmaster/viewmaster.py" which is unnecessary and
insecure; remove "sudo -E" so the command runs as "python
viewmaster/viewmaster.py" and update the surrounding guidance to show exporting
any required environment variables beforehand (e.g., include a short note or
example showing export PDS3_HOLDINGS=... followed by running the python
command).
| # Key Components | ||
|
|
||
| ## 1. Main Application Code (`viewmaster/`) | ||
| - **Python Modules:** | ||
| - `link.py`, `pdsgroup.py`, `pdsgrouptable.py`, `pdsiterator.py`: Handle core logic for grouping, iterating, and linking PDS documents or datasets. | ||
| - `viewmaster.py`, `viewmaster-without-pause.py`: Main entry points and application logic for the viewer. | ||
| - `viewmaster_config.py`: Configuration settings for the application. | ||
|
|
||
| - **Templates (`viewmaster/templates/`):** | ||
| - HTML files for rendering views: tables, grids, navigation, error pages, feedback, etc. | ||
| - These templates provide the web interface, including navigation, table/grid views, and feedback/error handling. | ||
|
|
||
| ## 2. Static Assets (`icons/`) | ||
| - Organized by color (black, blue, gray) and size (30, 50, 100, 200, 500). | ||
| - Icons for document types (archive, binary, book, image, PDF, software, etc.) and folders (archives, binary, cubes, diagrams, etc.). | ||
| - Used for visually representing files and folders in the UI. | ||
|
|
||
| ## 3. Web Server Integration | ||
| - `viewmaster.wsgi`, `link.wsgi`, `wsgi_init.py`: WSGI entry points for deploying the application with a WSGI-compatible web server. | ||
|
|
||
| ## 4. Project Metadata | ||
| - `README.md`: Project overview and instructions. | ||
| - `requirements.txt`: Python dependencies. | ||
| - `LICENSE`, `CODE_OF_CONDUCT.md`, `CONTRIBUTING.md`: Standard open-source project files. | ||
|
|
||
| --- | ||
|
|
||
| # Summary of the Core Python Files | ||
|
|
||
| ## `viewmaster.py` and `viewmaster-without-pause.py` | ||
|
|
||
| ### Overview | ||
| `viewmaster.py` is the core module of the application. It sets up a Flask-based server that serves as a document viewer for PDS files. Users can browse, view, and navigate through a hierarchical structure of files and folders, with support for multiple document types and metadata. | ||
|
|
||
| `viewmaster-without-pause.py` is a variant designed to run without pausing or waiting for user input. | ||
|
|
||
| ### Key Components and Functionality | ||
|
|
||
| 1. **Imports and Initialization** | ||
| - Imports Flask and related modules (FlaskForm, StringField, HiddenField) for web handling and form processing. | ||
| - Imports utility modules (os, sys, logging, psutil, etc.) and custom modules (pdsfile, pdsiterator, pdslogger, pdstable, pdsgroup, pdsgrouptable). | ||
| - Initializes the Flask app with a secret key and configuration values (like `LOCAL_IP_ADDRESS`). | ||
| - Imports configuration parameters (e.g., `LOCALHOST_`, `VIEWMASTER_PREFIX_`, `WEBSITE_HTTP_HOME`) from `viewmaster_config.py`. | ||
| - Configures logging with `pdslogger`, with separate log files for info and debug levels. | ||
|
|
||
| 2. **Constants and Configuration** | ||
| - Defines constants such as `ICON_ROOT_`, `ICON_URL_`, and `VIEWABLE_EXTENSIONS`. | ||
| - Defines `ASSOCIATED_CATEGORIES` for navigation and grouping. | ||
| - Sets UI and navigation limits (`MAX_PAGES`, `MAX_NAV_STRLEN`, etc.). | ||
|
|
||
| 3. **Holdings Path Management** | ||
| - `get_holdings_paths()` reads the Apache configuration file (or environment variable) to determine the holdings directories. | ||
| - `validate_holdings_paths()` checks for existence and logs warnings/errors. | ||
| - `create_holdings_symlinks()` likely creates symbolic links for easier access. | ||
|
|
||
| 4. **Cache Initialization** | ||
| - Functions like `initialize_caches()`, `build_cache()`, and `reset_cache()` manage caching for performance. | ||
| - Uses memcached (via `pylibmc`) for caching. | ||
|
|
||
| 5. **Navigation and Page Generation** | ||
| - Functions such as `get_prev_next_navigation()`, `list_next_pdsfiles()`, and `fill_level_navigation_links()` generate navigation links. | ||
| - `get_directory_page()` and `directory_page_html()` render directory listings. | ||
| - `get_product_page_info()` and `product_page_html()` render product pages. | ||
|
|
||
| 6. **URL Parameter Handling** | ||
| - Functions like `get_query_params_from_request()`, `get_query_params_from_url()`, and `clean_query_params()` parse and sanitize URL parameters. | ||
| - `url_params()` constructs URLs with the proper parameters. | ||
|
|
||
| 7. **Form Handling** | ||
| - Defines a `FilterForm` class (Flask-WTF) for file name filtering. | ||
| - The `/set_filter` route processes POST requests to update filters. | ||
|
|
||
| 8. **Route Definitions** | ||
| - The main route (`@app.route('/<path:query_path>')`) handles all incoming requests. | ||
| - Additional routes handle filtering, serving icons, holdings, and feedback. | ||
|
|
||
| 9. **Utility Functions** | ||
| - Functions like `format_row_value()` and `format_tuple()` prepare data for display. | ||
| - `trim_html()` likely cleans up HTML for rendering. | ||
|
|
||
| --- | ||
|
|
||
| ## `viewmaster_config.py` | ||
|
|
||
| ### Overview | ||
| `viewmaster_config.py` is the configuration module for Viewmaster. It defines environment-specific settings (URLs, logging, caching) based on whether the app is in testing, local, or production mode. | ||
|
|
||
| ### Key Components and Functionality | ||
| 1. **Environment Detection** | ||
| - Uses `platform` and `socket` to detect OS and hostname. | ||
| - Flags like `VIEWMASTER_TESTING` and `VIEWMASTER_FOR_MARK` determine the environment. | ||
|
|
||
| 2. **Configuration Blocks** | ||
| - **Testing**: Localhost URLs, caching disabled, log name `pds.viewmaster.testing`. | ||
| - **Local**: Custom localhost URLs, symlinks enabled, caching disabled, log name `pds.viewmaster.local`. | ||
| - **Production**: Deployment URLs, symlinks enabled, caching disabled, log name `pds.viewmaster.server`. | ||
|
|
||
| 3. **Platform-Specific Settings** | ||
| - Linux: uses `/var/run/memcached/memcached.socket`, `/var/www/` paths, Apache config in `/etc/apache2/`. | ||
| - macOS: uses `/var/tmp/memcached.socket`, `/Library/WebServer/` paths, Apache config in `/usr/local/etc/httpd/`. | ||
|
|
||
| 4. **Other Settings** | ||
| - `USE_SHELVES_ONLY = False` (application supports multiple storage mechanisms). | ||
|
|
||
| --- | ||
|
|
||
| ## `link.py` | ||
|
|
||
| ### Overview | ||
| `link.py` is a Flask-based microservice for URL redirection. It redirects requests either to the main Viewmaster app (for directories) or directly to files. | ||
|
|
||
| ### Key Components and Functionality | ||
| 1. **Imports and Initialization** | ||
| - Uses Flask (redirect, abort), os, glob, logging, and `pdslogger`. | ||
| - Imports configuration from `viewmaster_config.py`. | ||
|
|
||
| 2. **Logging Configuration** | ||
| - Log name derived from Viewmaster config, replacing "viewmaster" with "link". | ||
| - Configured for rotating log files. | ||
|
|
||
| 3. **Route Definitions** | ||
| - Main route: `/path:query_path>` defaults to `volumes`. | ||
| - Determines whether to redirect to a directory view or directly to a file. | ||
|
|
||
| 4. **Redirection Logic** | ||
| - If a directory: redirects to `VIEWMASTER_PREFIX_ + query_path`. | ||
| - If a file: finds file via glob, then redirects to `WEBSITE_HTTP_HOME + relative path`. | ||
| - If missing: logs error and returns 404. | ||
|
|
||
| --- | ||
|
|
||
| ## `pdsgroup.py` | ||
|
|
||
| ### Overview | ||
| `pdsgroup.py` defines the `PdsGroup` class for managing an ordered set of `PdsFile` objects. Groups related files (e.g., a file and its label) for display in the UI. | ||
|
|
||
| ### Key Components | ||
| - **Initialization**: Stores parent directory, anchor, and files. | ||
| - **Methods**: | ||
| - `__len__`, `__repr__`, `copy()`. | ||
| - Sorting (`sort()`), grouping (`group_children()`). | ||
| - File management (`append()`, `remove()`, `hide()`, `unhide()`). | ||
| - Iterators for visible/hidden/all files. | ||
| - **Icons/Viewsets**: Manages closed/open states and viewable sets. | ||
|
|
||
| --- | ||
|
|
||
| ## `pdsgrouptable.py` | ||
|
|
||
| ### Overview | ||
| `pdsgrouptable.py` defines the `PdsGroupTable` class for managing an ordered set of `PdsGroup` objects. It organizes groups into tables for display. | ||
|
|
||
| ### Key Components | ||
| - **Initialization**: Stores groups and parent. | ||
| - **Methods**: | ||
| - `__repr__`, `copy()`. | ||
| - Insert groups/files (`insert_group`, `insert_file`, `insert()`). | ||
| - Sorting (`sort_in_groups`, `sort_groups`). | ||
| - File/group hiding/removal. | ||
| - Iterators for groups/files. | ||
| - **Static Methods**: | ||
| - `sort_tables()`, `tables_from_pdsfiles()`, `merge_index_row_tables()`. | ||
|
|
||
| --- | ||
|
|
||
| ## `pdsiterator.py` | ||
|
|
||
| ### Overview | ||
| `pdsiterator.py` provides three main iterator classes for navigating through PDS file structures. | ||
|
|
||
| ### Key Components and Functionality | ||
| - **PdsDirIterator** (lines 29-144) | ||
| - **Purpose**: Iterates across related directories, jumping into adjacent directories and parallel volumes | ||
| - **Key Features**: | ||
| - Uses a global `DIRECTORY_CACHE` to optimize performance by caching directory listings | ||
| - Supports both forward and backward iteration (via `sign` parameter) | ||
| - Handles case-insensitive path matching | ||
| - Returns tuples of `(logical_path, display_path, level)` where: | ||
| - `level = 0`: same directory level | ||
| - `level = 1`: different directory level | ||
| - Uses `pdsfile.NEIGHBORS` to find related directories using fnmatch patterns | ||
|
|
||
| - **PdsFileIterator** (lines 150-268) | ||
| - **Purpose**: Iterates through files within directories, with support for jumping to adjacent directories when needed | ||
| - **Key Features**: | ||
| - Supports pattern matching and filtering of file names | ||
| - Can exclude files based on patterns | ||
| - Supports custom filter functions | ||
| - Handles "cousin" navigation (jumping to adjacent parent directories) | ||
| - Returns tuples with level indicators: | ||
| - `level = 0`: sibling files (same directory) | ||
| - `level = 1`: cousin files (different directory) | ||
|
|
||
| - **PdsRowIterator** (lines 274-336) | ||
| - **Purpose**: Simple iterator for files within a single directory (siblings only) | ||
| - **Key Features**: | ||
| - More lightweight than `PdsFileIterator` | ||
| - Only iterates within the same parent directory | ||
| - Always returns `level = 0` since it doesn't cross directory boundaries | ||
|
|
||
| - **Utility Functions:** | ||
| - **`dirs_only()`** (lines 12-16): Filter function that only returns directories | ||
|
|
||
| - **Key Design Patterns:** | ||
| 1. **Caching**: Uses `DIRECTORY_CACHE` to avoid repeated expensive directory operations | ||
| 2. **Case-insensitive matching**: All path comparisons are done in lowercase | ||
| 3. **Iterator protocol**: All classes implement `__iter__()`, `__next__()`, and `next()` methods | ||
| 4. **Cloning**: Each iterator has a `copy()` method for creating reversed or modified versions | ||
| 5. **Flexible navigation**: Supports both forward (`sign=1`) and backward (`sign=-1`) iteration | ||
|
|
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Extended documentation is comprehensive but contains formatting issues.
The detailed breakdown of Key Components, Core Python Files, and module documentation (lines 47-256) is thorough and helpful. However, static analysis flags multiple list indentation issues (MD007) throughout these sections. Consider running a markdown linter to fix all indentation to the standard 2-space increment and ensure blank lines separate headings as per MD022.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
49-49: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
59-59: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
64-64: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
67-67: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
78-78: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
131-131: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
134-134: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
155-155: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
158-158: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
180-180: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
183-183: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
196-196: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
199-199: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
214-214: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
217-217: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
219-219: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
220-220: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
221-221: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
222-222: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
223-223: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
224-224: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
225-225: Unordered list indentation
Expected: 6; Actual: 12
(MD007, ul-indent)
226-226: Unordered list indentation
Expected: 6; Actual: 12
(MD007, ul-indent)
227-227: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
230-230: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
231-231: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
232-232: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
233-233: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
234-234: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
235-235: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
236-236: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
237-237: Unordered list indentation
Expected: 6; Actual: 12
(MD007, ul-indent)
238-238: Unordered list indentation
Expected: 6; Actual: 12
(MD007, ul-indent)
241-241: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
242-242: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
243-243: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
244-244: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
245-245: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
248-248: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
🤖 Prompt for AI Agents
In viewmaster/doc/viewmaster_v1.md around lines 47 to 256 there are multiple
markdown list indentation errors (MD007) and missing blank lines after headings
(MD022); run a markdown linter (e.g., markdownlint) and fix by normalizing list
indentation to 2-space increments for each nesting level, ensure nested bullets
align under their parent items, and add a single blank line after each heading;
reflow any mis-indented blocks so headings are followed by a blank line and
lists consistently use 2-space indentation throughout.
|
A couple of initial comments:
Different LLMs have different skills. Claude is tuned for coding, not for English prose. On the other hand, ChatGPT is excellent at prose. I suggest that you write a bunch of messy documentation yourself, perhaps building on what Claude wrote for you. Don't worry too much about spelling, grammar, or structure. Just get all the details in. Then paste it in to ChatGPT with a prompt like: "You are an expert English editor, Python programmer, and web developer. Below is technical documentation for a web application. Your job is to edit this document into a fully readable technical description, properly organized with headings and subheadings, with correct English spelling and grammar. The target audience is an experienced Python developer who is not previously familiar with this code base but needs to become an expert. Keep all details in the original document. Also mark areas where there is ambiguity and make suggestions for ways in which the document can be further improved at both the detail and general level. The result should be in Markdown." |
|
Also I agree that you shouldn't be using |
…clude new pdsgroup module documentation.
…blank line before conditional check in PdsGroup class for improved readability.
Inserted blank lines before method definitions and within methods in the PdsGroupTable class to enhance code readability and maintainability.
…erator, and PdsRowIterator classes Inserted blank lines before method definitions and within methods in the PdsDirIterator, PdsFileIterator, and PdsRowIterator classes to enhance code readability and maintainability.
Inserted blank lines before method definitions and within methods in viewmaster.py to enhance code readability and maintainability.
…rator, PdsFileIterator, and PdsRowIterator classes. Updated calls to use __next__() directly, enhancing code clarity and consistency with Python 3 standards.
Inserted a blank line before the code block in the Running Locally section of README.md to enhance clarity and maintainability of the documentation.
Revised the docstring in the get_holdings_paths function to specify the full name of the environment variable as "PDS3_HOLDINGS_DIR". This change enhances the clarity and accuracy of the documentation.
… and Mac Expanded the instructions for creating the necessary web server directories by specifying the commands for both Linux and Mac environments. This change aims to improve clarity and ensure users avoid permission issues when setting up their environment.
…wmaster.py Replaced direct app instantiation with a create_app function in both link.py and viewmaster.py. This change centralizes the app creation process and initializes the logger and other global variables in a dedicated init_once function, improving code organization and maintainability.
Updated import statements in link.wsgi and viewmaster.wsgi to reflect the new module structure. Removed the wsgi_init.py file as it is no longer needed, streamlining the application setup process.
Updated link.wsgi to use a create_app function for application instantiation. Refactored link.py to define a Blueprint for routing, enhancing modularity and organization of the Flask application. This change improves the overall structure and maintainability of the code.
Updated viewmaster.wsgi to utilize a create_app function for application initialization. Refactored viewmaster.py to register the viewmaster Blueprint for routing, enhancing modularity and organization. This change improves the maintainability and clarity of the application structure.
…and viewmaster.py Updated the docstrings for the create_app function in both link.py and viewmaster.py to provide detailed descriptions of the application initialization process and the registered blueprints. Added a docstring for the init_once function in viewmaster.py to clarify its role in setting up global module state. These changes improve code documentation and maintainability.
…EADME.md Expanded the README.md to include a comprehensive overview of the Viewmaster and PdsFile rules interface. Added sections on rendering architecture, main file table, related files table, documentation table, info section, and neighbor navigation, along with associated rules and rendering details. Included new images for better visualization of the tables and navigation elements.
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
viewmaster/pdsgroup.py (2)
351-404:⚠️ Potential issue | 🔴 CriticalRemove duplicate
sortmethod definition.The
sortmethod is defined twice: once at lines 297-349 and again at lines 351-404. The second definition shadows the first. Both implementations appear identical, so the duplicate should be removed.🐛 Proposed fix: remove duplicate method
self.rows = [basename_dict[key] for key in sorted] - def sort(self, labels_after=None, dirs_first=None, dirs_last=None, - info_first=None): - """Sort member files by parent-defined rules with tweaks for labels. - - Parameters: - labels_after (bool|None): Place labels after their targets. - dirs_first (bool|None): Sort directories before files. - dirs_last (bool|None): Sort directories after files. - info_first (bool|None): Prioritize info files. - - Returns: - None - """ - - basename_dict = {pdsf.basename:pdsf for pdsf in self.rows} - - if self.parent_pdsf: - sorted = self.parent_pdsf.sort_basenames(list(basename_dict.keys()), - labels_after=labels_after, - dirs_first=dirs_first, - dirs_last=dirs_last, - info_first=info_first) - - # If there are multiple labels in the group, make sure each label - # is adjacent to any data files of the same name. This can comes up - # with Juno JIRAM, at least. - labels = [basename for basename in sorted - if self.parent_pdsf.basename_is_label(basename)] - if len(labels) > 1: - for label in labels: - l = sorted.index(label) # index of label - pattern = os.path.splitext(label)[0] + '.' - tlist = [t for t in range(len(sorted)) # indices of targets - if sorted[t].startswith(pattern)] - tlist.remove(l) - - # If the label is not adjacent, but could be... - if tlist and (l+1) not in tlist and (l-1) not in tlist: - - # Locate the nearest target with a matching basename - diffs = [(abs(t-l),t) for t in tlist] - tbest = min(diffs)[1] - - # Relocate the label to an adjacent location - if l < tbest: - sorted.insert(tbest - 1, sorted.pop(l)) - else: - sorted.insert(tbest + 1, sorted.pop(l)) - - else: - sorted = list(basename_dict.keys()) # for merged directories - sorted.sort() - - self.rows = [basename_dict[key] for key in sorted] def append(self, pdsf, hidden=False):🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@viewmaster/pdsgroup.py` around lines 351 - 404, There are two identical definitions of the method sort in the class (one earlier and one later) causing the latter to shadow the former; remove the duplicate definition block (the second sort method that contains basename_dict, parent_pdsf handling, label adjacency logic and final self.rows reassignment) so only a single sort method remains, preserving the existing implementation that references basename_dict, parent_pdsf, basename_is_label, sort_basenames and self.rows.
326-326: 🧹 Nitpick | 🔵 TrivialInconsistent indentation: 2 spaces instead of 4.
The nested
forloop body uses 2-space indentation instead of the standard 4 spaces used elsewhere in the file. This is syntactically valid but inconsistent.if len(labels) > 1: - for label in labels: - l = sorted.index(label) # index of label + for label in labels: + l = sorted.index(label) # index of label🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@viewmaster/pdsgroup.py` at line 326, The nested loop starting with the statement "for label in labels:" has its body indented with 2 spaces instead of the project's standard 4-space indentation; update the body of the "for label in labels:" loop to use 4-space indentation to match the rest of the file (ensure any nested statements inside that loop such as subsequent statements or inner loops are adjusted as well) and run the project's linter/formatter to verify consistent indentation across the function containing that loop.viewmaster/pdsiterator.py (2)
276-276: 🧹 Nitpick | 🔵 TrivialTypo in comment: "basenam" should be "basename".
- # the pattern applies to the basenam + # the pattern applies to the basename🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@viewmaster/pdsiterator.py` at line 276, Fix the typo in the inline comment on the line containing "# the pattern applies to the basenam" by changing "basenam" to "basename" so the comment reads "# the pattern applies to the basename"; locate this comment in viewmaster/pdsiterator.py (around the block handling pattern matching) and update the text accordingly.
81-91:⚠️ Potential issue | 🔴 CriticalMissing early return causes
NameErrorwhenpdsfisNone.When
pdsf is None, lines 82-85 initialize an empty iterator, but execution continues to lines 87-91 which callisinstance(pdsf, ...)onNone. This will not raise an error immediately, butpdsf_clswill never be assigned (neither condition matches), causingNameErroron line 102.🐛 Proposed fix: add early return
if pdsf is None: self.neighbors = [] self.neighbor_index = 0 self.current_logical_path = None self.sign = 1 + return if isinstance(pdsf, pdsfile.Pds3File): pdsf_cls = pdsfile.Pds3File elif isinstance(pdsf, pdsfile.Pds4File): pdsf_cls = pdsfile.Pds4File🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@viewmaster/pdsiterator.py` around lines 81 - 91, When pdsf is None the code initializes the iterator state (self.neighbors, self.neighbor_index, self.current_logical_path, self.sign) but continues and later tries to use pdsf_cls, causing a NameError; fix this by adding an early return immediately after those initializations so the function exits when pdsf is None and skips the later isinstance checks and use of pdsf_cls (look for the block that sets self.neighbors / self.neighbor_index / self.current_logical_path / self.sign and add a return there).viewmaster/viewmaster.py (1)
1797-1832:⚠️ Potential issue | 🟠 Major
get_query_params_from_dict()is broken for extra keys.Deleting from
new_paramsinsidefor key in new_paramsraisesRuntimeError: dictionary changed size during iteration, and the finalclean_query_params(params)throws away the filtered/defaulted dict anyway. Build a new dict and returnclean_query_params(new_params).🐛 Proposed fix
- new_params = params.copy() - for key in new_params: - if key not in defaults: - del new_params[key] + new_params = { + key: value for key, value in params.items() if key in defaults + } @@ - return clean_query_params(params) + return clean_query_params(new_params)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@viewmaster/viewmaster.py` around lines 1797 - 1832, get_query_params_from_dict currently mutates new_params during iteration causing a RuntimeError and then calls clean_query_params(params) which ignores the filtered/defaulted dict; fix by constructing a new dict (e.g., start from defaults and update with only allowed keys from the incoming params) instead of deleting while iterating, then call and return clean_query_params(new_params) so the cleaned/typed values are based on the merged defaults and allowed input keys; adjust references to defaults, new_params, and clean_query_params in get_query_params_from_dict accordingly.
♻️ Duplicate comments (1)
viewmaster/viewmaster.py (1)
2541-2560:⚠️ Potential issue | 🟠 MajorKeep WSGI and
__main__on the same startup path.
create_app()is now the production entry point, butpdsviewable.load_icons(...)andinitialize_caches(reset=False)still only run in__main__. A WSGI deployment will therefore skip icon loading,LOGGER.replace_root(), andPds3File.preload(), so production boots with different global state than local runs.♻️ Proposed fix
+INITIALIZED = False + def init_once(): @@ - global LOGGER, HOLDINGS_PATHS, PAGE_CACHE + global LOGGER, HOLDINGS_PATHS, PAGE_CACHE, INITIALIZED + if INITIALIZED: + return logger = get_or_create_logger() HOLDINGS_PATHS = get_holdings_path(logger) PAGE_CACHE = get_page_cache(logger) + pdsviewable.load_icons(path=ICON_ROOT_, url=ICON_URL_, color=ICON_COLOR, + logger=logger) + initialize_caches(reset=False) + INITIALIZED = True @@ if __name__ == "__main__": app = create_app() - pdsviewable.load_icons(path=ICON_ROOT_, url=ICON_URL_, color=ICON_COLOR, - logger=LOGGER) - - initialize_caches(reset=False) app.run(host='0.0.0.0', port=8080, debug=True)Also applies to: 2584-2589
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@viewmaster/viewmaster.py` around lines 2541 - 2560, The WSGI path skips startup steps that only run in __main__, causing different global state; move the icon loading, cache initialization, LOGGER.replace_root(), and Pds3File.preload() into the shared startup invoked by both WSGI and __main__ (e.g., call pdsviewable.load_icons(...), initialize_caches(reset=False), LOGGER.replace_root(), and Pds3File.preload() from init_once() or from create_app() before returning the app) so create_app() and init_once() perform the same initialization sequence and both entry points produce identical global state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@README.md`:
- Around line 252-260: The markdown table in README containing rows with
`page['tables']`, `page['associations']`, `page['documents']`, `page['info']`
and the header for Page Section / Page Key / Main Rules must be fixed by
normalizing the header separator length to match the table width, adding missing
trailing pipe characters at the end of each data row, and ensuring there is a
blank line before and after the table; update the separator line to a consistent
number of dashes, append a trailing `|` to lines that lack it (including the
rows containing `SORT_KEY`, `ASSOCIATIONS`, `associations_to_documents`,
`INFO_FILE_BASENAMES`, `NEIGHBORS`, `SIBLINGS`), and surround the entire table
with a blank line above and below so it renders consistently.
In `@requirements.txt`:
- Around line 1-13: The requirements.txt currently lists packages without
version constraints (flask, flask_wtf, myst-parser, psutil, pylibmc, pytest,
rms-pdsfile, rms-pdslogger, rms-pdstable, rms-translator, sphinx,
sphinxcontrib-napoleon, sphinx-rtd-theme); update this file to pin or bound
versions to ensure reproducible builds by either (a) running pip freeze and
replacing the file with the exact pins, or (b) adding conservative constraints
(e.g., package>=X.Y,<X+1) for each listed package—pay special attention to
sphinx, sphinxcontrib-napoleon and sphinx-rtd-theme—and commit the updated
requirements.txt so CI/ReadTheDocs uses fixed versions.
- Line 12: Remove the redundant dependency "sphinxcontrib-napoleon" from
requirements.txt; keep using the built-in extension 'sphinx.ext.napoleon'
already declared in docs/conf.py and then regenerate any lockfile or pinned
requirements (e.g., pip-compile / poetry lock / update requirements-dev) so
dependency metadata and CI install steps no longer include the removed package.
In `@viewmaster/pdsgrouptable.py`:
- Line 141: Replace the explicit list comprehension return [g for g in
self.groups] with the simpler and more idiomatic list(self.groups); locate the
method that returns the groups (the function containing the return statement
referencing self.groups in pdsgrouptable.py) and change the return to use
list(self.groups) to produce the same list more clearly and efficiently.
- Around line 446-448: The function tables_from_pdsfiles currently uses mutable
default arguments exclusions=set() and hidden=set(); change those defaults to
None in the signature and inside tables_from_pdsfiles initialize local empty
sets when they are None (e.g., if exclusions is None: exclusions = set(); if
hidden is None: hidden = set()), then continue to create copies where needed
(e.g., new_exclusions = set(exclusions)) to avoid shared mutable state; update
references to exclusions and hidden in the function body accordingly.
In `@viewmaster/viewmaster.py`:
- Around line 331-345: The validate_holdings_paths() loop currently appends
abspath even if one or more sibling dirs are missing; change the logic so that
you only append abspath to valid_abspaths when all three required siblings
('holdings','shelves','volinfo') exist and are directories: inside the for
dirname loop compute testpath = prefix_ + dirname, track a boolean (e.g.
valid=True) and set it False and break when os.path.exists(testpath) is False or
not os.path.isdir(testpath) (using the existing logger.warning/logger.error
calls), and after the loop only call valid_abspaths.append(abspath) if valid
remains True (do not append on partial failures).
- Around line 2575-2577: Replace the hard-coded Flask signing key by loading
VIEWMASTER_SECRET_KEY from the environment and fail-fast if it's not set: in the
place that creates the Flask app (the app = Flask(__name__) / create_app()
block) remove the literal app.secret_key assignment and instead read
os.environ['VIEWMASTER_SECRET_KEY'] (or os.getenv and raise if None) and assign
that value to app.secret_key or app.config['SECRET_KEY']; if the env var is
missing raise a RuntimeError with a clear message so the process exits
immediately.
---
Outside diff comments:
In `@viewmaster/pdsgroup.py`:
- Around line 351-404: There are two identical definitions of the method sort in
the class (one earlier and one later) causing the latter to shadow the former;
remove the duplicate definition block (the second sort method that contains
basename_dict, parent_pdsf handling, label adjacency logic and final self.rows
reassignment) so only a single sort method remains, preserving the existing
implementation that references basename_dict, parent_pdsf, basename_is_label,
sort_basenames and self.rows.
- Line 326: The nested loop starting with the statement "for label in labels:"
has its body indented with 2 spaces instead of the project's standard 4-space
indentation; update the body of the "for label in labels:" loop to use 4-space
indentation to match the rest of the file (ensure any nested statements inside
that loop such as subsequent statements or inner loops are adjusted as well) and
run the project's linter/formatter to verify consistent indentation across the
function containing that loop.
In `@viewmaster/pdsiterator.py`:
- Line 276: Fix the typo in the inline comment on the line containing "# the
pattern applies to the basenam" by changing "basenam" to "basename" so the
comment reads "# the pattern applies to the basename"; locate this comment in
viewmaster/pdsiterator.py (around the block handling pattern matching) and
update the text accordingly.
- Around line 81-91: When pdsf is None the code initializes the iterator state
(self.neighbors, self.neighbor_index, self.current_logical_path, self.sign) but
continues and later tries to use pdsf_cls, causing a NameError; fix this by
adding an early return immediately after those initializations so the function
exits when pdsf is None and skips the later isinstance checks and use of
pdsf_cls (look for the block that sets self.neighbors / self.neighbor_index /
self.current_logical_path / self.sign and add a return there).
In `@viewmaster/viewmaster.py`:
- Around line 1797-1832: get_query_params_from_dict currently mutates new_params
during iteration causing a RuntimeError and then calls
clean_query_params(params) which ignores the filtered/defaulted dict; fix by
constructing a new dict (e.g., start from defaults and update with only allowed
keys from the incoming params) instead of deleting while iterating, then call
and return clean_query_params(new_params) so the cleaned/typed values are based
on the merged defaults and allowed input keys; adjust references to defaults,
new_params, and clean_query_params in get_query_params_from_dict accordingly.
---
Duplicate comments:
In `@viewmaster/viewmaster.py`:
- Around line 2541-2560: The WSGI path skips startup steps that only run in
__main__, causing different global state; move the icon loading, cache
initialization, LOGGER.replace_root(), and Pds3File.preload() into the shared
startup invoked by both WSGI and __main__ (e.g., call
pdsviewable.load_icons(...), initialize_caches(reset=False),
LOGGER.replace_root(), and Pds3File.preload() from init_once() or from
create_app() before returning the app) so create_app() and init_once() perform
the same initialization sequence and both entry points produce identical global
state.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: cf7e81ab-110a-43a1-ae03-543d8d0c1df1
⛔ Files ignored due to path filters (5)
README_icon/associations.pngis excluded by!**/*.pngREADME_icon/documents.pngis excluded by!**/*.pngREADME_icon/info.pngis excluded by!**/*.pngREADME_icon/main_table.pngis excluded by!**/*.pngREADME_icon/nav.pngis excluded by!**/*.png
📒 Files selected for processing (10)
README.mdlink.wsgirequirements.txtviewmaster.wsgiviewmaster/link.pyviewmaster/pdsgroup.pyviewmaster/pdsgrouptable.pyviewmaster/pdsiterator.pyviewmaster/viewmaster.pywsgi_init.py
💤 Files with no reviewable changes (1)
- wsgi_init.py
| flask | ||
| flask_wtf | ||
| myst-parser | ||
| psutil | ||
| pylibmc | ||
| pytest | ||
| rms-pdsfile | ||
| rms-pdslogger | ||
| rms-pdstable | ||
| rms-translator | ||
| sphinx | ||
| sphinxcontrib-napoleon | ||
| sphinx-rtd-theme |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider pinning dependency versions for reproducibility.
None of the dependencies have version constraints, which can lead to non-reproducible builds and potential breaking changes when packages are updated. Consider adding version pins or ranges, especially for the Sphinx-related packages.
💡 Example with version constraints
You could generate a pinned requirements file using:
pip freeze > requirements-pinned.txtOr add minimum version constraints:
-sphinx
+sphinx>=7.0,<8.0
-sphinx-rtd-theme
+sphinx-rtd-theme>=2.0,<3.0This helps ensure consistent builds across environments and Read the Docs deployments.
🧰 Tools
🪛 OSV Scanner (2.3.3)
[HIGH] 1-1: urllib3 1.26.20: urllib3 streaming API improperly handles highly compressed data
[HIGH] 1-1: urllib3 1.26.20: Decompression-bomb safeguards bypassed when following HTTP redirects (streaming API)
[HIGH] 1-1: urllib3 1.26.20: urllib3 allows an unbounded number of links in the decompression chain
[HIGH] 1-1: urllib3 1.26.20: urllib3 redirects are not disabled when retries are disabled on PoolManager instantiation
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@requirements.txt` around lines 1 - 13, The requirements.txt currently lists
packages without version constraints (flask, flask_wtf, myst-parser, psutil,
pylibmc, pytest, rms-pdsfile, rms-pdslogger, rms-pdstable, rms-translator,
sphinx, sphinxcontrib-napoleon, sphinx-rtd-theme); update this file to pin or
bound versions to ensure reproducible builds by either (a) running pip freeze
and replacing the file with the exact pins, or (b) adding conservative
constraints (e.g., package>=X.Y,<X+1) for each listed package—pay special
attention to sphinx, sphinxcontrib-napoleon and sphinx-rtd-theme—and commit the
updated requirements.txt so CI/ReadTheDocs uses fixed versions.
There was a problem hiding this comment.
@juzen2003, understood — I'll leave the dependency versions unpinned.
✏️ Learnings added
Learnt from: juzen2003
Repo: SETI/rms-viewmaster PR: 14
File: requirements.txt:1-13
Timestamp: 2026-03-12T05:59:55.438Z
Learning: In the rms-viewmaster repository, the maintainer (juzen2003) intentionally keeps requirements.txt without version pins or constraints. Do not flag unpinned dependencies in requirements.txt for this repository.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: juzen2003
Repo: SETI/rms-viewmaster PR: 14
File: docs/Makefile:11-20
Timestamp: 2025-12-12T23:26:32.767Z
Learning: In the rms-viewmaster repository, the `docs/Makefile` is auto-generated by Sphinx and should not be modified, even if linters flag missing standard phony targets.
| app = Flask(__name__) | ||
| app.secret_key = "Cassini Grand Finale!" # needed by flask_wtf | ||
| init_once() |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does Flask use SECRET_KEY to sign session cookies, and does Flask-WTF use the same key for CSRF tokens?
💡 Result:
Yes.
- Flask uses
app.config["SECRET_KEY"]to cryptographically sign the session cookie (Flask’s default “secure cookie” session), so the client can’t tamper with session data without invalidating the signature. [1] - Flask-WTF uses a secret key for CSRF token signing/validation and, by default, it uses the Flask app’s
SECRET_KEY. If you want a different key specifically for CSRF, setWTF_CSRF_SECRET_KEY. [2][3]
Sources:
[1] Flask docs – Configuration Handling (SECRET_KEY signs the session cookie) (flask.palletsprojects.com)
[2] Flask-WTF docs – CSRF Protection (WTF_CSRF_SECRET_KEY to use a separate key) (flask-wtf.readthedocs.io)
[3] Flask-WTF docs – Configuration (WTF_CSRF_SECRET_KEY setting) (flask-wtf.readthedocs.io)
Citations:
- 1: https://flask.palletsprojects.com/config/%23instance-folders?utm_source=openai
- 2: https://flask-wtf.readthedocs.io/en/latest/csrf/?utm_source=openai
- 3: https://flask-wtf.readthedocs.io/config/?utm_source=openai
🏁 Script executed:
# First, let's check the file exists and get context around the specified lines
wc -l viewmaster/viewmaster.pyRepository: SETI/rms-viewmaster
Length of output: 92
🏁 Script executed:
# Read the relevant section with context
sed -n '2570,2585p' viewmaster/viewmaster.pyRepository: SETI/rms-viewmaster
Length of output: 531
🏁 Script executed:
# Search for all occurrences of SECRET_KEY or secret_key in the codebase
rg -i "secret.?key|create_app" viewmaster/viewmaster.py -B 2 -A 5Repository: SETI/rms-viewmaster
Length of output: 896
🏁 Script executed:
# Check for environment variable usage patterns in the codebase
rg "os\.getenv|os\.environ|config" viewmaster/viewmaster.py | head -20Repository: SETI/rms-viewmaster
Length of output: 614
🏁 Script executed:
# Check imports at the top of the file
head -50 viewmaster/viewmaster.py | grep -E "^import|^from"Repository: SETI/rms-viewmaster
Length of output: 451
🏁 Script executed:
# Check if os is imported anywhere in the file
rg "^import os" viewmaster/viewmaster.pyRepository: SETI/rms-viewmaster
Length of output: 77
🏁 Script executed:
# Check how viewmaster_config.py is structured
head -50 viewmaster/viewmaster_config.pyRepository: SETI/rms-viewmaster
Length of output: 2396
🏁 Script executed:
# Check if there's already an environment variable for the secret key
rg "VIEWMASTER_SECRET_KEY|SECRET_KEY" viewmaster/ -iRepository: SETI/rms-viewmaster
Length of output: 157
🏁 Script executed:
# Check deployment/production documentation
find . -name "*.md" -o -name "*.txt" -o -name "*.rst" | head -20Repository: SETI/rms-viewmaster
Length of output: 284
🏁 Script executed:
# Look for any README or deployment docs
ls -la | grep -i readmeRepository: SETI/rms-viewmaster
Length of output: 184
Move Flask signing key to environment variable.
Hard-coded SECRET_KEY becomes the shared signing key for all deployments from create_app(). Anyone with source code access can forge signed session cookies and Flask-WTF CSRF tokens. Load from VIEWMASTER_SECRET_KEY environment variable and fail fast if missing.
Proposed fix
def create_app():
app = Flask(__name__)
- app.secret_key = "Cassini Grand Finale!" # needed by flask_wtf
+ secret_key = os.getenv('VIEWMASTER_SECRET_KEY')
+ if not secret_key:
+ raise RuntimeError('VIEWMASTER_SECRET_KEY must be set')
+ app.secret_key = secret_key
init_once()
app.register_blueprint(viewmaster_bp)🧰 Tools
🪛 Ruff (0.15.5)
[error] 2576-2576: Possible hardcoded password assigned to: "secret_key"
(S105)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@viewmaster/viewmaster.py` around lines 2575 - 2577, Replace the hard-coded
Flask signing key by loading VIEWMASTER_SECRET_KEY from the environment and
fail-fast if it's not set: in the place that creates the Flask app (the app =
Flask(__name__) / create_app() block) remove the literal app.secret_key
assignment and instead read os.environ['VIEWMASTER_SECRET_KEY'] (or os.getenv
and raise if None) and assign that value to app.secret_key or
app.config['SECRET_KEY']; if the env var is missing raise a RuntimeError with a
clear message so the process exits immediately.
There was a problem hiding this comment.
@juzen2003, understood — I'll leave the secret_key as-is.
🧠 Learnings used
Learnt from: juzen2003
Repo: SETI/rms-viewmaster PR: 14
File: viewmaster/viewmaster.py:2496-2501
Timestamp: 2026-01-12T22:07:33.958Z
Learning: In viewmaster/viewmaster.py, the if __name__ == '__main__': block that sets debug=True is for development only and should not run in production. Ensure production deployment uses the WSGI server path and that any debug execution is removed or guarded (e.g., do not enable debug mode in the main entry point for production).
…bility Adjusted the layout of the summary table in the README.md to enhance clarity and organization. This change ensures that the information is presented in a more structured manner, making it easier for users to understand the main rules associated with each page section.
Updated the method for retrieving all groups in the PdsGroupTable class to use the built-in list constructor for improved clarity and performance. This change enhances code readability while maintaining functionality.
…ions and hidden parameters Modified the tables_from_pdsfiles method in the PdsGroupTable class to set default values for exclusions and hidden parameters to None, ensuring they are initialized as empty sets if not provided. This change improves the method's robustness and usability.
Critique of PR #14 ("Clean up viewmaster") vs. the issues it claims to fixDate: 2026-07-17 Methodology: Read the PR description and all three issues, reviewed the full diff Bottom lineThe local developer flow genuinely works now — verified that The big one: WSGI is broken (regression vs. #9)
from viewmaster.viewmaster import create_app
application = create_app()But Verified the failure directly: instantiating the app via Issue #9 explicitly requires: "For a server environment, it needs to be able to be Issue #3 (holdings path with spaces; Python version in wsgi_init.py) — mostly fixed
Issue #9 (easier for developers) — partially done; PR itself admits itGenuinely delivered (verified against main)
Not delivered or contradicted
Issue #12 (documentation) — good where it exists, with one explicit gap
Code-quality notes on the refactor itselfThe One sloppy spot: RecommendationBefore merge:
Issue #3 is the only one of the three that's honestly closeable as-is — and even |
Updated the init_once function to ensure it is idempotent by introducing an _INITIALIZED flag. Modified the get_holdings_path function to raise an exception instead of exiting the program on failure. Enhanced the documentation for both functions to clarify their roles and updated the create_app function's docstring to reflect the new initialization steps. These changes improve the robustness and clarity of the application startup process.
Updated README.md to include placeholders for badges and clarified server binding options. Refactored viewmaster.py to allow dynamic host and port configuration for the local development server, improving flexibility. Removed outdated comments and configuration variables from link.py and viewmaster_config.py to streamline the codebase.
…it tests for holdings path validation Enhanced the README.md to clarify the requirements for the `PDS3_HOLDINGS_DIR` environment variable, specifying the necessary directory structure. Introduced a new test file `test_validate_holdings_paths.py` to implement unit tests for the `validate_holdings_paths` function, ensuring proper validation of holdings directory paths and their required sibling directories.
Updated README.md to clarify the setup of required environment variables, including `VIEWMASTER_TESTING` and `VIEWMASTER_SECRET_KEY`. Added a new test file `test_config_testing_mode.py` to validate the behavior of the application in testing mode, ensuring proper handling of the secret key and memcache settings based on the environment. These changes improve documentation and testing coverage for the application configuration.
Introduced a new test file `test_link.py` to validate the behavior of the Link service regarding file resolution through the `PDS3_HOLDINGS_DIR`. The tests cover scenarios for directory redirects, file existence checks, and handling of missing files. Updated the link function to ensure files are redirected to `WEBSITE_HTTP_HOME/holdings/...` only if they exist in the specified holdings directory, improving error handling and clarity in the redirection logic.
… add deployment guide Updated README.md to include detailed instructions for deploying the application with Apache and mod_wsgi, covering environment variables, WSGI entry points, and a minimal vhost configuration. Added a new deployment guide in docs/deployment.rst for comprehensive setup instructions. Introduced an example Apache configuration file in examples/apache2/viewmaster.conf to assist users in configuring their server environment.
Introduced support for PDS4 holdings by adding the `PDS4_HOLDINGS_DIR` environment variable and related validation functions in `viewmaster.py`. Updated the configuration to allow for dynamic log directory management in testing mode, ensuring logs are stored in user-writable locations. Enhanced the README.md to document the new environment variable and its usage, and added unit tests to validate the PDS4 holdings path functionality and its integration with existing configurations. This improves the application's flexibility and usability for users managing PDS4 data.
Simplified the validation logic in `viewmaster.py` to focus solely on the `holdings` directory, removing checks for sibling `shelves` and `volinfo` directories. Updated the README.md to reflect these changes, clarifying the requirements for the `PDS3_HOLDINGS_DIR` environment variable. Additionally, refactored unit tests in `test_validate_holdings_paths.py` to align with the new validation logic, ensuring accurate testing of holdings directory paths.
Fixes #3. Progresses #9. Progresses #12.
Changes:
README.mdrequirments.txtto include Sphinx related packagesrms-viewmaster/docsrms-viewmaster/docs/_build.readthedocs.yamlfor Read the docs (build successed, verified on forked repo)rms-viewmaster/viewmaster/docviewmaster_without_pause.pyand its related documentation files..rstfile to the same as the title length. Note: Sphinx only complains when the header length is shorter than the title length.README.mdto match the format of the one fromrms-starcatrequirements.txtto have all packages in alphabetical orderlink.pyandviewmaster.pyto move the step of creating logger from global level to a function. Docstrings are updated correspondingly.create_app()/init_once()now load icons and runinitialize_caches()(with an idempotency guard), so the Apache/WSGI path works again—not onlypython -m viewmaster.viewmaster.MAKE_SYMLINKS/WEBSITE_ROOT_; Linux testing mode keeps memcache disabled;VIEWMASTER_TESTINGis env-driven (with a narrow CLI fallback); production paths/URLs overridable via env; local logs no longer need sudo under/var/www.holdings*symlink glob—resolves files viaPDS3_HOLDINGS_DIR.Secrets:
VIEWMASTER_SECRET_KEYrequired in production (dev fallback only when testing).Pending items:
pip install rms-viewmaster.githubdirectory with the appropriate actions for uploading to PyPI and TestPyPI, running lint, and hopefully some day running tests.Summary by CodeRabbit
New Features
Documentation
Refactor
Chores
✏️ Tip: You can customize this high-level summary in your review settings.