src-py-lib 0.1.0__py3-none-any.whl
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.
- src_py_lib/__init__.py +170 -0
- src_py_lib/clients/__init__.py +3 -0
- src_py_lib/clients/github.py +157 -0
- src_py_lib/clients/google_sheets.py +131 -0
- src_py_lib/clients/graphql.py +476 -0
- src_py_lib/clients/linear.py +101 -0
- src_py_lib/clients/one_password.py +95 -0
- src_py_lib/clients/slack.py +146 -0
- src_py_lib/clients/sourcegraph.py +127 -0
- src_py_lib/py.typed +0 -0
- src_py_lib/utils/__init__.py +3 -0
- src_py_lib/utils/config.py +603 -0
- src_py_lib/utils/http.py +279 -0
- src_py_lib/utils/json_cache.py +42 -0
- src_py_lib/utils/json_types.py +54 -0
- src_py_lib/utils/logging.py +950 -0
- src_py_lib/utils/tsv.py +95 -0
- src_py_lib-0.1.0.dist-info/METADATA +163 -0
- src_py_lib-0.1.0.dist-info/RECORD +21 -0
- src_py_lib-0.1.0.dist-info/WHEEL +4 -0
- src_py_lib-0.1.0.dist-info/licenses/LICENSE +21 -0
src_py_lib/utils/tsv.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Aligned TSV file writing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import unicodedata
|
|
6
|
+
from collections.abc import Iterable, Mapping
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Final
|
|
9
|
+
|
|
10
|
+
DEFAULT_MAX_COLUMN_WIDTH: Final[int] = 100
|
|
11
|
+
_ZERO_WIDTH_CATEGORIES: Final[frozenset[str]] = frozenset({"Cf", "Me", "Mn"})
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def write_tsv(
|
|
15
|
+
path: Path,
|
|
16
|
+
rows: Iterable[Mapping[str, object]],
|
|
17
|
+
*,
|
|
18
|
+
max_column_width: int = DEFAULT_MAX_COLUMN_WIDTH,
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Write rows as a padded TSV table, inferring the header from row keys."""
|
|
21
|
+
data_rows = list(rows)
|
|
22
|
+
fieldnames = list(data_rows[0]) if data_rows else []
|
|
23
|
+
table: list[Mapping[str, object]] = []
|
|
24
|
+
if fieldnames:
|
|
25
|
+
table.append(dict(zip(fieldnames, fieldnames, strict=True)))
|
|
26
|
+
table.extend(data_rows)
|
|
27
|
+
|
|
28
|
+
widths = {
|
|
29
|
+
field: max(
|
|
30
|
+
display_width(
|
|
31
|
+
format_tsv_value(
|
|
32
|
+
row.get(field, ""),
|
|
33
|
+
field,
|
|
34
|
+
max_column_width=max_column_width,
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
for row in table
|
|
38
|
+
)
|
|
39
|
+
for field in fieldnames
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
with path.open("w", encoding="utf-8") as file:
|
|
44
|
+
for row in table:
|
|
45
|
+
values = [
|
|
46
|
+
pad_display(
|
|
47
|
+
format_tsv_value(
|
|
48
|
+
row.get(field, ""),
|
|
49
|
+
field,
|
|
50
|
+
max_column_width=max_column_width,
|
|
51
|
+
),
|
|
52
|
+
widths[field],
|
|
53
|
+
)
|
|
54
|
+
for field in fieldnames
|
|
55
|
+
]
|
|
56
|
+
file.write("\t".join(values) + "\n")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def format_tsv_value(
|
|
60
|
+
value: object,
|
|
61
|
+
field: str,
|
|
62
|
+
*,
|
|
63
|
+
max_column_width: int = DEFAULT_MAX_COLUMN_WIDTH,
|
|
64
|
+
) -> str:
|
|
65
|
+
"""Return a single-line TSV cell value, truncating non-URL fields."""
|
|
66
|
+
text = str(value).replace("\r", " ").replace("\n", " ").replace("\t", " ")
|
|
67
|
+
if field == "url" or field.endswith("_url"):
|
|
68
|
+
return text
|
|
69
|
+
return text[:max_column_width]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def display_width(value: str) -> int:
|
|
73
|
+
"""Return terminal display width for padding aligned text columns."""
|
|
74
|
+
width = 0
|
|
75
|
+
for character in value:
|
|
76
|
+
if unicodedata.combining(character):
|
|
77
|
+
continue
|
|
78
|
+
if unicodedata.category(character) in _ZERO_WIDTH_CATEGORIES:
|
|
79
|
+
continue
|
|
80
|
+
width += 2 if unicodedata.east_asian_width(character) in {"F", "W"} else 1
|
|
81
|
+
return width
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def pad_display(value: str, width: int) -> str:
|
|
85
|
+
"""Pad text to a target display width."""
|
|
86
|
+
return value + " " * max(width - display_width(value), 0)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
__all__ = [
|
|
90
|
+
"DEFAULT_MAX_COLUMN_WIDTH",
|
|
91
|
+
"display_width",
|
|
92
|
+
"format_tsv_value",
|
|
93
|
+
"pad_display",
|
|
94
|
+
"write_tsv",
|
|
95
|
+
]
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: src-py-lib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reusable Python helpers for Sourcegraph projects
|
|
5
|
+
Project-URL: Homepage, https://github.com/sourcegraph/src-py-lib
|
|
6
|
+
Project-URL: Issues, https://github.com/sourcegraph/src-py-lib/issues
|
|
7
|
+
Author: Sourcegraph
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: Sourcegraph
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: httpx<1,>=0.28
|
|
20
|
+
Requires-Dist: pydantic<3,>=2
|
|
21
|
+
Requires-Dist: python-dotenv<2,>=1.2
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# src-py-lib
|
|
25
|
+
|
|
26
|
+
Reusable libraries for Sourcegraph-adjacent Python projects
|
|
27
|
+
|
|
28
|
+
This repo is the shared implementation layer for patterns which get
|
|
29
|
+
rebuilt in separate scripts: API clients, HTTP retries/timeouts, structured logging,
|
|
30
|
+
etc.
|
|
31
|
+
|
|
32
|
+
## Experimental - This is not a supported Sourcegraph product
|
|
33
|
+
|
|
34
|
+
This repo was created for Sourcegraph Implementation Engineering deployments,
|
|
35
|
+
and is not intended, designed, built, or supported for use in any other scenario.
|
|
36
|
+
Feel free to open issues or PRs, but responses are best effort.
|
|
37
|
+
|
|
38
|
+
## Install from another project
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
uv add git+https://github.com/sourcegraph/src-py-lib.git
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## What is included
|
|
45
|
+
|
|
46
|
+
- `src_py_lib.utils.logging` — centralized human stderr logs plus optional structured
|
|
47
|
+
JSONL events, run IDs, git commit metadata, context fields, event timing,
|
|
48
|
+
retention, startup metadata, and sanitized config snapshots.
|
|
49
|
+
- `src_py_lib.utils.config` — Pydantic-backed `Config` models loaded from code
|
|
50
|
+
defaults, `python-dotenv` `.env` parsing, shell environment, and CLI
|
|
51
|
+
overrides, with typed values, required checks, safe snapshots, and `op://...`
|
|
52
|
+
reference resolution.
|
|
53
|
+
- `src_py_lib.utils.http` — pooled `httpx` JSON HTTP client with a shared
|
|
54
|
+
30-second timeout, retry policy, `Retry-After` support, and contextual errors.
|
|
55
|
+
- `src_py_lib.utils.tsv` — padded TSV writer for human-readable tabular exports,
|
|
56
|
+
with newline/tab cleanup, URL preservation, and Unicode-aware column widths.
|
|
57
|
+
- `src_py_lib.clients.graphql` — shared GraphQL execution with automatic cursor
|
|
58
|
+
pagination, batched alias lookups, and schema introspection export.
|
|
59
|
+
- `src_py_lib.clients.sourcegraph` — Sourcegraph GraphQL client with token
|
|
60
|
+
validation, endpoint normalization, connection streaming, and shared config
|
|
61
|
+
fields for `SRC_ENDPOINT` (default: `https://sourcegraph.com`) and
|
|
62
|
+
`SRC_ACCESS_TOKEN`.
|
|
63
|
+
- `src_py_lib.clients.linear` — Linear GraphQL client with automatic cursor
|
|
64
|
+
handling, token validation, shared config fields, and injectable HTTP policy.
|
|
65
|
+
- `src_py_lib.clients.slack` — Slack Web API client with token validation,
|
|
66
|
+
cursor pagination, and method pacing. Consider `slack_sdk` if usage grows
|
|
67
|
+
beyond simple GET, pagination, and rate-limit handling.
|
|
68
|
+
- `src_py_lib.clients.github` — GitHub GraphQL client, PR URL parsing, and
|
|
69
|
+
batched PR lookups, with token validation. Defaults to `https://github.com`;
|
|
70
|
+
pass `github_url` for GitHub Enterprise Server. Keep lightweight for GraphQL;
|
|
71
|
+
GitHub SDKs help more for REST.
|
|
72
|
+
- `src_py_lib.clients.one_password` — tiny 1Password CLI wrapper for signing in,
|
|
73
|
+
validating authenticated `op` access, and resolving `op://...` references after config loading.
|
|
74
|
+
- `src_py_lib.clients.google_sheets` — Google Sheets API primitives with
|
|
75
|
+
spreadsheet access validation using gcloud Application Default Credentials or
|
|
76
|
+
a provided access token. Prefer Google's official libraries if Sheets usage
|
|
77
|
+
grows beyond small primitives, because auth, quota project, token refresh,
|
|
78
|
+
batching, and error shapes are subtle.
|
|
79
|
+
|
|
80
|
+
Prefer this library for shared logging, HTTP policy, and thin API wrappers.
|
|
81
|
+
Prefer vendor SDKs when they replace tricky auth, token refresh, retries,
|
|
82
|
+
pagination, quota behavior, or complex request models.
|
|
83
|
+
|
|
84
|
+
## Example
|
|
85
|
+
|
|
86
|
+
Define one project-specific `Config` model, then load it once at CLI startup.
|
|
87
|
+
For common CLI and client usage, import the curated root API:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from pathlib import Path
|
|
91
|
+
|
|
92
|
+
import src_py_lib as src
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class LinearExportConfig(src.LinearClientConfig):
|
|
96
|
+
output_dir: Path = src.config_field(
|
|
97
|
+
default=Path("."),
|
|
98
|
+
env_var="LINEAR_EXPORT_OUTPUT_DIR",
|
|
99
|
+
cli_flag="--output-dir",
|
|
100
|
+
metavar="PATH",
|
|
101
|
+
help="Directory for generated files.",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
config = src.parse_args(LinearExportConfig, description="Export Linear data.")
|
|
105
|
+
client = src.linear_client_from_config(config)
|
|
106
|
+
print(f"Writing files under {config.output_dir}")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Config precedence is: code defaults, `.env`, shell environment, then CLI
|
|
110
|
+
overrides. API client modules can provide shared Config base classes such as
|
|
111
|
+
`LinearClientConfig`, and `parse_args` resolves `op://...` references by
|
|
112
|
+
default. `config_field(default=...)` supports aliases, store-true /
|
|
113
|
+
store-false command flags, optional values, numeric bounds, and string patterns
|
|
114
|
+
for simple CLIs. Pass a custom `argparse.ArgumentParser` to `parse_args` only when you
|
|
115
|
+
need parsing beyond Config fields. Help text preserves description and
|
|
116
|
+
argument-help newlines, and reserves enough option-column width for long config
|
|
117
|
+
flags. Mark sensitive fields with `secret=True` so snapshots do not expose
|
|
118
|
+
resolved values.
|
|
119
|
+
|
|
120
|
+
## Logging example
|
|
121
|
+
|
|
122
|
+
Configure logging once at process startup. Prefer configuring the root logger
|
|
123
|
+
(`logger_name=""`, the default) so project modules and shared `src_py_lib` modules
|
|
124
|
+
such as `src_py_lib.utils.http` are captured by the same terminal and JSONL handlers.
|
|
125
|
+
Use `logging()` in CLIs to configure logging, add the command field to all
|
|
126
|
+
structured events, and emit standard run/startup/run-end metadata.
|
|
127
|
+
Use `debug()`, `info()`, `warning()`, `error()`, and `critical()` for one-off
|
|
128
|
+
structured events. Use `event()` blocks around timed work; they emit `trace`,
|
|
129
|
+
`span`, and nested `parent_span` fields. Use `start_level="debug"` to hide
|
|
130
|
+
noisy start events while keeping end timing visible, and
|
|
131
|
+
`omit_success_status=True` for very high-volume success events. Use `stage()`
|
|
132
|
+
for workflow context such as `stage="apply"`.
|
|
133
|
+
When the root logger is configured, noisy `httpx`/`httpcore` records are suppressed;
|
|
134
|
+
`HTTPClient` emits structured `http_request` events instead.
|
|
135
|
+
Run-end events include HTTP attempt/byte/status/retry counters. Set
|
|
136
|
+
`LoggingSettings.resource_sample_interval_seconds` to emit DEBUG
|
|
137
|
+
`resource_sample` events and include process resource totals on run end. Set
|
|
138
|
+
`SRC_LOG_LEVEL=INFO` for a run to omit DEBUG events from the log file.
|
|
139
|
+
`LoggingConfig` includes `--verbose/-v`, `--quiet/-q`, and `--silent/-s`
|
|
140
|
+
shortcuts (also available as `SRC_LOG_VERBOSE`, `SRC_LOG_QUIET`, and
|
|
141
|
+
`SRC_LOG_SILENT`). Use `logging_settings_from_config()` to build
|
|
142
|
+
`LoggingSettings` from those conventions.
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
import src_py_lib as src
|
|
146
|
+
|
|
147
|
+
with src.logging({"src_token": "provided"}):
|
|
148
|
+
src.info("sync_started", repository_count=3)
|
|
149
|
+
|
|
150
|
+
client = src.SourcegraphClient("https://sourcegraph.example.com", "token")
|
|
151
|
+
data = client.graphql("query Viewer { currentUser { username } }")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Development
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
uv sync
|
|
158
|
+
uv run ruff format .
|
|
159
|
+
uv run ruff check .
|
|
160
|
+
uv run pyright
|
|
161
|
+
uv run python -m unittest discover -s tests
|
|
162
|
+
npx --yes markdownlint-cli2
|
|
163
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
src_py_lib/__init__.py,sha256=RRUHFF4TtGAkwN_m_IGXDiDEqE9THi9z9sHLiYMnT6s,3958
|
|
2
|
+
src_py_lib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
src_py_lib/clients/__init__.py,sha256=Ux21Bu2_NNFklyF4M7iYzMUM4kTRcNiVIrjpz3hxpbM,103
|
|
4
|
+
src_py_lib/clients/github.py,sha256=F3ZLVs69_8_1_RvQXCBZeklKsYtA4P7qD_v3TFPM4WA,5202
|
|
5
|
+
src_py_lib/clients/google_sheets.py,sha256=jBEjODZsQDiZ-z2aM_Vld7kFy8TNvxcIe_qr89e9nvY,4481
|
|
6
|
+
src_py_lib/clients/graphql.py,sha256=z9ekJXmVwocVAUkDSBZXJEOsIBzVGjynhqpXXXmvp04,14940
|
|
7
|
+
src_py_lib/clients/linear.py,sha256=z5eAdDGfaiGaBD8WhF06vmuvUmnWqpVfqT6aLY-YV5U,2780
|
|
8
|
+
src_py_lib/clients/one_password.py,sha256=EvAcpMpUVpF4Bk1Y-uFLQPx8AlFbFPheWoOr9q4asG0,3666
|
|
9
|
+
src_py_lib/clients/slack.py,sha256=lemaTSFQpDzAKhS7e7U1JNU3pnw2kZlrX_WOYYcTMog,5271
|
|
10
|
+
src_py_lib/clients/sourcegraph.py,sha256=osn93J0i42FEhqPoDUyqjtUfOZ5Bnh96wF_SMRTqgzU,4304
|
|
11
|
+
src_py_lib/utils/__init__.py,sha256=TcJJo8jlZgN09c4z90QBHoEPO8US9RYkFLsR46igZ7c,107
|
|
12
|
+
src_py_lib/utils/config.py,sha256=hHB-pDFmYzPJRXVpQnxPyTQR8fO4rFGJb4cnusDqQ-8,21406
|
|
13
|
+
src_py_lib/utils/http.py,sha256=0Q7ZmbHch6dnuMoUOnAoqQ-qLHzV7QpWlhBVvbHmcKk,10606
|
|
14
|
+
src_py_lib/utils/json_cache.py,sha256=LfbOIKGdY1Ug5Hj81jG6cvdh4cY6R4FNbgsQloG6kq4,1327
|
|
15
|
+
src_py_lib/utils/json_types.py,sha256=0V8Ukd3fB9X6VVcatHAxjTdXjuTqxAlzUWgAQQVG6lQ,1993
|
|
16
|
+
src_py_lib/utils/logging.py,sha256=a-4hdfYKf8qmvXcOlZZTeHuicqcN6N7fnRfldmVVs6A,32667
|
|
17
|
+
src_py_lib/utils/tsv.py,sha256=hXz2xwZL-8v-iiD66P8WrPWPno3_5Ns-n2ysi3-Ce4Y,2723
|
|
18
|
+
src_py_lib-0.1.0.dist-info/METADATA,sha256=QMZ57HM9i3tE8uAPWsvCgRXNAPvlK3losUKXLyCr0bw,7334
|
|
19
|
+
src_py_lib-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
20
|
+
src_py_lib-0.1.0.dist-info/licenses/LICENSE,sha256=FCl6gJG7mu3Vdm030o3A0oG-_L60JJSlkc9f2T0rSGI,1068
|
|
21
|
+
src_py_lib-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sourcegraph
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|