Summary
base_path, written into the Archive Run Manifest and consumed by the
OpenLineage integration to build file:// source locations, is nothing
more than os.getcwd() captured at whatever moment a Metadata object
first accessed the property. It has no defined relationship to the
archive, the run, or the paths it is meant to help resolve. When the
configured input paths are absolute, which is the normal case for a
long-running deployment such as FlightPath Server, using base_path to
build a lineage URL produces a broken, doubled-up path. The same code even
treats the same kind of path two different ways a few lines apart.
Where it comes from
csvpath/managers/metadata.py:51-55, the base Metadata class inherited
by every metadata type:
#
# find base dir so we can add file:// refs, if needed
#
@property
def base_path(self):
if self._base_path is None:
self._base_path = os.getcwd()
return self._base_path
Lazily captured once per Metadata instance, the first time base_path
is read. Nothing ties it to archive_path, to the manifest own location,
or to any project root. It is simply wherever the process happened to be
running from at that moment.
Written to the filesystem manifest only in
csvpath/managers/run/run_registrar.py:48
(m["base_path"] = mdata.base_path, the Archive Run Manifest), though the
property is inherited and available on every metadata type, and is also
sent to the SQL integration tables for files and paths listeners
(csvpath/managers/integrations/sql/sql_file_listener.py:40,
csvpath/managers/integrations/sql/sql_paths_listener.py:35).
What it is actually used for
csvpath/managers/integrations/ol/job.py, the OpenLineage integration,
uses it to build sourceCodeLocation facets:
# build_file_job, line 56
location = f"file:////{mdata.base_path}{os.sep}{mdata.file_path}"
# build_paths_job, line 75
location = f"file:////{mdata.base_path}{os.sep}{mdata.group_file_path}"
This only produces a correct path when file_path / group_file_path are
relative to the process working directory at the exact moment base_path
was captured.
Why it is broken
file_path and group_file_path come from [inputs] files and
[inputs] csvpaths in config.ini (the same keys behind file_home and
named_paths_home). Those keys can be configured as either relative or
absolute paths, and are documented elsewhere in this review as being
absolute under a deployment such as FlightPath Server. Whenever they are
absolute, prepending base_path does not produce a valid path, it
produces an absolute path glued onto the front of another absolute path.
The same file is not even internally consistent about this assumption.
A few lines after building the file:// URL, build_paths_job opens
group_file_path directly, with no base_path prefix at all
(csvpath/managers/integrations/ol/job.py:84-86):
qp = f"{mdata.group_file_path}"
q = ""
with open(qp, "r", encoding="utf-8") as qf:
q = qf.read()
That only works if group_file_path is directly openable from the current
process working directory, which contradicts the assumption behind the
file:// URL built two lines earlier for the same path.
Separately, since base_path is just os.getcwd() of whichever process
happens to be writing the manifest at the time, in a long-running,
multi-project server process it reflects wherever that process was
launched from, not anything about the specific run, archive, or project.
The value can be effectively arbitrary noise from the point of view of the
manifest reader.
Why this matters beyond OpenLineage
OpenLineage itself is a narrow integration, but lineage correctness is a
core concern: a wrong file:// location silently produces incorrect
provenance data for every file-stage and paths-load job reported to a
lineage consumer, with no error or warning anywhere in the pipeline. This
is worse than an unused or redundant manifest field, it can actively
misinform anything downstream that trusts the lineage record.
Suggested fix direction
Stop deriving OpenLineage source locations from a bare process
os.getcwd() snapshot. Likely directions:
- Resolve
file_path / group_file_path to an absolute path once, at the
point they are first captured (using Nos, consistent with how the rest
of the codebase normalizes paths across backends), so nothing downstream
needs a separate base-path prefix to make sense of them, or
- If a base directory concept is still wanted for relative paths, derive it
from something stable and meaningful, for example the config-resolved
project root, rather than the ambient process working directory.
Either way, job.py build_file_job and build_paths_job should stop
special-casing file_path/group_file_path two different ways (once with
a base_path prefix, once by opening the raw path directly) and treat
them consistently.
Found while reviewing manifest fields for the references v3 work.
Summary
base_path, written into the Archive Run Manifest and consumed by theOpenLineage integration to build
file://source locations, is nothingmore than
os.getcwd()captured at whatever moment aMetadataobjectfirst accessed the property. It has no defined relationship to the
archive, the run, or the paths it is meant to help resolve. When the
configured input paths are absolute, which is the normal case for a
long-running deployment such as FlightPath Server, using
base_pathtobuild a lineage URL produces a broken, doubled-up path. The same code even
treats the same kind of path two different ways a few lines apart.
Where it comes from
csvpath/managers/metadata.py:51-55, the baseMetadataclass inheritedby every metadata type:
Lazily captured once per
Metadatainstance, the first timebase_pathis read. Nothing ties it to
archive_path, to the manifest own location,or to any project root. It is simply wherever the process happened to be
running from at that moment.
Written to the filesystem manifest only in
csvpath/managers/run/run_registrar.py:48(
m["base_path"] = mdata.base_path, the Archive Run Manifest), though theproperty is inherited and available on every metadata type, and is also
sent to the SQL integration tables for files and paths listeners
(
csvpath/managers/integrations/sql/sql_file_listener.py:40,csvpath/managers/integrations/sql/sql_paths_listener.py:35).What it is actually used for
csvpath/managers/integrations/ol/job.py, the OpenLineage integration,uses it to build
sourceCodeLocationfacets:This only produces a correct path when
file_path/group_file_patharerelative to the process working directory at the exact moment
base_pathwas captured.
Why it is broken
file_pathandgroup_file_pathcome from[inputs] filesand[inputs] csvpathsinconfig.ini(the same keys behindfile_homeandnamed_paths_home). Those keys can be configured as either relative orabsolute paths, and are documented elsewhere in this review as being
absolute under a deployment such as FlightPath Server. Whenever they are
absolute, prepending
base_pathdoes not produce a valid path, itproduces an absolute path glued onto the front of another absolute path.
The same file is not even internally consistent about this assumption.
A few lines after building the
file://URL,build_paths_jobopensgroup_file_pathdirectly, with nobase_pathprefix at all(
csvpath/managers/integrations/ol/job.py:84-86):That only works if
group_file_pathis directly openable from the currentprocess working directory, which contradicts the assumption behind the
file://URL built two lines earlier for the same path.Separately, since
base_pathis justos.getcwd()of whichever processhappens to be writing the manifest at the time, in a long-running,
multi-project server process it reflects wherever that process was
launched from, not anything about the specific run, archive, or project.
The value can be effectively arbitrary noise from the point of view of the
manifest reader.
Why this matters beyond OpenLineage
OpenLineage itself is a narrow integration, but lineage correctness is a
core concern: a wrong
file://location silently produces incorrectprovenance data for every file-stage and paths-load job reported to a
lineage consumer, with no error or warning anywhere in the pipeline. This
is worse than an unused or redundant manifest field, it can actively
misinform anything downstream that trusts the lineage record.
Suggested fix direction
Stop deriving OpenLineage source locations from a bare process
os.getcwd()snapshot. Likely directions:file_path/group_file_pathto an absolute path once, at thepoint they are first captured (using
Nos, consistent with how the restof the codebase normalizes paths across backends), so nothing downstream
needs a separate base-path prefix to make sense of them, or
from something stable and meaningful, for example the config-resolved
project root, rather than the ambient process working directory.
Either way,
job.pybuild_file_job and build_paths_job should stopspecial-casing
file_path/group_file_pathtwo different ways (once witha
base_pathprefix, once by opening the raw path directly) and treatthem consistently.
Found while reviewing manifest fields for the references v3 work.