sgn-skig 0.1.0__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.
Files changed (42) hide show
  1. sgn_skig-0.1.0/.gitignore +107 -0
  2. sgn_skig-0.1.0/.gitlab-ci.yml +23 -0
  3. sgn_skig-0.1.0/Makefile +30 -0
  4. sgn_skig-0.1.0/PKG-INFO +20 -0
  5. sgn_skig-0.1.0/README.md +231 -0
  6. sgn_skig-0.1.0/pyproject.toml +49 -0
  7. sgn_skig-0.1.0/setup.cfg +4 -0
  8. sgn_skig-0.1.0/src/sgn_skig.egg-info/PKG-INFO +20 -0
  9. sgn_skig-0.1.0/src/sgn_skig.egg-info/SOURCES.txt +40 -0
  10. sgn_skig-0.1.0/src/sgn_skig.egg-info/dependency_links.txt +1 -0
  11. sgn_skig-0.1.0/src/sgn_skig.egg-info/entry_points.txt +3 -0
  12. sgn_skig-0.1.0/src/sgn_skig.egg-info/requires.txt +16 -0
  13. sgn_skig-0.1.0/src/sgn_skig.egg-info/top_level.txt +1 -0
  14. sgn_skig-0.1.0/src/sgneskig/__init__.py +14 -0
  15. sgn_skig-0.1.0/src/sgneskig/_version.py +34 -0
  16. sgn_skig-0.1.0/src/sgneskig/bin/__init__.py +1 -0
  17. sgn_skig-0.1.0/src/sgneskig/bin/grafana_datasource.py +94 -0
  18. sgn_skig-0.1.0/src/sgneskig/bin/grafana_import.py +107 -0
  19. sgn_skig-0.1.0/src/sgneskig/metrics/__init__.py +27 -0
  20. sgn_skig-0.1.0/src/sgneskig/metrics/collector.py +637 -0
  21. sgn_skig-0.1.0/src/sgneskig/metrics/grafana.py +854 -0
  22. sgn_skig-0.1.0/src/sgneskig/metrics/writer.py +328 -0
  23. sgn_skig-0.1.0/src/sgneskig/pipeline.py +512 -0
  24. sgn_skig-0.1.0/src/sgneskig/sinks/__init__.py +6 -0
  25. sgn_skig-0.1.0/src/sgneskig/sinks/kafka_sink.py +205 -0
  26. sgn_skig-0.1.0/src/sgneskig/sinks/scald_metrics_sink.py +489 -0
  27. sgn_skig-0.1.0/src/sgneskig/sources/__init__.py +5 -0
  28. sgn_skig-0.1.0/src/sgneskig/sources/kafka_source.py +284 -0
  29. sgn_skig-0.1.0/src/sgneskig/transforms/__init__.py +7 -0
  30. sgn_skig-0.1.0/src/sgneskig/transforms/delay_buffer.py +180 -0
  31. sgn_skig-0.1.0/src/sgneskig/transforms/event_latency.py +159 -0
  32. sgn_skig-0.1.0/src/sgneskig/transforms/round_robin_distributor.py +133 -0
  33. sgn_skig-0.1.0/tests/__init__.py +0 -0
  34. sgn_skig-0.1.0/tests/test_bin_scripts.py +369 -0
  35. sgn_skig-0.1.0/tests/test_collector.py +715 -0
  36. sgn_skig-0.1.0/tests/test_grafana.py +789 -0
  37. sgn_skig-0.1.0/tests/test_imports.py +29 -0
  38. sgn_skig-0.1.0/tests/test_pipeline.py +565 -0
  39. sgn_skig-0.1.0/tests/test_sinks.py +935 -0
  40. sgn_skig-0.1.0/tests/test_sources.py +520 -0
  41. sgn_skig-0.1.0/tests/test_transforms.py +469 -0
  42. sgn_skig-0.1.0/tests/test_writer.py +599 -0
