From 12e019c051dc1830148f0608c434bc3e43c92c51 Mon Sep 17 00:00:00 2001 From: Martin Genet <10851655+mrtgenet@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:15:32 +0100 Subject: [PATCH 1/6] Update filename handling in InferableTypeStage for filespec --- src/pdal/pipeline.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pdal/pipeline.py b/src/pdal/pipeline.py index 60a181c0..42ea251c 100644 --- a/src/pdal/pipeline.py +++ b/src/pdal/pipeline.py @@ -217,7 +217,10 @@ def __or__(self, other: Union[Stage, Pipeline]) -> Pipeline: class InferableTypeStage(Stage): def __init__(self, filename: Optional[str] = None, **options: Any): if filename: - options["filename"] = filename + if isinstance(filename, dict): + options["filename"] = filename.get("path") + else: + options["filename"] = filename super().__init__(**options) @property From 199b695731f8f233d073a5fef3e89c8179df2606 Mon Sep 17 00:00:00 2001 From: Martin Genet <10851655+mrtgenet@users.noreply.github.com> Date: Wed, 28 Jan 2026 15:31:23 +0100 Subject: [PATCH 2/6] Validate 'path' in filename for InferableTypeStage Raise ValueError if 'path' is missing in filename dict --- src/pdal/pipeline.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pdal/pipeline.py b/src/pdal/pipeline.py index 42ea251c..f5b78bb6 100644 --- a/src/pdal/pipeline.py +++ b/src/pdal/pipeline.py @@ -218,7 +218,9 @@ class InferableTypeStage(Stage): def __init__(self, filename: Optional[str] = None, **options: Any): if filename: if isinstance(filename, dict): - options["filename"] = filename.get("path") + if "path" not in filename: + raise ValueError(f"'path' is missing in the provided filespec: {filename}") + options["filename"] = filename["path"] else: options["filename"] = filename super().__init__(**options) From 6a1583678520bf9e6ade9b11e7e73e2bcdc6cfa3 Mon Sep 17 00:00:00 2001 From: Martin Genet <10851655+mrtgenet@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:12:50 +0100 Subject: [PATCH 3/6] Enhance tests for pdal.Reader and pdal.Writer types Added assertions to validate reader and writer types for filespec format. --- test/test_pipeline.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/test_pipeline.py b/test/test_pipeline.py index 46e0a18d..136150c6 100644 --- a/test/test_pipeline.py +++ b/test/test_pipeline.py @@ -3,7 +3,6 @@ import os import sys -from typing import List from itertools import product import numpy as np import pytest @@ -284,6 +283,12 @@ def test_infer_stage_type(self): assert pdal.Writer("foo.xxx").type == "" assert pdal.Reader().type == "" assert pdal.Writer().type == "" + assert pdal.Reader({"path": "foo.las"}).type == "readers.las" + assert pdal.Writer({"path": "foo.las"}).type == "writers.las" + assert pdal.Reader({"path": "foo.xxx"}).type == "" + assert pdal.Writer({"path": "foo.xxx"}).type == "" + assert pdal.Reader({}).type == "" + assert pdal.Writer({}).type == "" def test_streamable(self): """Can we distinguish streamable from non-streamable stages and pipeline""" From 26c1cd94696cf5186210fed990cae5ebc485fb95 Mon Sep 17 00:00:00 2001 From: Howard Butler Date: Wed, 8 Jul 2026 16:33:25 -0500 Subject: [PATCH 4/6] support and test full filespec definitions --- src/pdal/pipeline.py | 16 +++++++++++++--- test/test_pipeline.py | 7 +++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/pdal/pipeline.py b/src/pdal/pipeline.py index f5b78bb6..e00b8c2d 100644 --- a/src/pdal/pipeline.py +++ b/src/pdal/pipeline.py @@ -220,9 +220,11 @@ def __init__(self, filename: Optional[str] = None, **options: Any): if isinstance(filename, dict): if "path" not in filename: raise ValueError(f"'path' is missing in the provided filespec: {filename}") - options["filename"] = filename["path"] - else: options["filename"] = filename + + else: + filespec = {'path':str(filename)} + options["filename"] = filespec super().__init__(**options) @property @@ -231,7 +233,15 @@ def type(self) -> str: return super().type except KeyError: filename = self._options.get("filename") - return str(self._infer_type(filename) if filename else "") + if isinstance(filename, dict): + if "path" not in filename: + raise ValueError(f"'path' is missing in the provided filespec: {filename}") + path = filename.get('path') + else: + path = str(filename) + + + return str(self._infer_type(path) if filename else "") _infer_type = staticmethod(lambda filename: "") diff --git a/test/test_pipeline.py b/test/test_pipeline.py index 136150c6..8760b1b5 100644 --- a/test/test_pipeline.py +++ b/test/test_pipeline.py @@ -290,6 +290,13 @@ def test_infer_stage_type(self): assert pdal.Reader({}).type == "" assert pdal.Writer({}).type == "" + def test_filespec(self): + """Can transit filespecs""" + spec = {'path':'junk.las', 'headers':{'header1':'header_1', 'header2':'header_2'}, 'query':{'query1':'query_1', 'query2':'query_2'}} + assert pdal.Reader(spec).type == "readers.las" + assert pdal.Reader(spec).options['filename']['path'] == "junk.las" + assert pdal.Writer("foo.las").type == "writers.las" + def test_streamable(self): """Can we distinguish streamable from non-streamable stages and pipeline""" rs = pdal.Reader(type="readers.las", filename="foo") From d6d3bfc050fcfa864c184ff42b13bbb7cb2a9cf7 Mon Sep 17 00:00:00 2001 From: Howard Butler Date: Thu, 9 Jul 2026 08:14:59 -0500 Subject: [PATCH 5/6] conditionalize FileSpec support to PDAL 2.9+ --- src/pdal/pipeline.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/pdal/pipeline.py b/src/pdal/pipeline.py index e00b8c2d..4310b232 100644 --- a/src/pdal/pipeline.py +++ b/src/pdal/pipeline.py @@ -33,6 +33,13 @@ LogLevelFromPDAL = {v: k for k, v in LogLevelToPDAL.items()} +HaveFileSpecSupport = False +if libpdalpython.getInfo().major == 2 and \ + libpdalpython.getInfo().minor >= 9 or \ + libpdalpython.getInfo().major > 2: + HaveFileSpecSupport = True + + class Pipeline(libpdalpython.Pipeline): def __init__( self, @@ -220,11 +227,22 @@ def __init__(self, filename: Optional[str] = None, **options: Any): if isinstance(filename, dict): if "path" not in filename: raise ValueError(f"'path' is missing in the provided filespec: {filename}") - options["filename"] = filename + if HaveFileSpecSupport: + options["filename"] = filename + else: + # log that we can't pass FileSpec to PDAL + # because the library version is too old + msg = "PDAL library version is too old for FileSpec support. " \ + "Defaulting to using filename only" + logging.info(msg) + options["filename"] = filename["path"] else: - filespec = {'path':str(filename)} - options["filename"] = filespec + if HaveFileSpecSupport: + filespec = {'path':str(filename)} + options["filename"] = filespec + else: + options["filename"] = filename super().__init__(**options) @property @@ -236,11 +254,14 @@ def type(self) -> str: if isinstance(filename, dict): if "path" not in filename: raise ValueError(f"'path' is missing in the provided filespec: {filename}") - path = filename.get('path') + if HaveFileSpecSupport: + path = filename.get('path') + else: + path = filename + else: path = str(filename) - return str(self._infer_type(path) if filename else "") _infer_type = staticmethod(lambda filename: "") From cec784a8b9eafaf5eca55576f1ce13ef59b6b024 Mon Sep 17 00:00:00 2001 From: Howard Butler Date: Thu, 9 Jul 2026 08:19:38 -0500 Subject: [PATCH 6/6] check headers for FileSpec --- test/test_pipeline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_pipeline.py b/test/test_pipeline.py index 8760b1b5..9bb3ba89 100644 --- a/test/test_pipeline.py +++ b/test/test_pipeline.py @@ -295,6 +295,7 @@ def test_filespec(self): spec = {'path':'junk.las', 'headers':{'header1':'header_1', 'header2':'header_2'}, 'query':{'query1':'query_1', 'query2':'query_2'}} assert pdal.Reader(spec).type == "readers.las" assert pdal.Reader(spec).options['filename']['path'] == "junk.las" + assert pdal.Reader(spec).options['filename']['headers']['header2'] == "header_2" assert pdal.Writer("foo.las").type == "writers.las" def test_streamable(self):