sgalchemy 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.
- sgalchemy/__init__.py +101 -0
- sgalchemy/__main__.py +8 -0
- sgalchemy/_cli.py +343 -0
- sgalchemy/_core/__init__.py +1 -0
- sgalchemy/_core/_async_backend.py +575 -0
- sgalchemy/_core/_async_engine.py +595 -0
- sgalchemy/_core/_compiler.py +622 -0
- sgalchemy/_core/_construction.py +277 -0
- sgalchemy/_core/_engine.py +618 -0
- sgalchemy/_core/_engine_common.py +192 -0
- sgalchemy/_core/_entity.py +55 -0
- sgalchemy/_core/_expressions.py +756 -0
- sgalchemy/_core/_options.py +240 -0
- sgalchemy/_core/_registry.py +1168 -0
- sgalchemy/_core/_result.py +41 -0
- sgalchemy/_core/_schema_drift.py +27 -0
- sgalchemy/_core/_statement.py +204 -0
- sgalchemy/_downstream.py +44 -0
- sgalchemy/_errors.py +95 -0
- sgalchemy/_orm/__init__.py +34 -0
- sgalchemy/_orm/_async_loaders.py +176 -0
- sgalchemy/_orm/_async_scoping.py +376 -0
- sgalchemy/_orm/_async_session.py +1334 -0
- sgalchemy/_orm/_loaders.py +333 -0
- sgalchemy/_orm/_scope_common.py +55 -0
- sgalchemy/_orm/_scoping.py +506 -0
- sgalchemy/_orm/_session.py +1028 -0
- sgalchemy/_orm/_session_common.py +51 -0
- sgalchemy/_orm/_state.py +718 -0
- sgalchemy/_orm/_uow.py +2137 -0
- sgalchemy/_shotgun.py +370 -0
- sgalchemy/_stubgen.py +381 -0
- sgalchemy/_stubgen_registry.py +162 -0
- sgalchemy/entities/__init__.py +34 -0
- sgalchemy/py.typed +0 -0
- sgalchemy/schema/__init__.py +7 -0
- sgalchemy/schema/_aliases.py +89 -0
- sgalchemy/schema/_cache.py +190 -0
- sgalchemy/schema/_cachedir.py +82 -0
- sgalchemy/schema/_model.py +132 -0
- sgalchemy/schema/_policy.py +34 -0
- sgalchemy/testing/__init__.py +227 -0
- sgalchemy-0.1.0.dist-info/METADATA +147 -0
- sgalchemy-0.1.0.dist-info/RECORD +47 -0
- sgalchemy-0.1.0.dist-info/WHEEL +4 -0
- sgalchemy-0.1.0.dist-info/entry_points.txt +2 -0
- sgalchemy-0.1.0.dist-info/licenses/LICENSE +21 -0
sgalchemy/__init__.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Build reflected queries and manage explicit entity reads and writes.
|
|
2
|
+
|
|
3
|
+
Create an engine, call its `load_schema()` method, and obtain reflected classes
|
|
4
|
+
from `engine.entities`. Async engines require awaited schema preparation.
|
|
5
|
+
Queries never autoflush staged changes; call `Session.commit()` or await
|
|
6
|
+
`AsyncSession.commit()` explicitly.
|
|
7
|
+
|
|
8
|
+
Factories use the process default Registry unless an explicit Registry or
|
|
9
|
+
`bind_default=False` selects isolation. Importing this package installs the
|
|
10
|
+
synchronous `Entity.find()` callback; `connect()` configures its default
|
|
11
|
+
session. Importing the package performs no provider or schema-cache I/O.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from sgalchemy._core._async_engine import AsyncEngine, create_async_engine
|
|
15
|
+
from sgalchemy._core._engine import Engine, create_engine
|
|
16
|
+
from sgalchemy._core._expressions import FieldExpr, and_, func, or_, raw
|
|
17
|
+
from sgalchemy._core._options import defer, joinedload, lazyload, load_only, selectinload
|
|
18
|
+
from sgalchemy._core._registry import (
|
|
19
|
+
Entity,
|
|
20
|
+
InspectionInfo,
|
|
21
|
+
Registry,
|
|
22
|
+
default_registry,
|
|
23
|
+
inspect,
|
|
24
|
+
set_default_registry,
|
|
25
|
+
)
|
|
26
|
+
from sgalchemy._core._result import Result, Row
|
|
27
|
+
from sgalchemy._core._statement import Select, select
|
|
28
|
+
from sgalchemy._errors import (
|
|
29
|
+
CommitError,
|
|
30
|
+
CompilationError,
|
|
31
|
+
RefreshRequiredError,
|
|
32
|
+
RelationshipTargetUnavailableError,
|
|
33
|
+
RetiredEntityWriteError,
|
|
34
|
+
SchemaUnavailableError,
|
|
35
|
+
SgalchemyError,
|
|
36
|
+
StaleObjectError,
|
|
37
|
+
UnfetchedFieldError,
|
|
38
|
+
)
|
|
39
|
+
from sgalchemy._orm import (
|
|
40
|
+
AsyncScopedSession,
|
|
41
|
+
AsyncSession,
|
|
42
|
+
AsyncSessionFactory,
|
|
43
|
+
ScopedSession,
|
|
44
|
+
Session,
|
|
45
|
+
SessionFactory,
|
|
46
|
+
async_scoped_session,
|
|
47
|
+
async_sessionmaker,
|
|
48
|
+
connect,
|
|
49
|
+
scoped_session,
|
|
50
|
+
sessionmaker,
|
|
51
|
+
)
|
|
52
|
+
from sgalchemy._shotgun import AsyncShotgunLike, ShotgunLike
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
'AsyncEngine',
|
|
56
|
+
'AsyncScopedSession',
|
|
57
|
+
'AsyncSession',
|
|
58
|
+
'AsyncSessionFactory',
|
|
59
|
+
'AsyncShotgunLike',
|
|
60
|
+
'CommitError',
|
|
61
|
+
'CompilationError',
|
|
62
|
+
'Engine',
|
|
63
|
+
'Entity',
|
|
64
|
+
'FieldExpr',
|
|
65
|
+
'InspectionInfo',
|
|
66
|
+
'RefreshRequiredError',
|
|
67
|
+
'Registry',
|
|
68
|
+
'RelationshipTargetUnavailableError',
|
|
69
|
+
'Result',
|
|
70
|
+
'RetiredEntityWriteError',
|
|
71
|
+
'Row',
|
|
72
|
+
'SchemaUnavailableError',
|
|
73
|
+
'ScopedSession',
|
|
74
|
+
'Select',
|
|
75
|
+
'Session',
|
|
76
|
+
'SessionFactory',
|
|
77
|
+
'SgalchemyError',
|
|
78
|
+
'ShotgunLike',
|
|
79
|
+
'StaleObjectError',
|
|
80
|
+
'UnfetchedFieldError',
|
|
81
|
+
'and_',
|
|
82
|
+
'async_scoped_session',
|
|
83
|
+
'async_sessionmaker',
|
|
84
|
+
'connect',
|
|
85
|
+
'create_async_engine',
|
|
86
|
+
'create_engine',
|
|
87
|
+
'default_registry',
|
|
88
|
+
'defer',
|
|
89
|
+
'func',
|
|
90
|
+
'inspect',
|
|
91
|
+
'joinedload',
|
|
92
|
+
'lazyload',
|
|
93
|
+
'load_only',
|
|
94
|
+
'or_',
|
|
95
|
+
'raw',
|
|
96
|
+
'scoped_session',
|
|
97
|
+
'select',
|
|
98
|
+
'selectinload',
|
|
99
|
+
'sessionmaker',
|
|
100
|
+
'set_default_registry',
|
|
101
|
+
]
|
sgalchemy/__main__.py
ADDED
sgalchemy/_cli.py
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""Command-line interface for schema caches and generated stubs."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import getpass
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
import warnings
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from sgalchemy import _stubgen
|
|
13
|
+
from sgalchemy._core._engine import Engine, create_engine
|
|
14
|
+
from sgalchemy._downstream import write_stdout
|
|
15
|
+
from sgalchemy._errors import SgalchemyError
|
|
16
|
+
from sgalchemy.schema._cache import SchemaCache, load_cache
|
|
17
|
+
from sgalchemy.schema._cachedir import cache_file
|
|
18
|
+
|
|
19
|
+
_COMMAND_GROUPS: tuple[tuple[str, tuple[tuple[str, str], ...]], ...] = (
|
|
20
|
+
(
|
|
21
|
+
'commands',
|
|
22
|
+
(
|
|
23
|
+
('stubs', 'generate or check entity type stubs'),
|
|
24
|
+
('schema', 'manage the cached schema'),
|
|
25
|
+
),
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
_COMMAND_HELP = {name: text for _title, commands in _COMMAND_GROUPS for name, text in commands}
|
|
29
|
+
_CONTROL_CHARACTERS = re.compile(r'[\x00-\x1f\x7f-\x9f]')
|
|
30
|
+
|
|
31
|
+
_MISSING_PROVIDER = (
|
|
32
|
+
'no SG API provider is installed; install sgalchemy with the official, api, or async extra'
|
|
33
|
+
)
|
|
34
|
+
_PROVIDER_FAILURE_NAMES = frozenset(
|
|
35
|
+
{'HttpLib2Error', 'ProtocolError', 'ResponseError', 'ShotgunError'}
|
|
36
|
+
)
|
|
37
|
+
_PROVIDER_MODULE = 'shotgun_api3'
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _CommandError(Exception):
|
|
41
|
+
"""Carry one anticipated failure message and its exit status."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, message: str, status: int) -> None:
|
|
44
|
+
"""Record the message and the exit status it maps to."""
|
|
45
|
+
super().__init__(message)
|
|
46
|
+
self._status = status
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class _RootParser(argparse.ArgumentParser):
|
|
50
|
+
"""Render options first and top-level commands under titled groups."""
|
|
51
|
+
|
|
52
|
+
def format_help(self, formatter: argparse.HelpFormatter | None = None) -> str:
|
|
53
|
+
"""Return help with options first and commands in titled groups."""
|
|
54
|
+
if formatter is None:
|
|
55
|
+
formatter = self._get_formatter()
|
|
56
|
+
formatter.add_usage(self.usage, self._actions, self._mutually_exclusive_groups)
|
|
57
|
+
formatter.add_text(self.description)
|
|
58
|
+
formatter.start_section('options')
|
|
59
|
+
formatter.add_arguments(self._get_optional_actions())
|
|
60
|
+
formatter.end_section()
|
|
61
|
+
for title, commands in _COMMAND_GROUPS:
|
|
62
|
+
formatter.start_section(title)
|
|
63
|
+
formatter.add_arguments(
|
|
64
|
+
[argparse.Action([], name, help=text) for name, text in commands]
|
|
65
|
+
)
|
|
66
|
+
formatter.end_section()
|
|
67
|
+
formatter.add_text(self.epilog)
|
|
68
|
+
return formatter.format_help()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
72
|
+
"""Build the command-line argument parser."""
|
|
73
|
+
parser = _RootParser(
|
|
74
|
+
prog='sgalchemy',
|
|
75
|
+
description='Generate typed entity stubs and manage cached schemas for SG sites.',
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument('--version', action='store_true', help='show the version and exit')
|
|
78
|
+
commands = parser.add_subparsers(
|
|
79
|
+
dest='command', metavar='COMMAND', parser_class=argparse.ArgumentParser
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
stubs = commands.add_parser(
|
|
83
|
+
'stubs',
|
|
84
|
+
help=_COMMAND_HELP['stubs'],
|
|
85
|
+
description='Generate or check type stubs for the entity classes in a site schema.',
|
|
86
|
+
epilog=(
|
|
87
|
+
'With --site, the API key comes from SGALCHEMY_API_KEY or a hidden terminal prompt.'
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
stubs.set_defaults(command_parser=stubs)
|
|
91
|
+
output = stubs.add_argument_group('output')
|
|
92
|
+
output.add_argument(
|
|
93
|
+
'-o',
|
|
94
|
+
'--out',
|
|
95
|
+
type=Path,
|
|
96
|
+
required=True,
|
|
97
|
+
metavar='DIR',
|
|
98
|
+
help='root directory for the generated stubs',
|
|
99
|
+
)
|
|
100
|
+
output.add_argument(
|
|
101
|
+
'--check',
|
|
102
|
+
action='store_true',
|
|
103
|
+
help='compare stubs with the schema without writing files',
|
|
104
|
+
)
|
|
105
|
+
source = stubs.add_argument_group('schema source')
|
|
106
|
+
source.add_argument(
|
|
107
|
+
'--from-cache',
|
|
108
|
+
type=Path,
|
|
109
|
+
metavar='FILE',
|
|
110
|
+
help='read the schema from a saved cache file',
|
|
111
|
+
)
|
|
112
|
+
source.add_argument('--site', metavar='URL', help='site URL to read the schema from')
|
|
113
|
+
source.add_argument('--script-name', metavar='NAME', help='script name to authenticate with')
|
|
114
|
+
content = stubs.add_argument_group('stub content')
|
|
115
|
+
content.add_argument(
|
|
116
|
+
'--no-entity-aliases',
|
|
117
|
+
action='store_false',
|
|
118
|
+
dest='entity_aliases',
|
|
119
|
+
help='omit display-name entity aliases',
|
|
120
|
+
)
|
|
121
|
+
content.add_argument(
|
|
122
|
+
'--no-field-aliases',
|
|
123
|
+
action='store_false',
|
|
124
|
+
dest='field_aliases',
|
|
125
|
+
help='omit field aliases with the sg_ prefix removed',
|
|
126
|
+
)
|
|
127
|
+
content.add_argument(
|
|
128
|
+
'--permissive',
|
|
129
|
+
action='store_true',
|
|
130
|
+
help='allow unknown attributes in generated classes',
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
schema = commands.add_parser(
|
|
134
|
+
'schema',
|
|
135
|
+
help=_COMMAND_HELP['schema'],
|
|
136
|
+
description='Manage the cached schema of an SG site.',
|
|
137
|
+
)
|
|
138
|
+
schema_commands = schema.add_subparsers(
|
|
139
|
+
title='commands', dest='schema_command', metavar='COMMAND', required=True
|
|
140
|
+
)
|
|
141
|
+
refresh = schema_commands.add_parser(
|
|
142
|
+
'refresh',
|
|
143
|
+
help='fetch the schema now and rewrite its cache file',
|
|
144
|
+
description='Fetch the site schema now and rewrite its cache file.',
|
|
145
|
+
epilog='The API key comes from SGALCHEMY_API_KEY or a hidden terminal prompt.',
|
|
146
|
+
)
|
|
147
|
+
refresh.set_defaults(command_parser=refresh)
|
|
148
|
+
refresh.add_argument(
|
|
149
|
+
'--site', required=True, metavar='URL', help='site URL whose schema to fetch'
|
|
150
|
+
)
|
|
151
|
+
refresh.add_argument(
|
|
152
|
+
'--script-name', required=True, metavar='NAME', help='script name to authenticate with'
|
|
153
|
+
)
|
|
154
|
+
return parser
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _describe_failure(error: Exception) -> str:
|
|
158
|
+
"""Return an exception's message, or its type name when the message is empty."""
|
|
159
|
+
return str(error) or type(error).__name__
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _is_provider_failure(error: Exception) -> bool:
|
|
163
|
+
"""Report whether an exception is a provider, network, or schema-cache failure."""
|
|
164
|
+
return isinstance(error, (OSError, SgalchemyError)) or any(
|
|
165
|
+
cls.__name__ in _PROVIDER_FAILURE_NAMES for cls in type(error).__mro__
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _load_site_schema(engine: Engine, *, force: bool) -> None:
|
|
170
|
+
"""Load the site schema, mapping a missing provider and provider failures."""
|
|
171
|
+
try:
|
|
172
|
+
engine.load_schema(force=force)
|
|
173
|
+
except ModuleNotFoundError as error:
|
|
174
|
+
if error.name != _PROVIDER_MODULE:
|
|
175
|
+
raise
|
|
176
|
+
raise _CommandError(_MISSING_PROVIDER, 2) from error
|
|
177
|
+
except Exception as error:
|
|
178
|
+
if not _is_provider_failure(error):
|
|
179
|
+
raise
|
|
180
|
+
raise _CommandError(
|
|
181
|
+
f'could not load the site schema: {_describe_failure(error)}', 1
|
|
182
|
+
) from error
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _read_api_key(parser: argparse.ArgumentParser) -> str:
|
|
186
|
+
"""Read an environment secret or require a hidden terminal prompt."""
|
|
187
|
+
key = os.environ.get('SGALCHEMY_API_KEY')
|
|
188
|
+
if key:
|
|
189
|
+
return key
|
|
190
|
+
if not sys.stdin.isatty():
|
|
191
|
+
parser.error('set SGALCHEMY_API_KEY or run interactively to enter an API key')
|
|
192
|
+
try:
|
|
193
|
+
with warnings.catch_warnings():
|
|
194
|
+
warnings.simplefilter('error', getpass.GetPassWarning)
|
|
195
|
+
key = getpass.getpass('API key: ')
|
|
196
|
+
except (EOFError, getpass.GetPassWarning):
|
|
197
|
+
parser.error('could not read a hidden API key; set SGALCHEMY_API_KEY')
|
|
198
|
+
if not key:
|
|
199
|
+
parser.error('the entered API key is empty; set SGALCHEMY_API_KEY or enter a key')
|
|
200
|
+
return key
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _render_version() -> tuple[int, str]:
|
|
204
|
+
"""Return the installed distribution version without other work."""
|
|
205
|
+
import importlib.metadata # noqa: PLC0415
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
version = importlib.metadata.version('sgalchemy')
|
|
209
|
+
except importlib.metadata.PackageNotFoundError:
|
|
210
|
+
print(
|
|
211
|
+
'error: installed distribution metadata for sgalchemy is unavailable',
|
|
212
|
+
file=sys.stderr,
|
|
213
|
+
)
|
|
214
|
+
return 2, ''
|
|
215
|
+
return 0, f'sgalchemy {version}\n'
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _report_error(message: str) -> None:
|
|
219
|
+
"""Print one terminal-safe error line to standard error."""
|
|
220
|
+
visible = _CONTROL_CHARACTERS.sub(lambda match: f'\\x{ord(match.group()):02x}', message)
|
|
221
|
+
print(f'error: {visible}', file=sys.stderr)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _site_engine(site: str, script_name: str, parser: argparse.ArgumentParser) -> Engine:
|
|
225
|
+
"""Create an engine for one site, reporting an invalid site URL or script name."""
|
|
226
|
+
api_key = _read_api_key(parser)
|
|
227
|
+
try:
|
|
228
|
+
return create_engine(site, script_name=script_name, api_key=api_key)
|
|
229
|
+
except ValueError as error:
|
|
230
|
+
raise _CommandError(str(error), 2) from error
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _connected_engine(
|
|
234
|
+
args: argparse.Namespace,
|
|
235
|
+
parser: argparse.ArgumentParser,
|
|
236
|
+
) -> Engine:
|
|
237
|
+
"""Create an engine from validated CLI credentials."""
|
|
238
|
+
if args.site is None or args.script_name is None:
|
|
239
|
+
parser.error('stubs requires --from-cache or both --site and --script-name')
|
|
240
|
+
return _site_engine(args.site, args.script_name, parser)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _load_schema_cache(
|
|
244
|
+
args: argparse.Namespace,
|
|
245
|
+
parser: argparse.ArgumentParser,
|
|
246
|
+
) -> SchemaCache:
|
|
247
|
+
"""Load the explicit or connection-populated schema cache."""
|
|
248
|
+
path = args.from_cache
|
|
249
|
+
if path is None:
|
|
250
|
+
engine = _connected_engine(args, parser)
|
|
251
|
+
_load_site_schema(engine, force=False)
|
|
252
|
+
path = cache_file(engine.site)
|
|
253
|
+
cache = load_cache(path)
|
|
254
|
+
if cache is None:
|
|
255
|
+
parser.error(f'could not load schema cache: {path}')
|
|
256
|
+
return cache
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _refresh_schema(args: argparse.Namespace, parser: argparse.ArgumentParser) -> tuple[int, str]:
|
|
260
|
+
"""Force a schema refetch and return its cache path."""
|
|
261
|
+
engine = _site_engine(args.site, args.script_name, parser)
|
|
262
|
+
_load_site_schema(engine, force=True)
|
|
263
|
+
return 0, f'refreshed schema cache: {cache_file(engine.site)}\n'
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _run_stubs(
|
|
267
|
+
args: argparse.Namespace,
|
|
268
|
+
parser: argparse.ArgumentParser,
|
|
269
|
+
) -> tuple[int, str]:
|
|
270
|
+
"""Generate or check stubs against the selected schema cache."""
|
|
271
|
+
cache = _load_schema_cache(args, parser)
|
|
272
|
+
if args.check:
|
|
273
|
+
fresh = _stubgen.check_stubs(
|
|
274
|
+
cache,
|
|
275
|
+
args.out,
|
|
276
|
+
entity_aliases=args.entity_aliases,
|
|
277
|
+
field_aliases=args.field_aliases,
|
|
278
|
+
permissive=args.permissive,
|
|
279
|
+
)
|
|
280
|
+
return (0, 'stubs are fresh\n') if fresh else (1, 'stub drift detected\n')
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
report = _stubgen.generate_stubs(
|
|
284
|
+
cache,
|
|
285
|
+
args.out,
|
|
286
|
+
entity_aliases=args.entity_aliases,
|
|
287
|
+
field_aliases=args.field_aliases,
|
|
288
|
+
permissive=args.permissive,
|
|
289
|
+
)
|
|
290
|
+
except (OSError, SgalchemyError) as error:
|
|
291
|
+
raise _CommandError(f'could not write stubs: {_describe_failure(error)}', 1) from error
|
|
292
|
+
lines = [f'wrote {path}' for path in report.written]
|
|
293
|
+
if report.skipped_aliases:
|
|
294
|
+
lines.append(f'aliases skipped: {", ".join(report.skipped_aliases)}')
|
|
295
|
+
else:
|
|
296
|
+
lines.append('aliases skipped: none')
|
|
297
|
+
return 0, '\n'.join(lines) + '\n'
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _run(arguments: list[str]) -> tuple[int, str]:
|
|
301
|
+
"""Parse arguments, run the selected command, and report anticipated failures."""
|
|
302
|
+
parser = _build_parser()
|
|
303
|
+
args = parser.parse_args(arguments)
|
|
304
|
+
if args.version:
|
|
305
|
+
return _render_version()
|
|
306
|
+
if args.command is None:
|
|
307
|
+
parser.error('the following arguments are required: COMMAND')
|
|
308
|
+
handler = _run_stubs if args.command == 'stubs' else _refresh_schema
|
|
309
|
+
try:
|
|
310
|
+
result = handler(args, args.command_parser)
|
|
311
|
+
except _CommandError as failure:
|
|
312
|
+
_report_error(str(failure))
|
|
313
|
+
return failure._status, '' # noqa: SLF001 # pyright: ignore[reportPrivateUsage]
|
|
314
|
+
return result
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
318
|
+
"""Run the `sgalchemy` command-line interface.
|
|
319
|
+
|
|
320
|
+
Each command finishes its work before writing output. If the reader closes
|
|
321
|
+
standard output, unwritten output is discarded and the exit status still
|
|
322
|
+
reports the command's result.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
argv: Arguments to parse, or `None` to use `sys.argv`.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
The process exit status.
|
|
329
|
+
"""
|
|
330
|
+
if sys.stdout is None:
|
|
331
|
+
# Started with standard output closed: nothing can be delivered, and the
|
|
332
|
+
# no text stream is available to write to.
|
|
333
|
+
_report_error('standard output is unavailable')
|
|
334
|
+
return 2
|
|
335
|
+
output = ''
|
|
336
|
+
try:
|
|
337
|
+
status, output = _run(sys.argv[1:] if argv is None else list(argv))
|
|
338
|
+
except KeyboardInterrupt:
|
|
339
|
+
status = 130
|
|
340
|
+
finally:
|
|
341
|
+
# Also flushes help or usage text that argparse wrote before raising SystemExit.
|
|
342
|
+
write_stdout(output)
|
|
343
|
+
return status
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Hold package-internal query and engine modules; import the public API from sgalchemy."""
|