Skip to content

Commit 7eb3be4

Browse files
committed
Refactor auto-injection of PDAL stage factory methods and include the respective options in their docstring
1 parent e568b46 commit 7eb3be4

File tree

3 files changed

+97
-26
lines changed

3 files changed

+97
-26
lines changed

pdal/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
__version__ = "2.4.2"
2-
__all__ = ["Pipeline", "Reader", "Filter", "Writer", "dimensions", "info"]
2+
__all__ = ["Pipeline", "Stage", "Reader", "Filter", "Writer", "dimensions", "info"]
33

4+
from .drivers import inject_pdal_drivers
45
from .libpdalpython import getDimensions, getInfo
5-
from .pipeline import Pipeline, Reader, Filter, Writer
6+
from .pipeline import Filter, Pipeline, Reader, Stage, Writer
67

8+
inject_pdal_drivers()
79
dimensions = getDimensions()
810
info = getInfo()
9-
del getDimensions, getInfo
11+
12+
del inject_pdal_drivers, getDimensions, getInfo

pdal/drivers.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import json
2+
import subprocess
3+
from dataclasses import dataclass, field
4+
from typing import Callable, ClassVar, Mapping, Optional, Sequence, Type
5+
6+
from .pipeline import Filter, Reader, Stage, Writer
7+
8+
9+
@dataclass
10+
class Option:
11+
name: str
12+
description: str
13+
default: Optional[str]
14+
15+
def __repr__(self) -> str:
16+
if self.default is not None:
17+
return f"{self.name}={self.default!r}: {self.description}"
18+
else:
19+
return f"{self.name}: {self.description}"
20+
21+
22+
@dataclass
23+
class Driver:
24+
name: str
25+
short_name: str = field(init=False)
26+
type: Type[Stage] = field(init=False)
27+
description: str
28+
options: Optional[Sequence[Option]]
29+
30+
def __post_init__(self) -> None:
31+
prefix, _, suffix = self.name.partition(".")
32+
self.type = self._prefix_to_type[prefix]
33+
self.short_name = suffix
34+
35+
@property
36+
def factory(self) -> Callable[..., Stage]:
37+
factory: Callable[..., Stage]
38+
if self.type is Reader:
39+
factory = lambda filename, **kwargs: Reader(
40+
filename, type=self.name, **kwargs
41+
)
42+
elif self.type is Writer:
43+
factory = lambda filename=None, **kwargs: Writer(
44+
filename, type=self.name, **kwargs
45+
)
46+
else:
47+
factory = lambda **kwargs: Filter(type=self.name, **kwargs)
48+
factory.__name__ = self.short_name
49+
factory.__qualname__ = f"{self.type.__name__}.{self.short_name}"
50+
factory.__doc__ = self.description
51+
if self.options:
52+
factory.__doc__ += "\n\n"
53+
factory.__doc__ += "\n".join(map(repr, self.options))
54+
return factory
55+
56+
_prefix_to_type: ClassVar[Mapping[str, Type[Stage]]] = {
57+
"readers": Reader,
58+
"filters": Filter,
59+
"writers": Writer,
60+
}
61+
62+
63+
def inject_pdal_drivers() -> None:
64+
drivers = json.loads(
65+
subprocess.run(["pdal", "--drivers", "--showjson"], capture_output=True).stdout
66+
)
67+
options = dict(
68+
json.loads(
69+
subprocess.run(
70+
["pdal", "--options", "all", "--showjson"], capture_output=True
71+
).stdout
72+
)
73+
)
74+
for d in drivers:
75+
name = d["name"]
76+
d_options = options.get(name)
77+
if d_options is not None:
78+
d_options = [
79+
Option(o["name"], o["description"], o.get("default")) for o in d_options
80+
]
81+
# move filename option first
82+
try:
83+
i = next(i for i, opt in enumerate(d_options) if opt.name == "filename")
84+
d_options.insert(0, d_options.pop(i))
85+
except StopIteration:
86+
pass
87+
driver = Driver(name, d["description"], d_options)
88+
setattr(driver.type, driver.short_name, staticmethod(driver.factory))

pdal/pipeline.py

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,13 @@
22

33
import glob
44
import json
5-
import subprocess
65
from abc import ABC, abstractmethod
76
from typing import Any, Container, Dict, Iterator, List, Optional, Sequence, Union, cast
87

98
import numpy as np
109

1110
from . import libpdalpython
1211

13-
_PDAL_DRIVERS = json.loads(
14-
subprocess.run(["pdal", "--drivers", "--showjson"], capture_output=True).stdout
15-
)
16-
1712

1813
class Pipeline(libpdalpython.Pipeline):
1914
def __init__(
@@ -73,13 +68,6 @@ def _json(self) -> str:
7368

7469

7570
class Stage:
76-
def __init_subclass__(cls, type_prefix: Optional[str] = None) -> None:
77-
for driver in _PDAL_DRIVERS:
78-
name = driver["name"]
79-
prefix, _, suffix = name.partition(".")
80-
if prefix == type_prefix:
81-
cls._add_constructor(name, suffix, driver["description"])
82-
8371
def __init__(self, **options: Any):
8472
self._options = options
8573

@@ -106,14 +94,6 @@ def pipeline(self, *arrays: np.ndarray) -> Pipeline:
10694
def __or__(self, other: Union[Stage, Pipeline]) -> Pipeline:
10795
return Pipeline((self, other))
10896

109-
@classmethod
110-
def _add_constructor(cls, type: str, name: str, description: str) -> None:
111-
constructor = lambda cls, *args, **kwargs: cls(*args, **kwargs, type=type)
112-
constructor.__name__ = name
113-
constructor.__qualname__ = f"{cls.__name__}.{name}"
114-
constructor.__doc__ = description
115-
setattr(cls, name, classmethod(constructor))
116-
11797

11898
class InferableTypeStage(ABC, Stage):
11999
@staticmethod
@@ -129,19 +109,19 @@ def type(self) -> str:
129109
return self.infer_type(self._options["filename"])
130110

131111

132-
class Reader(InferableTypeStage, type_prefix="readers"):
112+
class Reader(InferableTypeStage):
133113
infer_type = staticmethod(libpdalpython.infer_reader_driver)
134114

135115
def __init__(self, filename: str, **options: Any):
136116
super().__init__(filename=filename, **options)
137117

138118

139-
class Filter(Stage, type_prefix="filters"):
119+
class Filter(Stage):
140120
def __init__(self, type: str, **options: Any):
141121
super().__init__(type=type, **options)
142122

143123

144-
class Writer(InferableTypeStage, type_prefix="writers"):
124+
class Writer(InferableTypeStage):
145125
infer_type = staticmethod(libpdalpython.infer_writer_driver)
146126

147127
def __init__(self, filename: Optional[str] = None, **options: Any):

0 commit comments

Comments
 (0)