1

As far as I'm aware I'm using best practices to define paths (using raw strings) and how I go about joining them (using os.path.join()), e.g.

import os

fdir = r'C:\Code\...\samples'

fpath = os.path.join(fdir, 'fname.ext')

and doing so has not caused me any problems when running my code within a Python or command shell. If I print fpath to the console I get consistent use of \s in the path:

C:\Code...\samples\fname.ext

But when I run a Docker containerized version of the code and run the image I get the error:

FileNotFoundError: [Errno 2] No such file or directory: 'C:\Code\...\samples/fname.ext'

I don't understand why os.path.join() has used a / to join fdir and fname.ext when the rest of the path included \\. It doesn't do this when I run the code outside of the container.

I have tried using os.path.normpath():

fpath = os.path.join(fdir, 'fname.ext')
fpath = os.path.normpath(fpath)

as discussed here, and os.sep.join():

fpath = os.sep.join([fdir, 'fname.ext'])

as covered here, and Path().joinpath():

from pathlib import Path

fpath = Path(fdir).joinpath('fname.ext')

as well as Path() / 'path_to_add':

fpath = Path(fdir) / 'fname.ext'

as discussed here, but in every case I end up with the same result using os.path.join().

Can someone please help me to understand what is going on and how to create consistent paths that will work whether I run the code in Python in a Windows environment, or in a Docker container?

Update Nov. 16:

In trying to keep my question brief I think I've left out details that are crucial. Apologies to those who have kindly taken the time to offer suggestions based on my incomplete description of the problem.

My code needs to import/export files from/to directories that are defined within a user-specified configuration file.

So the configuration file has a section of code where the user defines variables and paths, e.g.

samplesDir = r"path-to-samples-directory"

The variables are stored in a dictionary of dictionaris and stored as a .json.

At the start of the code the user defines the key that selects the dictionary of interest so that at various parts in my code when a file needs to be imported/exported, the paths are at hand.

So back to my example, samplesDir is stored in the configuration dictionary, cfgDict, so all I need to do is append the file name:

sampleFpath = os.path.join(sampleDir, sampleFname)

and sampleFname is determined based on other variables.

Because of the dynamic nature of the variables (including directory paths and file paths), I think it rules out the use of static path defined in a .yml with Docker Compose.

Update Nov. 18:

It may help to include a few more details and some screenshots.

Folder structure of src on my local system

The above screenshot shows the file and folder structure of the src directory containing the source code, the main app.py script for command-line use, the Dockerfile, etc.

The configs folder contains JSON files that includes variables, paths to directories and files. The user can create configuration files either by copying an existing one and modifying the entries, or configuration files can be generated by calling config.py.

Within config.py I have pre-set variables and paths, so that the directory path to the configuration files (configs), sample files (sample_DROs) and others (e.g. fiducials) are all within src.

I don't anticipate any reason why the user would want to store the config files anywhere else, nor do I expect them to want to use different sample files (or move them elsewhere). However, they will undoubtedly create their own fiducials and may decide not to store them in the fiducials directory (i.e. somewhere not within the src directory).

Likewise I have pre-set the download directory (based on the parameters stored within the configuration files, files are fetched from a server and downloaded) to be the default Downloads directory:

rootDownloadDir = os.path.join(Path.home(), "Downloads", "xnat_downloads")

Those files are later imported, processed, and the outputs are (by default) exported into sub-directories within rootDownloadDir.

Within Dockerfile I set the working directory of the container to be that of the source code and copy all of the contents of src (with the exception of some directories defined in .dockerignore):

WORKDIR C:/Code/WP1.3_multiple_modalities/src
...
COPY . .

so that the structure of the container mimics that of WORKDIR:

Folder structure in container

Hence I have allowed for flexibility in import/export directories, and they are by default a combination of paths within and outside of the src directory. And so, the code executed within the container will need to access files both within and outside of src.

That said, I don't know what rootDownloadDir will look like when os.path.join(Path.home(), "Downloads", "xnat_downloads") is run within the container.

This has got me thinking - Is it bad practice to set the download directory outside of src?

Returning to the original error:

No such file or directory

the sample file is in the container:

Contents of sample_DROs within container

critor
  • 173
  • 2
  • 11
  • 1
    If it's a Linux container, that value for `fdir` isn't meaningful. The `os.path` functions won't change the direction of slashes in strings you give it. Can you use the current directory instead of hard-coding a path in your code? – David Maze Oct 15 '21 at 15:59
  • Thanks @DavidMaze, I don't think your suggestion would work for my case (please correct me if I'm wrong). In my question I left out other details that I think would rule out using the working directory. The code searches for a configuration file and uses the directories, file paths and other variables stored within the file elsewhere in the code, which includes directories where inputs will be imported, and results exported to. – critor Oct 15 '21 at 19:20

1 Answers1

1

From the actual behavior I can suppose that the container is based on Unix-like image. Path separator is / in such systems. To build an environment-independent path which works inside and outside of the container you need the following steps:

  1. Mounting of host folder to container directory.
  2. Environment variable inside and outside the container.

I can show an example of how this is achievable via docker-compose tool and its configuration file docker-compose.yml:

# docker-compose.yml file
version: '3'

services:
  <service_name>: # your service name here
    image: <image_name> # name of image your container is built on
    environment:
      - SAMPLES_PATH=/samples
    volumes:
      - C:\Code\somepath\samples:/samples

In your python code you can use the following structure:

import os

fdir = os.getenv('SAMPLES_PATH', r'C:\Code\...\samples')

fpath = os.path.join(fdir, 'fname.ext')
  • Thanks @AshotVantsyan I'm a Docker newbie and have not yet tried anything using Docker Compose but I'm not sure if you suggestion will work. I wanted to keep my question concise but have probably left out critical info, which I'll try to clarify now. – critor Oct 15 '21 at 19:50
  • @critor can you describe file system structure in more details? Particularly, if user defines variables of directories in JSON does container have access to such directories? E.g. if user specified `D:\mypath` and it is not achievable in the container, then path separator issue is trivial, the real one is that path cannot be accessed. – Ashot Vantsyan Oct 16 '21 at 16:06
  • Hi @AshotVantsyan, yes the container does have access to the folder containing the JSONs since I copy the directory containing them to the container. But your question has got me thinking about files that are downloaded to a directory outside of the container. I don't know how that will work if at all. Since I can't add screenshots to comments I have added an update (see "Update Nov. 18") that will hopefully clarify what it is that I'm doing and what I'm trying to do. – critor Oct 18 '21 at 10:00
  • To be clear, the original error that this post relates to, occurred when trying to read in a file that should be accessible since the container. – critor Oct 18 '21 at 10:10
  • 1
    I've just noticed what should have been an obvious problem. In the traceback the filepath is to the file in my Windows system (albeit with an odd combination of '\\' and '/') - rather than the directory within the container! And of course it would be because that's what the directory path is set to! This is presummably what you meant by having to set environment variables and maping volumes from C to the container, and I guess I will have to do that for every import/export directory that my code uses. Thanks for your help @AshotVantsyan! – critor Oct 18 '21 at 10:27