openapi-spec-tools 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 (23) hide show
  1. openapi_spec_tools-0.1.0/LICENSE +21 -0
  2. openapi_spec_tools-0.1.0/PKG-INFO +45 -0
  3. openapi_spec_tools-0.1.0/README.md +25 -0
  4. openapi_spec_tools-0.1.0/openapi_spec_tools/__init__.py +10 -0
  5. openapi_spec_tools-0.1.0/openapi_spec_tools/_typer.py +18 -0
  6. openapi_spec_tools-0.1.0/openapi_spec_tools/cli_gen/_arguments.py +101 -0
  7. openapi_spec_tools-0.1.0/openapi_spec_tools/cli_gen/_console.py +25 -0
  8. openapi_spec_tools-0.1.0/openapi_spec_tools/cli_gen/_display.py +309 -0
  9. openapi_spec_tools-0.1.0/openapi_spec_tools/cli_gen/_exceptions.py +22 -0
  10. openapi_spec_tools-0.1.0/openapi_spec_tools/cli_gen/_logging.py +24 -0
  11. openapi_spec_tools-0.1.0/openapi_spec_tools/cli_gen/_requests.py +220 -0
  12. openapi_spec_tools-0.1.0/openapi_spec_tools/cli_gen/_tree.py +170 -0
  13. openapi_spec_tools-0.1.0/openapi_spec_tools/cli_gen/cli.py +425 -0
  14. openapi_spec_tools-0.1.0/openapi_spec_tools/cli_gen/constants.py +2 -0
  15. openapi_spec_tools-0.1.0/openapi_spec_tools/cli_gen/generate.py +183 -0
  16. openapi_spec_tools-0.1.0/openapi_spec_tools/cli_gen/generator.py +837 -0
  17. openapi_spec_tools-0.1.0/openapi_spec_tools/cli_gen/layout.py +247 -0
  18. openapi_spec_tools-0.1.0/openapi_spec_tools/cli_gen/layout_types.py +90 -0
  19. openapi_spec_tools-0.1.0/openapi_spec_tools/cli_gen/utils.py +45 -0
  20. openapi_spec_tools-0.1.0/openapi_spec_tools/oas.py +638 -0
  21. openapi_spec_tools-0.1.0/openapi_spec_tools/types.py +52 -0
  22. openapi_spec_tools-0.1.0/openapi_spec_tools/utils.py +513 -0
  23. openapi_spec_tools-0.1.0/pyproject.toml +75 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Rick Porter
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.
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.3
2
+ Name: openapi-spec-tools
3
+ Version: 0.1.0
4
+ Summary: OpenAPI specification tools for analyzing, updating, and generating a CLI.
5
+ Author: Rick Porter
6
+ Author-email: rickwporter@gmail.com
7
+ Requires-Python: >=3.9,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.9
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
15
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
16
+ Requires-Dist: rich (>=13.9.4,<14.0.0)
17
+ Requires-Dist: typer (>=0.15.1,<0.16.0)
18
+ Description-Content-Type: text/markdown
19
+
20
+ # oas-tools
21
+
22
+ Welcome to OpenAPI specification (OAS) tools!
23
+
24
+ This is a collection of tools for using OpenAPI specifications. The OpenAPI community has a plethora of tools, and this is intended to supplement those. The tools here provide functionality that has not been readily available elsewhere.
25
+
26
+ ## OAS
27
+
28
+ The `oas` script provides a tool for analyzing and modifying an OpenAPI spec. See [OAS.md](OAS.md) for more info.
29
+
30
+ ## CLI Generation
31
+
32
+ The `cli-gen` tool allows users to create a user-friendly CLI using the OpenAPI spec and a layout file. The layout file provides the CLI structure and refers to the OpenAPI spec for details of operations. [LAYOUT.md](LAYOUT.md) has more details about the layout file, and the [CLI_GEN.md](CLI_GEN.md) has more info about CLI generation.
33
+
34
+ See the examples in `examples/` for some more complete works.
35
+
36
+ ## client.mk
37
+
38
+ The `client.mk` file is an example of a `Makefile` to invoke the [OpenAPI generator](https://github.com/OpenAPITools/openapi-generator) via a container. The file can be copied/modified to be invoked with an OpenAPI specfication (other than `openapi.yaml`) and a real package name. For a more complete list of generator options, look at the [OpenAPI generator usage documentation](https://openapi-generator.tech/docs/usage#generate).
39
+
40
+ ## Contributing
41
+
42
+ The [DEVELOPMENT.md](DEVELOPMENT.md) has more information about getting setup as a developer.
43
+
44
+ The [TODO.md](TODO.md) has some ideas where this project can be improved and expanded -- please add your ideas here, or email Rick directly (rickwporter@gmail.com).
45
+
@@ -0,0 +1,25 @@
1
+ # oas-tools
2
+
3
+ Welcome to OpenAPI specification (OAS) tools!
4
+
5
+ This is a collection of tools for using OpenAPI specifications. The OpenAPI community has a plethora of tools, and this is intended to supplement those. The tools here provide functionality that has not been readily available elsewhere.
6
+
7
+ ## OAS
8
+
9
+ The `oas` script provides a tool for analyzing and modifying an OpenAPI spec. See [OAS.md](OAS.md) for more info.
10
+
11
+ ## CLI Generation
12
+
13
+ The `cli-gen` tool allows users to create a user-friendly CLI using the OpenAPI spec and a layout file. The layout file provides the CLI structure and refers to the OpenAPI spec for details of operations. [LAYOUT.md](LAYOUT.md) has more details about the layout file, and the [CLI_GEN.md](CLI_GEN.md) has more info about CLI generation.
14
+
15
+ See the examples in `examples/` for some more complete works.
16
+
17
+ ## client.mk
18
+
19
+ The `client.mk` file is an example of a `Makefile` to invoke the [OpenAPI generator](https://github.com/OpenAPITools/openapi-generator) via a container. The file can be copied/modified to be invoked with an OpenAPI specfication (other than `openapi.yaml`) and a real package name. For a more complete list of generator options, look at the [OpenAPI generator usage documentation](https://openapi-generator.tech/docs/usage#generate).
20
+
21
+ ## Contributing
22
+
23
+ The [DEVELOPMENT.md](DEVELOPMENT.md) has more information about getting setup as a developer.
24
+
25
+ The [TODO.md](TODO.md) has some ideas where this project can be improved and expanded -- please add your ideas here, or email Rick directly (rickwporter@gmail.com).
@@ -0,0 +1,10 @@
1
+ from openapi_spec_tools.utils import count_values
2
+ from openapi_spec_tools.utils import find_diffs
3
+ from openapi_spec_tools.utils import find_paths
4
+ from openapi_spec_tools.utils import find_references
5
+ from openapi_spec_tools.utils import map_operations
6
+ from openapi_spec_tools.utils import open_oas
7
+ from openapi_spec_tools.utils import remove_schema_tags
8
+ from openapi_spec_tools.utils import schema_operations_filter
9
+ from openapi_spec_tools.utils import set_nullable_not_required
10
+ from openapi_spec_tools.utils import unroll
@@ -0,0 +1,18 @@
1
+ """
2
+ This provides some common extensions to Typer.
3
+ """
4
+
5
+ import typer
6
+ from rich import print
7
+ from typing_extensions import Annotated
8
+
9
+ # Common argument definition
10
+ OasFilenameArgument = Annotated[str, typer.Argument(show_default=False, help="OpenAPI specification file")]
11
+
12
+
13
+ def error_out(message: str, exit_code: int = 1) -> None:
14
+ """Utility to print provided error message (with red ERROR prefix) and exit"""
15
+ print(f"[red]ERROR:[/red] {message}")
16
+ raise typer.Exit(exit_code)
17
+
18
+
@@ -0,0 +1,101 @@
1
+ from typing import Optional
2
+
3
+ import typer
4
+ from typing_extensions import Annotated
5
+
6
+ from openapi_spec_tools.cli_gen._display import OutputFormat
7
+ from openapi_spec_tools.cli_gen._display import OutputStyle
8
+ from openapi_spec_tools.cli_gen._logging import LogLevel
9
+ from openapi_spec_tools.cli_gen._tree import TreeDisplay
10
+
11
+ ENV_API_HOST = "API_HOST"
12
+ ENV_API_KEY = "API_KEY"
13
+ ENV_API_TIME = "API_TIMEOUT"
14
+ ENV_LOG_LEVEL = "LOG_LEVEL"
15
+ ENV_OUT_FORMAT = "OUTPUT_FORMAT"
16
+ ENV_OUT_STYLE = "OUTPUT_STYLE"
17
+
18
+ ApiKeyOption = Annotated[
19
+ str,
20
+ typer.Option(
21
+ "--api-key",
22
+ show_default=False,
23
+ envvar=ENV_API_KEY,
24
+ help="API key for authentication",
25
+ ),
26
+ ]
27
+ ApiHostOption = Annotated[
28
+ str,
29
+ typer.Option(
30
+ "--api-host",
31
+ show_default=False,
32
+ envvar=ENV_API_HOST,
33
+ help="API host address",
34
+ ),
35
+ ]
36
+ ApiTimeoutOption = Annotated[
37
+ int,
38
+ typer.Option(
39
+ "--api-timeout",
40
+ envvar=ENV_API_TIME,
41
+ help="API request timeout in seconds for a single request",
42
+ ),
43
+ ]
44
+ DetailsOption = Annotated[
45
+ bool,
46
+ typer.Option(
47
+ "--details/--summary",
48
+ "-v",
49
+ help="Display the full details or a summary."
50
+ ),
51
+ ]
52
+ LogLevelOption = Annotated[
53
+ LogLevel,
54
+ typer.Option(
55
+ "--log",
56
+ case_sensitive=False,
57
+ envvar=ENV_LOG_LEVEL,
58
+ help="Log level",
59
+ ),
60
+ ]
61
+ MaxDepthOption = Annotated[
62
+ int,
63
+ typer.Option(
64
+ "--depth",
65
+ "--max-depth",
66
+ help="Maximum depth of tree to display."
67
+ ),
68
+ ]
69
+ MaxCountOption = Annotated[
70
+ Optional[int],
71
+ typer.Option(
72
+ "--max",
73
+ "--max-count",
74
+ help="Maximum number of items to get (if any)."
75
+ )
76
+ ]
77
+ OutputFormatOption = Annotated[
78
+ OutputFormat,
79
+ typer.Option(
80
+ "--format",
81
+ case_sensitive=False,
82
+ envvar=ENV_OUT_FORMAT,
83
+ help="Output format style",
84
+ ),
85
+ ]
86
+ OutputStyleOption = Annotated[
87
+ OutputStyle,
88
+ typer.Option(
89
+ "--style",
90
+ case_sensitive=False,
91
+ envvar=ENV_OUT_STYLE,
92
+ help="Style for output",
93
+ ),
94
+ ]
95
+ TreeDisplayOption = Annotated[
96
+ TreeDisplay,
97
+ typer.Option(
98
+ case_sensitive=False,
99
+ help="Details of the CLI command tree to show."
100
+ ),
101
+ ]
@@ -0,0 +1,25 @@
1
+ import os
2
+
3
+ from rich.console import Console
4
+
5
+ TEST_TERMINAL_WIDTH = 100
6
+
7
+
8
+ def console_factory(*args, **kwargs) -> Console:
9
+ """Utility to consolidate creation/initialization of Console.
10
+
11
+ A little hacky here... Allow terminal width to be set directly by an environment variable, or
12
+ when detecting that we're testing use a wide terminal to avoid line wrap issues.
13
+ """
14
+ width = kwargs.pop("width", None)
15
+ width_env = os.environ.get("TERMINAL_WIDTH")
16
+ pytest_version = os.environ.get("PYTEST_VERSION")
17
+ if width is not None:
18
+ pass
19
+ elif width_env is not None:
20
+ width = int(width_env)
21
+ elif pytest_version is not None:
22
+ width = TEST_TERMINAL_WIDTH
23
+ return Console(*args, width=width, **kwargs)
24
+
25
+
@@ -0,0 +1,309 @@
1
+ from enum import Enum
2
+ from gettext import gettext
3
+ from typing import Any
4
+ from typing import Optional
5
+
6
+ import yaml
7
+ from rich.box import HEAVY_HEAD
8
+ from rich.markup import escape
9
+ from rich.table import Table
10
+
11
+ from openapi_spec_tools.cli_gen._console import console_factory
12
+
13
+ DEFAULT_ROW_PROPS = {
14
+ "justify": "left",
15
+ "no_wrap": True,
16
+ "overflow": "ignore",
17
+ }
18
+
19
+ # allow for i18n/l8n
20
+ ITEMS = gettext("Items")
21
+ PROPERTY = gettext("Property")
22
+ PROPERTIES = gettext("Properties")
23
+ VALUE = gettext("Value")
24
+ VALUES = gettext("Values")
25
+ UNKNOWN = gettext("Unknown")
26
+ FOUND_ITEMS = gettext("Found {} items")
27
+ ELLIPSIS = gettext("...")
28
+
29
+ OBJECT_HEADERS = [PROPERTY, VALUE]
30
+
31
+ KEY_FIELDS = ["name", "id"]
32
+ URL_PREFIXES = ["http://", "https://", "ftp://"]
33
+
34
+ KEY_MAX_LEN = 35
35
+ VALUE_MAX_LEN = 50
36
+ URL_MAX_LEN = 100
37
+
38
+
39
+ # NOTE: the key field of dictionaries are expected to be be `str`, `int`, `float`, but use
40
+ # `Any` readability.
41
+
42
+
43
+ class TableConfig:
44
+ """This data class provides a means for customizing the table outputs.
45
+
46
+ The defaults provide a standard look and feel, but can be overridden to all customization.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ items_label: str = ITEMS,
52
+ property_label: str = PROPERTY,
53
+ properties_label: str = PROPERTIES,
54
+ value_label: str = VALUE,
55
+ values_label: str = VALUES,
56
+ unknown_label: str = UNKNOWN,
57
+ items_caption: str = FOUND_ITEMS,
58
+ url_prefixes: list[str] = URL_PREFIXES,
59
+ url_max_len: int = URL_MAX_LEN,
60
+ key_fields: list[str] = KEY_FIELDS,
61
+ key_max_len: int = KEY_MAX_LEN,
62
+ value_max_len: int = VALUE_MAX_LEN,
63
+ row_properties: dict[str, Any] = DEFAULT_ROW_PROPS,
64
+ ):
65
+ self.items_label = items_label
66
+ self.property_label = property_label
67
+ self.properties_label = properties_label
68
+ self.value_label = value_label
69
+ self.values_label = values_label
70
+ self.unknown_label = unknown_label
71
+ self.items_caption = items_caption
72
+ self.url_prefixes = url_prefixes
73
+ self.url_max_len = url_max_len
74
+ self.key_fields = key_fields
75
+ self.key_max_len = key_max_len
76
+ self.value_max_len = value_max_len
77
+ self.row_properties = row_properties
78
+
79
+
80
+ class RichTable(Table):
81
+ """
82
+ This is wrapper around the rich.Table to provide some methods for adding complex items.
83
+ """
84
+
85
+ def __init__(
86
+ self,
87
+ *args: Any,
88
+ outer: bool = True,
89
+ row_props: dict[str, Any] = DEFAULT_ROW_PROPS,
90
+ **kwargs: Any,
91
+ ):
92
+ super().__init__(
93
+ # items with "regular" defaults
94
+ highlight=kwargs.pop("highlight", True),
95
+ row_styles=kwargs.pop("row_styles", None),
96
+ expand=kwargs.pop("expand", False),
97
+ caption_justify=kwargs.pop("caption_justify", "left"),
98
+ border_style=kwargs.pop("border_style", None),
99
+ leading=kwargs.pop(
100
+ "leading", 0
101
+ ), # warning: setting to non-zero disables lines
102
+ # these items take queues from `outer`
103
+ show_header=kwargs.pop("show_header", outer),
104
+ show_edge=kwargs.pop("show_edge", outer),
105
+ box=HEAVY_HEAD if outer else None,
106
+ **kwargs,
107
+ )
108
+ for name in args:
109
+ self.add_column(name, **row_props)
110
+
111
+
112
+ def _truncate(s: str, max_length: int) -> str:
113
+ """Truncates the provided string to a maximum of max_length (including elipsis)"""
114
+ if len(s) < max_length:
115
+ return s
116
+ return s[: max_length - 3] + ELLIPSIS
117
+
118
+
119
+ def _get_name_key(item: dict[Any, Any], key_fields: list[str]) -> Optional[str]:
120
+ """Attempts to find an identifying value."""
121
+ for k in key_fields:
122
+ key = str(k)
123
+ if key in item:
124
+ return key
125
+
126
+ return None
127
+
128
+
129
+ def _is_url(s: str, url_prefixes: list[str]) -> bool:
130
+ """Rudimentary check for somethingt starting with URL prefix"""
131
+ return any(s.startswith(p) for p in url_prefixes)
132
+
133
+
134
+ def _safe(v: Any) -> str:
135
+ """Converts 'v' to a string that is properly escaped."""
136
+ return escape(str(v))
137
+
138
+
139
+ def _create_list_table(
140
+ items: list[dict[Any, Any]], outer: bool, config: TableConfig
141
+ ) -> RichTable:
142
+ """Creates a table from a list of dictionary items.
143
+
144
+ If an identifying "name key" is found (in the first entry), the table will have 2 columns: name, Properties
145
+ If no identifying "name key" is found, the table will be a single column table with the properties.
146
+
147
+ NOTE: nesting is done as needed
148
+ """
149
+ caption = config.items_caption.format(len(items)) if outer else None
150
+ name_key = _get_name_key(items[0], config.key_fields)
151
+ if not name_key:
152
+ # without identifiers just create table with one "Values" column
153
+ table = RichTable(
154
+ config.values_label,
155
+ outer=outer,
156
+ show_lines=True,
157
+ caption=caption,
158
+ row_props=config.row_properties,
159
+ )
160
+ for item in items:
161
+ table.add_row(_table_cell_value(item, config))
162
+ return table
163
+
164
+ # create a table with identifier in left column, and rest of data in right column
165
+ name_label = name_key[0].upper() + name_key[1:]
166
+ fields = [name_label, config.properties_label]
167
+ table = RichTable(
168
+ *fields,
169
+ outer=outer,
170
+ show_lines=True,
171
+ caption=caption,
172
+ row_props=config.row_properties,
173
+ )
174
+ for item in items:
175
+ # id may be an int, so convert to string before truncating
176
+ name = _safe(item.pop(name_key, config.unknown_label))
177
+ body = _table_cell_value(item, config)
178
+ table.add_row(_truncate(name, config.key_max_len), body)
179
+
180
+ return table
181
+
182
+
183
+ def _create_object_table(
184
+ obj: dict[Any, Any], outer: bool, config: TableConfig
185
+ ) -> RichTable:
186
+ """Creates a table of a dictionary object.
187
+
188
+ NOTE: nesting is done in the right column as needed.
189
+ """
190
+ headers = [config.property_label, config.value_label]
191
+ table = RichTable(
192
+ *headers, outer=outer, show_lines=False, row_props=config.row_properties
193
+ )
194
+ for k, v in obj.items():
195
+ name = _safe(k)
196
+ table.add_row(_truncate(name, config.key_max_len), _table_cell_value(v, config))
197
+
198
+ return table
199
+
200
+
201
+ def _table_cell_value(obj: Any, config: TableConfig) -> Any:
202
+ """Creates the "inner" value for a table cell.
203
+
204
+ Depending on the input value type, the cell may look different. If a dict, or list[dict],
205
+ an inner table is created. Otherwise, the object is converted to a printable value.
206
+ """
207
+ value: Any = None
208
+ if isinstance(obj, dict):
209
+ value = _create_object_table(obj, outer=False, config=config)
210
+ elif isinstance(obj, list) and obj:
211
+ if isinstance(obj[0], dict):
212
+ value = _create_list_table(obj, outer=False, config=config)
213
+ else:
214
+ values = [str(x) for x in obj]
215
+ s = _safe(", ".join(values))
216
+ value = _truncate(s, config.value_max_len)
217
+ else:
218
+ s = _safe(obj)
219
+ max_len = (
220
+ config.url_max_len
221
+ if _is_url(s, config.url_prefixes)
222
+ else config.value_max_len
223
+ )
224
+ value = _truncate(s, max_len)
225
+
226
+ return value
227
+
228
+
229
+ def rich_table_factory(obj: Any, config: TableConfig = TableConfig()) -> RichTable:
230
+ """Create a RichTable (alias for rich.table.Table) from the object."""
231
+ if isinstance(obj, dict):
232
+ return _create_object_table(obj, outer=True, config=config)
233
+
234
+ if isinstance(obj, list) and obj and isinstance(obj[0], dict):
235
+ return _create_list_table(obj, outer=True, config=config)
236
+
237
+ # this is a list of "simple" properties
238
+ if (
239
+ isinstance(obj, list)
240
+ and obj
241
+ and all(
242
+ item is None or isinstance(item, (str, float, bool, int)) for item in obj
243
+ )
244
+ ):
245
+ caption = config.items_caption.format(len(obj))
246
+ table = RichTable(
247
+ config.items_label,
248
+ outer=True,
249
+ show_lines=True,
250
+ caption=caption,
251
+ row_props=config.row_properties,
252
+ )
253
+ for item in obj:
254
+ table.add_row(_table_cell_value(item, config))
255
+ return table
256
+
257
+ raise ValueError(f"Unable to create table for type {type(obj).__name__}")
258
+
259
+
260
+ ###################################################################################################
261
+ # Below will remain even after the code from https://github.com/fastapi/typer/pull/1099 merges.
262
+ ###################################################################################################
263
+ class OutputFormat(str, Enum):
264
+ TABLE = "table"
265
+ JSON = "json"
266
+ YAML = "yaml"
267
+
268
+
269
+ class OutputStyle(str, Enum):
270
+ NONE = "none"
271
+ BOLD = "bold"
272
+ ALL = "all"
273
+
274
+
275
+ def summary(obj: Any, properties: list[str]) -> Any:
276
+ """Gets the item with just the specified properties."""
277
+ if obj is None:
278
+ return None
279
+
280
+ if isinstance(obj, list):
281
+ # recursively call for each object in list
282
+ return [summary(item, properties) for item in obj]
283
+
284
+ return {prop: obj.get(prop) for prop in properties}
285
+
286
+
287
+ def display(obj: Any, fmt: OutputFormat, style: OutputStyle, indent: int = 2) -> None:
288
+ """
289
+ This function handles display of the data provided in obj, according to the formating arguments.
290
+ """
291
+ no_color = style != OutputStyle.ALL
292
+ highlight = style != OutputStyle.NONE
293
+ console = console_factory(no_color=no_color, highlight=highlight)
294
+
295
+ if fmt == OutputFormat.JSON:
296
+ console.print_json(data=obj, indent=indent, highlight=highlight)
297
+ return
298
+
299
+ if fmt == OutputFormat.YAML:
300
+ console.print(_safe(yaml.dump(obj, indent=indent)))
301
+ return
302
+
303
+ if not obj:
304
+ console.print("Nothing found")
305
+ return
306
+
307
+ table = rich_table_factory(obj)
308
+ console.print(table)
309
+ return
@@ -0,0 +1,22 @@
1
+ import typer
2
+ from requests import HTTPError
3
+
4
+ from openapi_spec_tools.cli_gen._console import console_factory
5
+
6
+
7
+ class MissingRequiredError(Exception):
8
+ """Short wrapper to provde feedback about missing required options."""
9
+ def __init__(self, names: list[str]):
10
+ message = f"Missing required parameters, please provide: {', '.join(names)}"
11
+ super().__init__(message)
12
+
13
+
14
+ def handle_exceptions(ex: Exception) -> None:
15
+ """Process exception and print a more concise error"""
16
+ if isinstance(ex, HTTPError):
17
+ message = str(ex.args[0])
18
+ else:
19
+ message = str(ex)
20
+ console = console_factory()
21
+ console.print(f"[red]ERROR:[/red] {message}")
22
+ raise typer.Exit(1)
@@ -0,0 +1,24 @@
1
+ import logging
2
+ from enum import Enum
3
+ from typing import Optional
4
+
5
+ LOG_CLASS = "cli"
6
+ LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s %(message)s"
7
+ LOG_DATE_FMT = "%Y-%m-%d %I:%M:%S %p"
8
+
9
+
10
+ class LogLevel(str, Enum):
11
+ CRITICAL = "critical"
12
+ ERROR = "error"
13
+ WARN = "warn"
14
+ INFO = "info"
15
+ DEBUG = "debug"
16
+
17
+
18
+ def logger(name: Optional[str] = LOG_CLASS) -> logging.Logger:
19
+ return logging.getLogger(name=name)
20
+
21
+
22
+ def init_logging(level: LogLevel, name: Optional[str] = LOG_CLASS):
23
+ logging.basicConfig(format=LOG_FORMAT, datefmt=LOG_DATE_FMT)
24
+ logger(name).setLevel(level.upper())