Using a Path object. (Note once OpenLP has been converted to using Path
objects AppLocation.get_section_data_path will return a Path
# Using the Path constructor (If you're creating a Path object from scratch)path=Path(AppLocation.get_section_data_path('themes'),'theme_name')# Creating a new Path object from an existing Path object path=AppLocation.get_section_data_path('themes')/'theme_name'# -- or --path=AppLocation.get_section_data_path('themes').joinpath('theme_name')
The '/' is used to join Paths, or a Path and a string object regardless
if the operating system uses forward or backward slashes.
Nothing special needs to be done when using a Path object as an argument
to the format method of a string.
The Path object is divided in to ConcretePath objects (ones who's
methods access the file system) and PurePath objects (ones who's methods
provide their functionally with out accessing the file system). These
are objects are sub classed to provide the Path object. See the pathlib
documentation for more
These are methods that do not access the file system, consequently,
PurePosixPath can be imported in Windows and PureWindowsPath can be
imported on Posix systems. The same cannot be said for the ConcretePath
name (File / Directory Names)
Used to access the name of the last part of the path (anything after the
filename=os.path.split(self.theme.background_filename)# -- or --filename=os.path.basename(self.theme.background_filename)
When using os.walk, and only expecting results from the source directory
(i.e. no sub directories).
Open the file and read out the text.
song_file=open(self.import_source,'rt',encoding='utf-8-sig')file_content=song_file.read()song_file.close()# -- or --withopen(self.import_source,'rt',encoding='utf-8-sig')assong_file:file_content=song_file.read()
fn=open(notes_file,mode='wt',encoding='utf-8')fn.write(note)fn.close()# -- or --withopen(notes_file,mode='wt',encoding='utf-8')asfn:fn.write(note)
Wrappers and Utility functions
No such thing as a Falsey path
Perhaps the biggest annoyance of the Path object is that the Path object
is assumed to be relative to the current working directory. If its
instantiated with out any arguments, or an empty string, its still a
object with a path relative to the current working directory.
Previously in OpenLP there would be cases where we did things like:
file_name=''# some code ...iffile_name:
We could do this because an empty string is a Falsey value. However all
Path objects are Truthy
To work round this empty path variables should be defined as None. This
leads to extra effort when handling things like QFileDialogs, as they
return an empty string if the user cancels the dialog box. Meaning we
can't just wrap the return value with a Path object. Instead the return
needs evaluating and if equal to a Falsey value we need to return None.
file_name=''# some code ..iffile_name=='':file_path=Noneelse:file_path=Path(file_name)
Of course it goes the other way too. We cannot just call str() on a
variable which stores a Path object, as it could be None, and str(None)
== 'None'. So something like the following is needed.
file_path=None# some code ..iffile_pathisNone:file_name=''else:file_name=str(file_path)
To simplify this I have implemented a version of both the above code
samples as utilities path_to_str and str_to_path.
Path object removes the trailing \
Another feature to look out for is that the Path object removes the
trailing slash. For example:
This kind of makes sense. Drop in to a terminal and try the following
(should work on Windows too)
:~$ cd Documents/:~/Documents$ cd ..:~$ cd Documents:~/Documents$
However this leads to some inconsistencies between the os.path module
and the pathlib module. Here are some (but not exhaustive examples):
a_name='user/desktop'b_name='user/desktop/'a_path=Path(a_name)b_path=Path(b_name)(a_path==b_path)==True# Get the file / directory nameos.path.basename(a_name)=='desktop'os.path.basename(b_name)==''a_path.name=='desktop'# Get the parent directoryos.path.dirname(a_name)=='user'os.path.dirname(b_name)=='user/desktop'a_path.parent==Path('user')
To save a Path in a cross platform way, you should consider using
relative Paths, i.e. relative to the service file, theme file, data
folder and so on.
To facilitate the above a couple modules have been implemented.
This module has been designed with the future in mind, whilst
implementing the minimum required for the current use. With the addition
of a function to register custom objects this module will be able to
en/decode objects that the json standard library cannot. The Path object
has been re-implemented (openlp.core.common.path) to provide methods to
facilitate this. Saving a path is as simple as:
When the json methods are passed with the additional arguments, the
arguments are passed to the json en/decode methods of the custom object.
The custom Path object accepts a 'base_path' parameter, which allows it
to automatically convert the Path to a relative path (if possible) for
storage. The above code then becomes:
See how json_encoded_path is now relative to the base path? Any
relative paths stored in this way will automatically be converted to an
absolute path if a base_path parameter is also supplied when the json
object is decoded:
A 'PathType' (openlp.core.lib.db) has also been created to wrap the json
en/decoding of path objects to allow them to be stored as plain text in
the database. As OpenLP uses 'open' formats such as OpenLyrics to export
data, it is expected that the sqlite databases are kept internal. For
this reason Paths stored using the 'PathType' are made relative to the
data folder. This allows for easy changing of the data path.
For an example of the 'PathType' in use see the song and image pugins'