3
\$\begingroup\$

I have a small Python application that needs to load a few different files (templates and config files). It should look in a number of destinations, in the following order, and load the file from the first destination that contains such a file.

  • current working directory
  • $MYAPP_PATH (may contain several paths,separated by colon)
  • /usr/local/lib/python3.8/dist-packages/myapp/resources/

I have written the following:

def open_sp(filepath): #open searchpath
    pathlist = []
    pathlist.append('.')
    pathlist.extend(os.environ['MYAPP_PATH'].split(':'))
    pathlist.append(os.path.join(os.path.abspath(os.path.dirname(__file__)),'resources'))
    for p in pathlist:
        try:
            return open(os.path.join(p,filepath))
        except FileNotFoundError:
            pass
    raise FileNotFoundError()

And I have exported the following environment variable in shell:

$ export MYAPP_PATH=/home/jdoe/templates/:/home/jdoe/legacy_templates/

In my case, it will look for the file in the following locations:

  • ./foo.conf
  • /home/jdoe/templates/foo.conf
  • /home/jdoe/legacy_templates/foo.conf
  • /usr/local/lib/python3.8/dist-packages/foo.conf

And it seems to work. But I suspect this could be much more elegant, perhaps by using some built-in Python tool, maybe from the importlib-package.

Also, if considering my code, I'm not sure if FileNotFound is the proper exception to throw, since it expects a path argument, and I am not sure which of the path arguments to provide.

\$\endgroup\$
2
  • \$\begingroup\$ A colon ':' can be a valid character in a Windows path. It might make your code more portable to use a semi-colon ';' to separate paths in MYAPP_PATH. \$\endgroup\$
    – RootTwo
    Jun 17 at 0:49
  • \$\begingroup\$ @RootTwo: Good point. However I believe the convention on Unix is to use colons as separator, and I simply chose to stick to this convention. \$\endgroup\$ Jun 19 at 19:05

1 Answer 1

2
\$\begingroup\$

Rather than incrementally building up a list, you can just use a tuple literal.

Add PEP484 type hints.

Prefer pathlib over os.path for path manipulation.

This function shouldn't be obligated with opening the file, only finding the first matching path.

Raising FileNotFoundError is fine but you should give it a sane error message string.

Suggested

import os
from pathlib import Path


def get_path(filename: str) -> Path:
    paths = (
        '.',
        *os.environ['MYAPP_PATH'].split(':'),
        Path(__file__).parent / 'resources',
    )
    for p in paths:
        path = Path(p) / filename
        if path.is_file():
            return path

    raise FileNotFoundError(f'{filename} not found in the search directories')
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.