python-jack-knife 0.6.11__tar.gz → 0.6.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/PKG-INFO +1 -1
- python_jack_knife-0.6.12/src/pjk/common.py +174 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/main.py +1 -2
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/man_page.py +9 -11
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/registry.py +94 -64
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/graph.py +2 -2
- python_jack_knife-0.6.12/src/pjk/sinks/graph_bar_line.py +383 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/factory.py +0 -2
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/version.py +1 -1
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/python_jack_knife.egg-info/PKG-INFO +1 -1
- python_jack_knife-0.6.11/src/pjk/common.py +0 -123
- python_jack_knife-0.6.11/src/pjk/sinks/graph_bar_line.py +0 -229
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/LICENSE +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/README.md +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/pyproject.toml +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/setup.cfg +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/__init__.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/components.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/integrations/opensearch_client.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/integrations/opensearch_index_sink.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/integrations/opensearch_query_pipe.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/integrations/postgres_pipe.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/integrations/snowflake_pipe.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/log.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/parser.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/__init__.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/denorm.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/factory.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/filter.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/head.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/join.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/let_reduce.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/map.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/move_field.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/progress_pipe.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/query_pipe.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/remove_field.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/sample.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/select.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/sort.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/tail.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/user_pipe_factory.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/pipes/where.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/progress.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/__init__.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/create_sink.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/csv_sink.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/devnull.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/dir_sink.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/expect.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/factory.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/format_sink.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/graph_cumulative.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/graph_hist.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/graph_scatter.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/json_sink.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/s3_sink.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/s3_stream.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/sinks.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/stdout.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/tsv_sink.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sinks/user_sink_factory.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/__init__.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/configs_source.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/csv_source.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/dir_source.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/favorite_source.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/format_source.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/inline_source.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/json_source.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/lazy_file.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/lazy_file_local.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/lazy_file_s3.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/macro_source.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/npy_source.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/parquet_source.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/s3_source.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/source_list.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/sql_source.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/tsv_source.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/sources/user_source_factory.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/pjk/usage.py +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/python_jack_knife.egg-info/SOURCES.txt +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/python_jack_knife.egg-info/dependency_links.txt +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/python_jack_knife.egg-info/entry_points.txt +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/python_jack_knife.egg-info/requires.txt +0 -0
- {python_jack_knife-0.6.11 → python_jack_knife-0.6.12}/src/python_jack_knife.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright 2024 Mike Schultz
|
|
3
|
+
|
|
4
|
+
import contextlib, io, os, subprocess, sys
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
from abc import ABC
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from pjk.sources.format_source import FormatSource
|
|
10
|
+
from pjk.sinks.format_sink import FormatSink
|
|
11
|
+
from typing import List, Type
|
|
12
|
+
|
|
13
|
+
class SafeNamespace:
|
|
14
|
+
def __init__(self, obj):
|
|
15
|
+
for k, v in obj.items():
|
|
16
|
+
if isinstance(v, dict):
|
|
17
|
+
v = SafeNamespace(v)
|
|
18
|
+
elif isinstance(v, list):
|
|
19
|
+
v = [SafeNamespace(x) if isinstance(x, dict) else x for x in v]
|
|
20
|
+
setattr(self, k, v)
|
|
21
|
+
|
|
22
|
+
def __getattr__(self, key):
|
|
23
|
+
return None # gracefully handle missing keys
|
|
24
|
+
|
|
25
|
+
class ReducingNamespace:
|
|
26
|
+
def __init__(self, record):
|
|
27
|
+
self._record = record
|
|
28
|
+
|
|
29
|
+
def __getattr__(self, name):
|
|
30
|
+
value = self._record[name]
|
|
31
|
+
if isinstance(value, (list, tuple, set)):
|
|
32
|
+
return value
|
|
33
|
+
return [value] # promote scalars to singleton lists
|
|
34
|
+
|
|
35
|
+
# pjk/common.py
|
|
36
|
+
import contextlib, io, os, subprocess, sys
|
|
37
|
+
|
|
38
|
+
@contextlib.contextmanager
|
|
39
|
+
def pager_stdout(use_pager: bool = True):
|
|
40
|
+
"""
|
|
41
|
+
Stream stdout into `less` via a pipe.
|
|
42
|
+
- If stdout is not a TTY or use_pager is False → write directly to sys.stdout.
|
|
43
|
+
- Otherwise spawn `less` and replace sys.stdout with less.stdin.
|
|
44
|
+
"""
|
|
45
|
+
# If not a TTY, paging makes no sense
|
|
46
|
+
if not use_pager or not sys.stdout.isatty():
|
|
47
|
+
yield
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
env = os.environ.copy()
|
|
51
|
+
# -R: pass ANSI; -S: chop long lines; you can add -F/-X to taste
|
|
52
|
+
env.setdefault("LESS", "-R")
|
|
53
|
+
# Ensure UTF-8
|
|
54
|
+
env.setdefault("LESSCHARSET", "utf-8")
|
|
55
|
+
|
|
56
|
+
stdout_orig = sys.stdout
|
|
57
|
+
stderr_orig = sys.stderr
|
|
58
|
+
|
|
59
|
+
# Start less with a *pipe* for stdin and inherit the real terminal for out/err
|
|
60
|
+
pager = subprocess.Popen(
|
|
61
|
+
["less"],
|
|
62
|
+
stdin=subprocess.PIPE,
|
|
63
|
+
stdout=stdout_orig, # keep interactivity
|
|
64
|
+
stderr=stderr_orig,
|
|
65
|
+
env=env,
|
|
66
|
+
close_fds=True,
|
|
67
|
+
bufsize=0, # unbuffered pipe
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Wrap less.stdin as a text writer and swap sys.stdout
|
|
71
|
+
assert pager.stdin is not None
|
|
72
|
+
pager_bin = pager.stdin
|
|
73
|
+
pager_txt = io.TextIOWrapper(pager_bin, encoding="utf-8", write_through=True)
|
|
74
|
+
|
|
75
|
+
sys.stdout = pager_txt
|
|
76
|
+
try:
|
|
77
|
+
yield
|
|
78
|
+
except BrokenPipeError:
|
|
79
|
+
pass
|
|
80
|
+
finally:
|
|
81
|
+
try:
|
|
82
|
+
sys.stdout.flush()
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
85
|
+
# Restore first, then close pager stdin to send EOF
|
|
86
|
+
sys.stdout = stdout_orig
|
|
87
|
+
try:
|
|
88
|
+
pager_txt.flush()
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
try:
|
|
92
|
+
pager_bin.close() # EOF → lets less exit
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
try:
|
|
96
|
+
pager.wait()
|
|
97
|
+
except Exception:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
COLOR_CODES = {
|
|
101
|
+
'bold': '\033[1m',
|
|
102
|
+
'underline': '\033[4m',
|
|
103
|
+
'red': '\033[31m',
|
|
104
|
+
'green': '\033[32m',
|
|
105
|
+
'yellow': '\033[33m',
|
|
106
|
+
'blue': '\033[34m',
|
|
107
|
+
'magenta': '\033[35m',
|
|
108
|
+
'cyan': '\033[36m',
|
|
109
|
+
'gray': '\033[90m',
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
RESET = '\033[0m'
|
|
113
|
+
|
|
114
|
+
def highlight(text: str, color: str = 'bold', value: str = None) -> str:
|
|
115
|
+
value = text if not value else value
|
|
116
|
+
style = COLOR_CODES.get(color.lower(), COLOR_CODES['bold'])
|
|
117
|
+
return text.replace(value, f"{style}{value}{RESET}")
|
|
118
|
+
|
|
119
|
+
# mixin
|
|
120
|
+
class Integration(ABC):
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
class ComponentOrigin(Enum):
|
|
124
|
+
CORE = 0 # core components defined in python-jack-knife
|
|
125
|
+
EXTERNAL = 1 # component loaded via load_package_extras (displayed in either 'integrations' or 'applications')
|
|
126
|
+
USER = 2 # components loaded via load_user_components (always displayed in user_components)
|
|
127
|
+
|
|
128
|
+
class ComponentWrapper:
|
|
129
|
+
def __init__(self, name: str, comp_class, origin: ComponentOrigin):
|
|
130
|
+
self.name = name
|
|
131
|
+
self.comp_class = comp_class
|
|
132
|
+
self.origin = origin
|
|
133
|
+
self.is_integration = issubclass(comp_class, Integration)
|
|
134
|
+
|
|
135
|
+
class ComponentFactory:
|
|
136
|
+
def __init__(self, core_components: dict):
|
|
137
|
+
self.wrappers = {}
|
|
138
|
+
for k, v in core_components.items():
|
|
139
|
+
self.register(k, v, origin=ComponentOrigin.CORE)
|
|
140
|
+
|
|
141
|
+
def register(self, name, comp_class, origin: ComponentOrigin):
|
|
142
|
+
self.wrappers[name] = ComponentWrapper(name, comp_class=comp_class, origin=origin)
|
|
143
|
+
|
|
144
|
+
# is_integration True|False|None=don't care
|
|
145
|
+
def get_components(self, origin_list: List[ComponentOrigin], is_integration: bool) -> dict:
|
|
146
|
+
all = {}
|
|
147
|
+
for wrapper in self.wrappers.values():
|
|
148
|
+
if is_integration is not None:
|
|
149
|
+
if wrapper.is_integration != is_integration:
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
for o in origin_list:
|
|
153
|
+
if wrapper.origin == o:
|
|
154
|
+
all[wrapper.name] = wrapper.comp_class
|
|
155
|
+
|
|
156
|
+
return all
|
|
157
|
+
|
|
158
|
+
def get_component_class(self, name: str):
|
|
159
|
+
wrapper = self.wrappers.get(name, None)
|
|
160
|
+
if not wrapper:
|
|
161
|
+
return None
|
|
162
|
+
return wrapper.comp_class
|
|
163
|
+
|
|
164
|
+
#def get_usage(self, name: str):
|
|
165
|
+
# comp_class = self.get_component_class(name)
|
|
166
|
+
# if not comp_class:
|
|
167
|
+
# return None
|
|
168
|
+
# return comp_class.usage()
|
|
169
|
+
|
|
170
|
+
def create(self, token: str):
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
def is_valid_field_name(name: str):
|
|
174
|
+
return re.fullmatch(r'^[A-Za-z_][A-Za-z0-9_]*$', name)
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import sys
|
|
6
6
|
import os
|
|
7
7
|
import shlex
|
|
8
|
-
import shutil
|
|
9
8
|
from typing import List
|
|
10
9
|
from pjk.parser import ExpressionParser
|
|
11
10
|
from pjk.usage import UsageError
|
|
@@ -80,7 +79,7 @@ def execute(command: str):
|
|
|
80
79
|
|
|
81
80
|
def execute_tokens(tokens: List[str]):
|
|
82
81
|
initialize()
|
|
83
|
-
|
|
82
|
+
|
|
84
83
|
if '--version' in tokens:
|
|
85
84
|
print(f"pjk version {__version__}")
|
|
86
85
|
sys.exit(0)
|
|
@@ -8,7 +8,7 @@ from pjk.parser import ExpressionParser
|
|
|
8
8
|
from pjk.components import Source, Pipe, Sink
|
|
9
9
|
from pjk.usage import Usage, ParsedToken
|
|
10
10
|
from pjk.registry import ComponentRegistry
|
|
11
|
-
from pjk.common import pager_stdout, highlight
|
|
11
|
+
from pjk.common import pager_stdout, highlight, ComponentOrigin
|
|
12
12
|
from contextlib import nullcontext
|
|
13
13
|
|
|
14
14
|
def get_base_class(usage: Usage, as_string: bool = False):
|
|
@@ -49,9 +49,9 @@ def do_man(name: str, registry: ComponentRegistry):
|
|
|
49
49
|
# source and sinks have common names so go through multiple times
|
|
50
50
|
printed = False
|
|
51
51
|
for factory in registry.get_factories():
|
|
52
|
-
|
|
53
|
-
if
|
|
54
|
-
print_man(registry, name, usage)
|
|
52
|
+
comp_class = factory.get_component_class(name)
|
|
53
|
+
if comp_class:
|
|
54
|
+
print_man(registry, name, comp_class.usage())
|
|
55
55
|
printed = True
|
|
56
56
|
|
|
57
57
|
if not printed:
|
|
@@ -61,11 +61,9 @@ def do_all_man(registry: ComponentRegistry, no_pager: bool = True):
|
|
|
61
61
|
cm = nullcontext() if no_pager else pager_stdout()
|
|
62
62
|
with cm:
|
|
63
63
|
for factory in registry.get_factories():
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
usage = factory.get_usage(name)
|
|
68
|
-
print_man(registry, name, usage)
|
|
64
|
+
component_dict = factory.get_components([ComponentOrigin.CORE, ComponentOrigin.EXTERNAL, ComponentOrigin.USER], is_integration=None)
|
|
65
|
+
for name, comp_class in component_dict.items():
|
|
66
|
+
print_man(registry, name, comp_class.usage())
|
|
69
67
|
print()
|
|
70
68
|
|
|
71
69
|
def print_man(registry: ComponentRegistry, name: str, usage: Usage):
|
|
@@ -94,8 +92,8 @@ def do_examples(token:str, registry: ComponentRegistry):
|
|
|
94
92
|
cm = nullcontext() if no_pager else pager_stdout()
|
|
95
93
|
with cm:
|
|
96
94
|
for factory in registry.get_factories():
|
|
97
|
-
|
|
98
|
-
for name, comp_class in
|
|
95
|
+
component_dict = factory.get_components([ComponentOrigin.CORE, ComponentOrigin.EXTERNAL, ComponentOrigin.USER], is_integration=None)
|
|
96
|
+
for name, comp_class in component_dict.items():
|
|
99
97
|
usage = comp_class.usage()
|
|
100
98
|
|
|
101
99
|
comp_type = get_base_class(usage, as_string=True)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
|
+
import time
|
|
6
7
|
from pjk.sinks.factory import SinkFactory
|
|
7
8
|
from pjk.pipes.factory import PipeFactory
|
|
8
9
|
from pjk.sources.factory import SourceFactory
|
|
@@ -11,24 +12,33 @@ from pjk.sources.format_source import FormatSource
|
|
|
11
12
|
import importlib.util
|
|
12
13
|
import importlib
|
|
13
14
|
import importlib.metadata
|
|
15
|
+
import sysconfig, pathlib, importlib
|
|
14
16
|
from pjk.components import Pipe, Source, Sink
|
|
15
|
-
from pjk.common import ComponentFactory, highlight
|
|
16
|
-
from typing import List
|
|
17
|
+
from pjk.common import ComponentFactory, highlight, ComponentOrigin
|
|
18
|
+
from typing import List, Type
|
|
17
19
|
|
|
18
|
-
class
|
|
19
|
-
def __init__(self,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
class ExternalRegistrar:
|
|
21
|
+
def __init__(self, sources: SourceFactory, pipes: PipeFactory, sinks: SinkFactory) -> None:
|
|
22
|
+
self._sources: SourceFactory = sources
|
|
23
|
+
self._pipes: PipeFactory = pipes
|
|
24
|
+
self._sinks: SinkFactory = sinks
|
|
25
|
+
|
|
26
|
+
def source(self, name: str, cls: Type[Source]) -> None:
|
|
27
|
+
self._sources.register(name, cls, origin=ComponentOrigin.EXTERNAL)
|
|
28
|
+
|
|
29
|
+
def pipe(self, name: str, cls: Type[Pipe]) -> None:
|
|
30
|
+
self._pipes.register(name, cls, origin=ComponentOrigin.EXTERNAL)
|
|
31
|
+
|
|
32
|
+
def sink(self, name: str, cls: Type[Sink]) -> None:
|
|
33
|
+
self._sinks.register(name, cls, origin=ComponentOrigin.EXTERNAL)
|
|
23
34
|
|
|
24
35
|
class ComponentRegistry:
|
|
25
36
|
def __init__(self):
|
|
26
37
|
self.source_factory = SourceFactory()
|
|
27
38
|
self.pipe_factory = PipeFactory()
|
|
28
39
|
self.sink_factory = SinkFactory()
|
|
29
|
-
|
|
30
40
|
self.load_user_components()
|
|
31
|
-
load_package_extras()
|
|
41
|
+
self.load_package_extras()
|
|
32
42
|
|
|
33
43
|
def create_source(self, token: str):
|
|
34
44
|
return self.source_factory.create(token)
|
|
@@ -56,9 +66,31 @@ class ComponentRegistry:
|
|
|
56
66
|
print()
|
|
57
67
|
print_factory_core(self.sink_factory, header='sinks')
|
|
58
68
|
|
|
59
|
-
self.
|
|
60
|
-
self.
|
|
69
|
+
self.print_non_core([ComponentOrigin.CORE,ComponentOrigin.EXTERNAL], is_integration=True, header='integrations')
|
|
70
|
+
self.print_non_core([ComponentOrigin.EXTERNAL], is_integration=False, header='apps')
|
|
71
|
+
self.print_non_core([ComponentOrigin.USER], is_integration=None, header='user components (~/.pjk/plugins)')
|
|
72
|
+
|
|
73
|
+
# is_integration = True|False|None None=don't care
|
|
74
|
+
def print_non_core(self, origin_list: List[ComponentOrigin], is_integration: bool, header:str):
|
|
75
|
+
all = {}
|
|
76
|
+
for factory in [self.source_factory, self.pipe_factory, self.sink_factory]:
|
|
77
|
+
component_dict = factory.get_components(origin_list=origin_list, is_integration=is_integration)
|
|
78
|
+
all.update(component_dict)
|
|
79
|
+
|
|
80
|
+
if not all:
|
|
81
|
+
return
|
|
61
82
|
|
|
83
|
+
print()
|
|
84
|
+
print(highlight(header))
|
|
85
|
+
|
|
86
|
+
for name, comp_class in all.items():
|
|
87
|
+
usage = comp_class.usage()
|
|
88
|
+
comp_class_type_str = get_component_type(comp_class)
|
|
89
|
+
lines = usage.desc.split('\n')
|
|
90
|
+
temp = highlight(comp_class_type_str)
|
|
91
|
+
line = f' {name:<17} {temp:<15} {lines[0]}'
|
|
92
|
+
print(line)
|
|
93
|
+
|
|
62
94
|
def load_user_components(self, path=os.path.expanduser("~/.pjk/plugins")):
|
|
63
95
|
if not os.path.isdir(path):
|
|
64
96
|
return
|
|
@@ -87,36 +119,55 @@ class ComponentRegistry:
|
|
|
87
119
|
name = usage.name
|
|
88
120
|
|
|
89
121
|
if is_sink(obj, module):
|
|
90
|
-
self.sink_factory.register(name, obj,
|
|
122
|
+
self.sink_factory.register(name, obj, ComponentOrigin.USER)
|
|
91
123
|
elif is_pipe(obj, module):
|
|
92
|
-
self.pipe_factory.register(name, obj,
|
|
124
|
+
self.pipe_factory.register(name, obj, ComponentOrigin.USER)
|
|
93
125
|
elif is_source(obj, module):
|
|
94
|
-
self.source_factory.register(name, obj,
|
|
126
|
+
self.source_factory.register(name, obj, ComponentOrigin.USER)
|
|
95
127
|
|
|
96
|
-
def
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
128
|
+
def iter_entry_points(self, group: str):
|
|
129
|
+
"""
|
|
130
|
+
Return entry points in the given group across Python 3.9–3.12.
|
|
131
|
+
"""
|
|
132
|
+
eps = importlib.metadata.entry_points()
|
|
133
|
+
return eps.select(group=group) if hasattr(eps, "select") else eps.get(group, [])
|
|
100
134
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
print(highlight(header))
|
|
135
|
+
def load_package_extras(self, group: str = "pjk.package_extras") -> None:
|
|
136
|
+
"""
|
|
137
|
+
Load pip-installed extras and register their components into THIS registry.
|
|
105
138
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
139
|
+
Preferred contract:
|
|
140
|
+
entry point -> callable register(registrar)
|
|
141
|
+
|
|
142
|
+
Fallback (legacy):
|
|
143
|
+
entry point -> module path; we import the MODULE PART ONLY for side-effects.
|
|
144
|
+
"""
|
|
145
|
+
registrar = ExternalRegistrar(self.source_factory, self.pipe_factory, self.sink_factory)
|
|
146
|
+
|
|
147
|
+
for ep in self.iter_entry_points(group):
|
|
148
|
+
try:
|
|
149
|
+
# Try the modern, explicit path first.
|
|
150
|
+
loader = getattr(ep, "load", None)
|
|
151
|
+
if callable(loader):
|
|
152
|
+
target = ep.load() # resolves "module:object" to the actual object
|
|
153
|
+
if callable(target):
|
|
154
|
+
target(registrar) # plugin registers into your live factories
|
|
155
|
+
#print(f"[pjk] loaded extra (callable): {ep.name} -> {ep.value}")
|
|
156
|
+
continue
|
|
157
|
+
# Not callable -> fall through to legacy import
|
|
158
|
+
# Legacy path: import ONLY the module portion before ':' for side-effects
|
|
159
|
+
mod = ep.value.split(":", 1)[0]
|
|
160
|
+
importlib.import_module(mod)
|
|
161
|
+
#print(f"[pjk] loaded extra (import): {ep.name} -> {ep.value}")
|
|
162
|
+
except Exception as e:
|
|
163
|
+
print(f"[pjk] failed to load extra {ep.name}: {e}")
|
|
113
164
|
|
|
114
165
|
def print_core_formats(factories: List[ComponentFactory]):
|
|
115
166
|
print(highlight('formats'))
|
|
116
167
|
formats = set()
|
|
117
168
|
for factory in factories:
|
|
118
|
-
|
|
119
|
-
for name, comp_class in
|
|
169
|
+
component_dict = factory.get_components([ComponentOrigin.CORE], is_integration=False)
|
|
170
|
+
for name, comp_class in component_dict.items():
|
|
120
171
|
if issubclass(comp_class, FormatSink|FormatSource):
|
|
121
172
|
formats.add(name)
|
|
122
173
|
|
|
@@ -124,23 +175,21 @@ def print_core_formats(factories: List[ComponentFactory]):
|
|
|
124
175
|
lst = ', '.join(list(formats))
|
|
125
176
|
print(f'{space:<15}{lst}. (sources/sinks in local files, dirs and s3)')
|
|
126
177
|
|
|
127
|
-
def print_factory_core(factory: ComponentFactory, header: str
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
178
|
+
def print_factory_core(factory: ComponentFactory, header: str):
|
|
179
|
+
component_dict = factory.get_components([ComponentOrigin.CORE], is_integration=False)
|
|
180
|
+
header = highlight(header)
|
|
181
|
+
print(header)
|
|
131
182
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
continue
|
|
183
|
+
# user and outside package components are also here, but printed from registry class
|
|
184
|
+
for name, comp_class in component_dict.items():
|
|
185
|
+
if issubclass(comp_class, FormatSink|FormatSource):
|
|
186
|
+
continue
|
|
137
187
|
|
|
138
|
-
|
|
139
|
-
|
|
188
|
+
usage = comp_class.usage()
|
|
189
|
+
lines = usage.desc.split('\n')
|
|
140
190
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
i += 1
|
|
191
|
+
line = f' {name:<12} {lines[0]}'
|
|
192
|
+
print(line)
|
|
144
193
|
|
|
145
194
|
def get_component_type(component_class) -> str:
|
|
146
195
|
if issubclass(component_class, Sink):
|
|
@@ -178,22 +227,3 @@ def is_sink(obj, module):
|
|
|
178
227
|
and obj.__module__ == module.__name__
|
|
179
228
|
)
|
|
180
229
|
|
|
181
|
-
|
|
182
|
-
def iter_entry_points(group: str):
|
|
183
|
-
eps = importlib.metadata.entry_points()
|
|
184
|
-
if hasattr(eps, "select"):
|
|
185
|
-
# Python 3.10+ (importlib.metadata.EntryPoints)
|
|
186
|
-
return eps.select(group=group)
|
|
187
|
-
# Python 3.9 and older
|
|
188
|
-
return eps.get(group, [])
|
|
189
|
-
|
|
190
|
-
def load_package_extras():
|
|
191
|
-
"""
|
|
192
|
-
Discover and import all installed pjk extras (via entry points).
|
|
193
|
-
"""
|
|
194
|
-
for ep in iter_entry_points("pjk.package_extras"):
|
|
195
|
-
try:
|
|
196
|
-
importlib.import_module(ep.value)
|
|
197
|
-
print(f"[pjk] loaded package extra: {ep.name} -> {ep.value}")
|
|
198
|
-
except Exception as e:
|
|
199
|
-
print(f"[pjk] failed to load extra {ep.name}: {e}")
|
|
@@ -18,8 +18,8 @@ class GraphSink(Sink):
|
|
|
18
18
|
component_class=cls
|
|
19
19
|
)
|
|
20
20
|
usage.def_arg(name='kind', usage='hist|scatter|bar|line|cumulative')
|
|
21
|
-
usage.def_param(name='x', usage='
|
|
22
|
-
usage.def_param(name='y', usage='
|
|
21
|
+
usage.def_param(name='x', usage='x-axis field', default='x')
|
|
22
|
+
usage.def_param(name='y', usage='comma separated list of y-axis fields', default='y')
|
|
23
23
|
usage.def_param(name='pause', usage='Seconds to show graph', is_num=True, default='-1')
|
|
24
24
|
return usage
|
|
25
25
|
|