Skip to content

fix: tolerate symlinked directories in get_config - #293

Open
Manny7717 wants to merge 3 commits into
pytroll:mainfrom
Manny7717:fix/symlink-dirs
Open

Manny7717 wants to merge 3 commits into
pytroll:mainfrom
Manny7717:fix/symlink-dirs

Conversation

@Manny7717

Copy link
Copy Markdown

What

Fixes #266: get_config() crashed with FileExistsError when rsr_dir or rayleigh_dir is configured behind a symlink whose target does not exist.

os.makedirs(..., exist_ok=True) raises FileExistsError when a path component is a symlink whose target is missing — reproduced locally:

$ ln -s /tmp/real /tmp/data   # dangling: /tmp/real does not exist
$ os.makedirs('/tmp/data', exist_ok=True)
FileExistsError: [Errno 17] File exists: '/tmp/data'

Change

config.py now creates the symlink target in that case, so directories behind symlinks behave like ordinary directories. Verified to fail without the fix and pass with it.

Tests

  • test_get_config_tolerates_dangling_symlink (parametrized over rsr_dir/rayleigh_dir): get_config succeeds and the link target is created.
  • test_get_config_creates_missing_directories: ordinary missing directories still work.
  • flake8 clean.

os.makedirs(..., exist_ok=True) raises FileExistsError when a path
component is a symlink whose target does not exist. get_config()
therefore crashed on configurations that point rsr_dir or
rayleigh_dir behind such a symlink. Create the link target instead.
Closes pytroll#266

@mraspaud mraspaud left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for looking into this. Comment inline.

Comment thread pyspectral/config.py Outdated

@Manny7717 Manny7717 left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion — you're right, os.path.realpath resolves dangling symlinks to their (nonexistent) target, so the try/except was unnecessary. Simplified _ensure_dir to a single os.makedirs(os.path.realpath(path), exist_ok=True) call in 5d9e17d. All 3 tests in test_config.py still pass and flake8 is clean.

@codecov

codecov Bot commented Sep 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.24%. Comparing base (7a61fdf) to head (89da24a).
⚠️ Report is 17 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #293      +/-   ##
==========================================
+ Coverage   91.17%   91.24%   +0.07%     
==========================================
  Files          25       26       +1     
  Lines        2673     2697      +24     
==========================================
+ Hits         2437     2461      +24     
  Misses        236      236              
Flag Coverage Δ
unittests 91.24% <100.00%> (+0.07%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@djhoese djhoese left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I just had a couple suggestions.

cfg_file = tmp_path / "cfg.yaml"
cfg_file.write_text("".join(f"{key}: {value}\n" for key, value in settings.items()))

get_config(str(cfg_file))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_config should accept a Path so this conversion seems unnecessary.

Suggested change
get_config(str(cfg_file))
get_config(cfg_file)

cfg_file = tmp_path / "cfg.yaml"
cfg_file.write_text(f"rsr_dir: {data}\nrayleigh_dir: {data}\n")

get_config(str(cfg_file))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Suggested change
get_config(str(cfg_file))
get_config(cfg_file)

assert other.is_dir()


def test_get_config_creates_missing_directories(tmp_path):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this test be combined with the above test? They seem like similar situations so parametrize could be used to select either a missing directory (this test) or a symlink (the above test).

@adybbroe

Copy link
Copy Markdown
Collaborator

Many thanks for this! I have no further suggestions. Would be good to have it merged I think. Do you have time addressing the few comments above related to the unit tests @Manny7717 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Creation of rayleigh lut directory fails when symlink is present in path

4 participants