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.
@@ -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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.