@@ -0,0 +1,107 @@
1
+ # Auto-generated version file
2
+ src/sgneskig/_version.py
3
+
4
+ # Byte-compiled / optimized / DLL files
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+
9
+ # C extensions
10
+ *.so
11
+
12
+ # Distribution / packaging
13
+ .Python
14
+ build/
15
+ develop-eggs/
16
+ dist/
17
+ downloads/
18
+ eggs/
19
+ .eggs/
20
+ lib/
21
+ lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ wheels/
26
+ *.egg-info/
27
+ .installed.cfg
28
+ *.egg
29
+
30
+ # PyInstaller
31
+ *.manifest
32
+ *.spec
33
+
34
+ # Installer logs
35
+ pip-log.txt
36
+ pip-delete-this-directory.txt
37
+
38
+ # Unit test / coverage reports
39
+ htmlcov/
40
+ .tox/
41
+ .nox/
42
+ .coverage
43
+ .coverage.*
44
+ .cache
45
+ nosetests.xml
46
+ coverage.xml
47
+ *.cover
48
+ *.py,cover
49
+ .hypothesis/
50
+ .pytest_cache/
51
+
52
+ # Translations
53
+ *.mo
54
+ *.pot
55
+
56
+ # Environments
57
+ .env
58
+ .venv
59
+ env/
60
+ venv/
61
+ ENV/
62
+ env.bak/
63
+ venv.bak/
64
+
65
+ # Spyder project settings
66
+ .spyderproject
67
+ .spyproject
68
+
69
+ # Rope project settings
70
+ .ropeproject
71
+
72
+ # mkdocs documentation
73
+ /site
74
+
75
+ # mypy
76
+ .mypy_cache/
77
+ .dmypy.json
78
+ dmypy.json
79
+
80
+ # Pyre type checker
81
+ .pyre/
82
+
83
+ # pytype static type analyzer
84
+ .pytype/
85
+
86
+ # Cython debug symbols
87
+ cython_debug/
88
+
89
+ # IDEs
90
+ .idea/
91
+ .vscode/
92
+ *.swp
93
+ *.swo
94
+ *~
95
+
96
+ # macOS
97
+ .DS_Store
98
+
99
+ # Jupyter Notebook
100
+ .ipynb_checkpoints
101
+
102
+ # Secrets / credentials
103
+ *.pem
104
+ *.key
105
+ credentials.json
106
+ secrets.yaml
107
+ .env.local
@@ -0,0 +1,23 @@
1
+ include:
2
+ - component: git.ligo.org/computing/gitlab/components/python/sdist@1
3
+ - component: git.ligo.org/computing/gitlab/components/python/wheel@1
4
+ - component: git.ligo.org/computing/gitlab/components/python/code-quality@1
5
+ inputs:
6
+ requirements: ".[lint]"
7
+ - component: git.ligo.org/computing/gitlab/components/python/dependency-scanning@1
8
+ - component: git.ligo.org/computing/gitlab/components/python/test@1
9
+ inputs:
10
+ install_extra: test
11
+ python_versions:
12
+ - "3.11"
13
+ - "3.12"
14
+ - "3.13"
15
+
16
+ check_makefile:
17
+ image: python:3.11
18
+ stage: test
19
+ script:
20
+ - apt-get update
21
+ - apt-get install make
22
+ - pip3 install ".[dev]"
23
+ - make all
@@ -0,0 +1,30 @@
1
+ .PHONY: help all install test lint format clean
2
+
3
+ all: format lint test
4
+
5
+ help:
6
+ @echo "Available targets:"
7
+ @echo " all Run format, lint, and test (in order)"
8
+ @echo " install Install package in development mode"
9
+ @echo " test Run tests with coverage"
10
+ @echo " lint Run ruff linter"
11
+ @echo " format Format code with ruff"
12
+ @echo " clean Remove build artifacts"
13
+
14
+ install:
15
+ pip install -e ".[dev]"
16
+
17
+ test:
18
+ pytest
19
+
20
+ lint:
21
+ ruff check src/ tests/
22
+
23
+ format:
24
+ ruff format src/ tests/
25
+ ruff check --fix src/ tests/
26
+
27
+ clean:
28
+ rm -rf build/ dist/ *.egg-info src/*.egg-info
29
+ find . -type d -name __pycache__ -exec rm -rf {} +
30
+ find . -type f -name "*.pyc" -delete
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: sgn-skig
3
+ Version: 0.1.0
4
+ Summary: SGN infrastructure for metrics, Kafka, InfluxDB, and Grafana integration
5
+ Author-email: Chad Hanna <crh184@psu.edu>
6
+ License: MPL-2.0
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: sgn>=0.5
9
+ Requires-Dist: confluent-kafka>=2.0
10
+ Requires-Dist: ligo-scald
11
+ Requires-Dist: pyyaml
12
+ Requires-Dist: requests
13
+ Provides-Extra: test
14
+ Requires-Dist: pytest; extra == "test"
15
+ Requires-Dist: pytest-cov; extra == "test"
16
+ Requires-Dist: pytest-asyncio; extra == "test"
17
+ Provides-Extra: lint
18
+ Requires-Dist: ruff; extra == "lint"
19
+ Provides-Extra: dev
20
+ Requires-Dist: sgn-skig[lint,test]; extra == "dev"
@@ -0,0 +1,231 @@
1
+ # sgn-skig
2
+
3
+ SGN infrastructure for **S**cald, **K**afka, **I**nfluxDB, and **G**rafana.
4
+
5
+ Provides reusable metrics collection, pipeline infrastructure, and generic elements for [SGN](https://git.ligo.org/cbc/sgn) pipelines.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install sgn-skig
11
+ ```
12
+
13
+ Or for development:
14
+
15
+ ```bash
16
+ pip install -e ".[dev]"
17
+ ```
18
+
19
+ ## Features
20
+
21
+ ### MetricsPipeline
22
+
23
+ A drop-in replacement for `sgn.Pipeline` that adds automatic metrics infrastructure:
24
+
25
+ ```python
26
+ from sgneskig import MetricsPipeline
27
+
28
+ pipeline = MetricsPipeline(
29
+ name="my-pipeline",
30
+ influxdb_host="localhost",
31
+ influxdb_port=8086,
32
+ influxdb_db="my_metrics",
33
+ )
34
+
35
+ # Insert elements - metrics are automatically configured
36
+ pipeline.insert(source, transform, sink)
37
+ pipeline.connect(source, transform)
38
+ pipeline.connect(transform, sink)
39
+
40
+ # Export Grafana dashboard from discovered metrics
41
+ pipeline.export_grafana_dashboard("dashboard.json")
42
+ ```
43
+
44
+ ### MetricsCollectorMixin
45
+
46
+ Add metrics collection to any SGN element:
47
+
48
+ ```python
49
+ from dataclasses import dataclass
50
+ from sgn.base import TransformElement
51
+ from sgneskig.metrics import MetricsCollectorMixin, metrics
52
+
53
+ @dataclass
54
+ class MyTransform(TransformElement, MetricsCollectorMixin):
55
+ # Declarative metrics schema - auto-discovered by MetricsPipeline
56
+ metrics_schema = metrics([
57
+ ("events_processed", "counter", ["pipeline"], "Events processed"),
58
+ ("process_time", "timing", [], "Processing duration"),
59
+ ])
60
+
61
+ # Enable automatic cycle time tracking
62
+ track_elapsed_time: bool = True
63
+ elapsed_metric_name: str = "my_transform_elapsed"
64
+
65
+ def __post_init__(self):
66
+ super().__post_init__()
67
+ self._init_metrics()
68
+
69
+ def internal(self):
70
+ with self.time_operation("process_time"):
71
+ # ... do work ...
72
+ pass
73
+ self.increment_counter("events_processed", tags={"pipeline": "SGNL"})
74
+ ```
75
+
76
+ ### Kafka Integration
77
+
78
+ **KafkaSource** - Read from Kafka topics with automatic batching:
79
+
80
+ ```python
81
+ from sgneskig.sources import KafkaSource
82
+
83
+ source = KafkaSource(
84
+ name="kafka_source",
85
+ bootstrap_servers="localhost:9092",
86
+ topics=["events"],
87
+ group_id="my-consumer",
88
+ auto_offset_reset="latest",
89
+ )
90
+ ```
91
+
92
+ **KafkaSink** - Write to Kafka topics:
93
+
94
+ ```python
95
+ from sgneskig.sinks import KafkaSink
96
+
97
+ sink = KafkaSink(
98
+ name="kafka_sink",
99
+ bootstrap_servers="localhost:9092",
100
+ topic="output-events",
101
+ )
102
+ ```
103
+
104
+ ### Generic Transforms
105
+
106
+ **DelayBuffer** - Buffer events for a configurable delay:
107
+
108
+ ```python
109
+ from sgneskig.transforms import DelayBuffer
110
+
111
+ buffer = DelayBuffer(
112
+ name="delay_buffer",
113
+ delay_seconds=30.0, # Hold events for 30s
114
+ )
115
+ ```
116
+
117
+ **EventLatency** - Measure end-to-end latency:
118
+
119
+ ```python
120
+ from sgneskig.transforms import EventLatency
121
+
122
+ latency = EventLatency(
123
+ name="latency_tracker",
124
+ timestamp_field="gpstime", # Field containing event timestamp
125
+ )
126
+ ```
127
+
128
+ **RoundRobinDistributor** - Distribute work across multiple outputs:
129
+
130
+ ```python
131
+ from sgneskig.transforms import RoundRobinDistributor
132
+
133
+ distributor = RoundRobinDistributor(
134
+ name="distributor",
135
+ num_outputs=4, # Creates output pads: out_0, out_1, out_2, out_3
136
+ )
137
+ ```
138
+
139
+ ### Grafana Integration
140
+
141
+ Generate dashboards and datasources from your pipeline's metrics:
142
+
143
+ ```python
144
+ # After inserting elements into the pipeline
145
+ pipeline.export_grafana_dashboard("grafana/dashboards/my-pipeline.json")
146
+ pipeline.export_grafana_datasource("grafana/provisioning/datasources/influxdb.yaml")
147
+
148
+ # Or get the exporter for more control
149
+ exporter = pipeline.get_grafana_exporter(dashboard_title="My Pipeline Metrics")
150
+ print(exporter.datasource_curl_command())
151
+ ```
152
+
153
+ ## CLI Tools
154
+
155
+ ### sgn-grafana-datasource
156
+
157
+ Generate Grafana datasource provisioning YAML:
158
+
159
+ ```bash
160
+ sgn-grafana-datasource --host localhost --port 8086 --db my_metrics > datasource.yaml
161
+ ```
162
+
163
+ ### sgn-grafana-import
164
+
165
+ Import a dashboard JSON file into Grafana:
166
+
167
+ ```bash
168
+ sgn-grafana-import dashboard.json --grafana-url http://localhost:3000 --auth admin:admin
169
+ ```
170
+
171
+ ## Architecture
172
+
173
+ ```
174
+ ┌─────────────────────────────────────────────────────────────────┐
175
+ │ MetricsPipeline │
176
+ │ ┌──────────────────────────────────────────────────────────┐ │
177
+ │ │ MetricsWriter │ │
178
+ │ │ (shared writer for direct InfluxDB writes via ligo.scald)│ │
179
+ │ └──────────────────────────────────────────────────────────┘ │
180
+ │ ▲ ▲ ▲ │
181
+ │ │ │ │ │
182
+ │ ┌────────┴───────┐ ┌───────┴────────┐ ┌───────┴───────┐ │
183
+ │ │ KafkaSource │──▶│ MyTransform │──▶│ KafkaSink │ │
184
+ │ │ (metrics via │ │ (metrics via │ │ (metrics via │ │
185
+ │ │ mixin) │ │ mixin) │ │ mixin) │ │
186
+ │ └────────────────┘ └────────────────┘ └───────────────┘ │
187
+ └─────────────────────────────────────────────────────────────────┘
188
+ │
189
+ ▼
190
+ ┌────────────┐
191
+ │ InfluxDB │
192
+ │ (1s, 10s, │
193
+ │ 100s, ... │
194
+ │ retention) │
195
+ └────────────┘
196
+ │
197
+ ▼
198
+ ┌────────────┐
199
+ │ Grafana │
200
+ └────────────┘
201
+ ```
202
+
203
+ ## Metrics Storage
204
+
205
+ sgn-skig uses [ligo.scald](https://lscsoft.docs.ligo.org/scald/) for multi-resolution time series storage. Data is automatically aggregated at multiple resolutions:
206
+
207
+ - `1s` - 1 second buckets
208
+ - `10s` - 10 second buckets
209
+ - `100s` - ~1.5 minute buckets
210
+ - `1000s` - ~16 minute buckets
211
+ - `10000s` - ~2.7 hour buckets
212
+ - `100000s` - ~1 day buckets
213
+
214
+ Retention policies are created automatically on first write.
215
+
216
+ ## Development
217
+
218
+ ```bash
219
+ # Run tests
220
+ make test
221
+
222
+ # Run linter
223
+ make lint
224
+
225
+ # Run all checks
226
+ make
227
+ ```
228
+
229
+ ## License
230
+
231
+ MPL-2.0
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = [
3
+ "setuptools>=42",
4
+ "setuptools_scm[toml]>=3.4.3",
5
+ "wheel",
6
+ ]
7
+ build-backend = "setuptools.build_meta"
8
+
9
+ [project]
10
+ name = "sgn-skig"
11
+ dynamic = ["version"]
12
+ description = "SGN infrastructure for metrics, Kafka, InfluxDB, and Grafana integration"
13
+ authors = [{ name = "Chad Hanna", email = "crh184@psu.edu" }]
14
+ license = { text = "MPL-2.0" }
15
+ requires-python = ">= 3.11"
16
+ dependencies = [
17
+ "sgn >= 0.5",
18
+ "confluent-kafka >= 2.0",
19
+ "ligo-scald",
20
+ "pyyaml",
21
+ "requests",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ test = ["pytest", "pytest-cov", "pytest-asyncio"]
26
+ lint = ["ruff"]
27
+ dev = ["sgn-skig[test,lint]"]
28
+
29
+ [project.scripts]
30
+ sgn-grafana-datasource = "sgneskig.bin.grafana_datasource:main"
31
+ sgn-grafana-import = "sgneskig.bin.grafana_import:main"
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["src"]
35
+
36
+ [tool.setuptools_scm]
37
+ write_to = "src/sgneskig/_version.py"
38
+
39
+ [tool.ruff]
40
+ line-length = 88
41
+ target-version = "py311"
42
+
43
+ [tool.ruff.lint]
44
+ select = ["E", "F", "W", "I", "UP", "S", "B", "C4"]
45
+ ignore = ["S101", "S310", "S311", "S603", "S608"]
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]
49
+ addopts = "--cov=sgneskig --cov-report=term-missing"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: sgn-skig
3
+ Version: 0.1.0
4
+ Summary: SGN infrastructure for metrics, Kafka, InfluxDB, and Grafana integration
5
+ Author-email: Chad Hanna <crh184@psu.edu>
6
+ License: MPL-2.0
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: sgn>=0.5
9
+ Requires-Dist: confluent-kafka>=2.0
10
+ Requires-Dist: ligo-scald
11
+ Requires-Dist: pyyaml
12
+ Requires-Dist: requests
13
+ Provides-Extra: test
14
+ Requires-Dist: pytest; extra == "test"
15
+ Requires-Dist: pytest-cov; extra == "test"
16
+ Requires-Dist: pytest-asyncio; extra == "test"
17
+ Provides-Extra: lint
18
+ Requires-Dist: ruff; extra == "lint"
19
+ Provides-Extra: dev
20
+ Requires-Dist: sgn-skig[lint,test]; extra == "dev"
@@ -0,0 +1,40 @@
1
+ .gitignore
2
+ .gitlab-ci.yml
3
+ Makefile
4
+ README.md
5
+ pyproject.toml
6
+ src/sgn_skig.egg-info/PKG-INFO
7
+ src/sgn_skig.egg-info/SOURCES.txt
8
+ src/sgn_skig.egg-info/dependency_links.txt
9
+ src/sgn_skig.egg-info/entry_points.txt
10
+ src/sgn_skig.egg-info/requires.txt
11
+ src/sgn_skig.egg-info/top_level.txt
12
+ src/sgneskig/__init__.py
13
+ src/sgneskig/_version.py
14
+ src/sgneskig/pipeline.py
15
+ src/sgneskig/bin/__init__.py
16
+ src/sgneskig/bin/grafana_datasource.py
17
+ src/sgneskig/bin/grafana_import.py
18
+ src/sgneskig/metrics/__init__.py
19
+ src/sgneskig/metrics/collector.py
20
+ src/sgneskig/metrics/grafana.py
21
+ src/sgneskig/metrics/writer.py
22
+ src/sgneskig/sinks/__init__.py
23
+ src/sgneskig/sinks/kafka_sink.py
24
+ src/sgneskig/sinks/scald_metrics_sink.py
25
+ src/sgneskig/sources/__init__.py
26
+ src/sgneskig/sources/kafka_source.py
27
+ src/sgneskig/transforms/__init__.py
28
+ src/sgneskig/transforms/delay_buffer.py
29
+ src/sgneskig/transforms/event_latency.py
30
+ src/sgneskig/transforms/round_robin_distributor.py
31
+ tests/__init__.py
32
+ tests/test_bin_scripts.py
33
+ tests/test_collector.py
34
+ tests/test_grafana.py
35
+ tests/test_imports.py
36
+ tests/test_pipeline.py
37
+ tests/test_sinks.py
38
+ tests/test_sources.py
39
+ tests/test_transforms.py
40
+ tests/test_writer.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ sgn-grafana-datasource = sgneskig.bin.grafana_datasource:main
3
+ sgn-grafana-import = sgneskig.bin.grafana_import:main
@@ -0,0 +1,16 @@
1
+ sgn>=0.5
2
+ confluent-kafka>=2.0
3
+ ligo-scald
4
+ pyyaml
5
+ requests
6
+
7
+ [dev]
8
+ sgn-skig[lint,test]
9
+
10
+ [lint]
11
+ ruff
12
+
13
+ [test]
14
+ pytest
15
+ pytest-cov
16
+ pytest-asyncio
@@ -0,0 +1 @@
1
+ sgneskig
@@ -0,0 +1,14 @@
1
+ """SGN-SKIG: SGN infrastructure for Scald, Kafka, InfluxDB, and Grafana.
2
+
3
+ Provides reusable metrics collection, pipeline infrastructure, and
4
+ generic transforms/sinks/sources for SGN pipelines.
5
+ """
6
+
7
+ from sgneskig.pipeline import MetricsPipeline
8
+
9
+ try:
10
+ from sgneskig._version import __version__
11
+ except ModuleNotFoundError:
12
+ __version__ = "0.0.0"
13
+
14
+ __all__ = ["MetricsPipeline", "__version__"]
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '0.1.0'
32
+ __version_tuple__ = version_tuple = (0, 1, 0)
33
+
34
+ __commit_id__ = commit_id = 'gc8f50f74a'
@@ -0,0 +1 @@
1
+ """Command-line tools for sgn-skig."""