aio-kong 3.5.0__py3-none-any.whl → 3.7.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.
@@ -1,4 +1,4 @@
1
- Copyright (c) 2023 Quantmind
1
+ Copyright (c) 2018-2025 Quantmind
2
2
 
3
3
  Redistribution and use in source and binary forms, with or without modification,
4
4
  are permitted provided that the following conditions are met:
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: aio-kong
3
- Version: 3.5.0
3
+ Version: 3.7.0
4
4
  Summary: Asynchronous Kong Client
5
5
  License: BSD-3-Clause
6
- Author: Luca
6
+ Author: Luca Sbardella
7
7
  Author-email: luca@quantmind.com
8
8
  Requires-Python: >=3.10,<4.0
9
9
  Classifier: License :: OSI Approved :: BSD License
@@ -12,9 +12,11 @@ Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Programming Language :: Python :: 3.13
15
- Requires-Dist: PyYAML (>=6.0,<7.0)
16
- Requires-Dist: aiohttp (>=3.9.5,<4.0.0)
17
- Requires-Dist: click (>=8.1.3,<9.0.0)
15
+ Provides-Extra: cli
16
+ Requires-Dist: PyYAML (>=6.0) ; extra == "cli"
17
+ Requires-Dist: aiohttp (>=3.9.5)
18
+ Requires-Dist: click (>=8.1.3) ; extra == "cli"
19
+ Requires-Dist: rich (>=14.0.0,<15.0.0) ; extra == "cli"
18
20
  Project-URL: Issues, https://github.com/quantmind/aio-kong/issues
19
21
  Project-URL: Repository, https://github.com/quantmind/aio-kong
20
22
  Description-Content-Type: text/markdown
@@ -30,7 +32,7 @@ Description-Content-Type: text/markdown
30
32
 
31
33
  Tested with [kong][] v3.8
32
34
 
33
- ## Installation & Testing
35
+ ## Installation
34
36
 
35
37
  To install the package
36
38
 
@@ -38,9 +40,12 @@ To install the package
38
40
  pip install aio-kong
39
41
  ```
40
42
 
41
- To run tests, clone and
43
+ ## Testing
44
+
45
+ To run tests, clone the repository and
42
46
 
43
47
  ```
48
+ make install
44
49
  make test
45
50
  ```
46
51
 
@@ -94,10 +99,18 @@ await cli.apply_json(config)
94
99
 
95
100
  ## Command line tool
96
101
 
97
- The library install the `kongfig` command line tool for uploading kong configuration files.
102
+ The library can install the `kongfig` command line tool for uploading kong configuration files.
103
+
104
+ For the command line tool to work, you need to install the package as:
105
+
106
+ ```bash
107
+ pip install aio-kong[cli]
108
+ ```
109
+
110
+ and to run the cli tool, you can use the following command:
98
111
 
99
112
  ```
100
- kongfig --yaml config.yaml
113
+ kongfig --help
101
114
  ```
102
115
 
103
116
  [kong]: https://github.com/Kong/kong
@@ -1,19 +1,19 @@
1
- kong/__init__.py,sha256=B7HArV6-6zGHI3LOefG855TT4UVJKSDSy5xjDZb9BLQ,54
1
+ kong/__init__.py,sha256=eqr9UIVWumESkCzgs9mvIiJ_8VyEtp4do1jmLBlkLrM,54
2
2
  kong/acls.py,sha256=1HR66CKp6SVOmtauXOnj77moLMeHfL7gsfBRwlbJK1U,127
3
3
  kong/auths.py,sha256=1aNSQ6Sw5rjh8lbJppH9GOXVMG6KL7-bO4P75MsFKJU,2337
4
4
  kong/certificates.py,sha256=7QYS0BLg5QKyjdi1dUDGErXBZHQK4ARv_iYFluXX3RM,275
5
- kong/cli.py,sha256=TbzNWkx1rj2bnfarC28e2tndwwXWoW61ONJPpr5485c,1953
6
- kong/client.py,sha256=2GmJ_0AArwBF9RpycT-gGBzwB7Ev46Qy8Mq8HTPRUXc,3887
5
+ kong/cli.py,sha256=9_HiFC2FxhNpCufkg86E00k1nKjorp4O8Bss6ZmLdx8,5649
6
+ kong/client.py,sha256=T8cTcpCMZ4Lm_BFta56sFintV3vqgQFr5_4kwUi7LDc,4010
7
7
  kong/components.py,sha256=ZT3bSsfFzKOH-ipXFDFAsMCjWydqqCGsakbhB26CBfM,5471
8
8
  kong/consumers.py,sha256=SDR9W6zY3PAIq_iC0EOSKbn0lyTl4C7WbsyM7MA7hLs,2715
9
9
  kong/plugins.py,sha256=GmuL-EnrpHeZEzv0bjDvXE0oTtG3DYWpGi0AZHMmY7E,2730
10
10
  kong/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  kong/routes.py,sha256=e1n5hIIirwkAVacbFnJ_C1AwQN_IC10gISyXU8ex-g0,1493
12
- kong/services.py,sha256=iuJu2Y8QzLtTBwLNT5yCunJbY3AHSmejz7gXAbvC4bY,2515
12
+ kong/services.py,sha256=fVArH4m7DfTfgBD7n1OGUArE-P6ZNJUP_zPPM1sBNRo,2616
13
13
  kong/snis.py,sha256=QxjHFUtMGvlBcfCdiye5hQ9n8D1PRSQQThqaB-OekLI,716
14
14
  kong/utils.py,sha256=Uoe5DRclN-t1DX8AvwgidYn8GMMvLUXVjMgzqs6mUAE,932
15
- aio_kong-3.5.0.dist-info/LICENSE,sha256=4S4Rfy2EEQkrBsn1cu8_EZkf2Q8XfOoxjabtI0BOhdQ,1461
16
- aio_kong-3.5.0.dist-info/METADATA,sha256=OueTmt1drm9zHn0iUXefJjylKlKWAZ3Zj_lbGyvvBCE,2962
17
- aio_kong-3.5.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
18
- aio_kong-3.5.0.dist-info/entry_points.txt,sha256=Wp_emFiShNNMtqwMxwwdo7qQDiLMxYqZwk9sP4YbEp4,41
19
- aio_kong-3.5.0.dist-info/RECORD,,
15
+ aio_kong-3.7.0.dist-info/LICENSE,sha256=hTYbUZ810IUK9Cr9n0_PpfL7rpjDPSC9_CaS2014bdg,1466
16
+ aio_kong-3.7.0.dist-info/METADATA,sha256=WZipQ802pCcb_VOJBVz0NQZzHi2br_HF3R7z2U4S44I,3257
17
+ aio_kong-3.7.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
18
+ aio_kong-3.7.0.dist-info/entry_points.txt,sha256=Wp_emFiShNNMtqwMxwwdo7qQDiLMxYqZwk9sP4YbEp4,41
19
+ aio_kong-3.7.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.1
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
kong/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Asynchronous Kong client"""
2
2
 
3
- __version__ = "3.5.0"
3
+ __version__ = "3.7.0"
kong/cli.py CHANGED
@@ -4,59 +4,167 @@ from typing import Any, cast
4
4
 
5
5
  import click
6
6
  import yaml as _yaml
7
+ from rich.console import Console
8
+ from rich.table import Table
7
9
 
8
10
  from . import __version__
9
- from .client import Kong, KongError
11
+ from .client import Kong, KongError, default_admin_url
12
+ from .components import KongEntity
10
13
  from .consumers import Consumer
11
14
  from .utils import local_ip
12
15
 
16
+ admin_url = click.option(
17
+ "--url", default=default_admin_url(), help="Kong Admin URL", show_default=True
18
+ )
13
19
 
14
- @click.command()
15
- @click.option("--version", is_flag=True, default=False, help="Display version and exit")
16
- @click.option("--ip", is_flag=True, default=False, help="Show local IP address")
17
- @click.option(
18
- "--key-auth", help="Create or display an authentication key for a consumer"
20
+ as_json = click.option("--json", default=False, is_flag=True, help="Output as JSON")
21
+
22
+
23
+ @click.group()
24
+ @click.version_option(version=__version__)
25
+ def kong() -> None:
26
+ """Kong CLI - Manage Kong Gateway configurations."""
27
+ pass
28
+
29
+
30
+ @kong.command()
31
+ @click.argument(
32
+ "yaml",
33
+ type=click.File("r"),
19
34
  )
20
- @click.option("--yaml", type=click.File("r"), help="Yaml configuration to upload")
21
35
  @click.option(
22
- "--clear", default=False, is_flag=True, help="Clear objects not in configuration"
36
+ "--clear",
37
+ default=False,
38
+ is_flag=True,
39
+ help="Clear objects not in configuration",
23
40
  )
24
- @click.pass_context
25
- def kong(
26
- ctx: click.Context,
27
- version: bool,
28
- ip: bool,
29
- key_auth: str,
30
- yaml: click.File | None,
41
+ @admin_url
42
+ def yaml(
43
+ yaml: click.File,
31
44
  clear: bool,
45
+ url: str,
32
46
  ) -> None:
33
- if version:
34
- click.echo(__version__)
35
- elif ip:
36
- click.echo(local_ip())
37
- elif key_auth:
38
- _run(_auth_key(key_auth))
39
- elif yaml:
40
- _run(_yml(yaml, clear))
41
- else:
42
- click.echo(ctx.get_help())
47
+ "Upload a configuration from a yaml file"
48
+ asyncio.run(_yml(yaml, clear, url))
49
+
50
+
51
+ @kong.command()
52
+ def ip() -> None:
53
+ "Show local IP address"
54
+ click.echo(local_ip())
55
+
43
56
 
57
+ @kong.command()
58
+ @admin_url
59
+ @as_json
60
+ def services(url: str, json: bool) -> None:
61
+ "Display services"
62
+ asyncio.run(_services(url, as_json=json))
44
63
 
45
- def _run(coro: Any) -> None:
46
- asyncio.get_event_loop().run_until_complete(coro)
47
64
 
65
+ @kong.command()
66
+ @click.argument("service")
67
+ @admin_url
68
+ @as_json
69
+ def routes(service: str, url: str, json: bool) -> None:
70
+ "Display routes for a service"
71
+ asyncio.run(_routes(service, url, as_json=json))
48
72
 
49
- async def _yml(yaml: Any, clear: bool) -> None:
50
- async with Kong() as cli:
73
+
74
+ @kong.command()
75
+ @admin_url
76
+ @as_json
77
+ def consumers(url: str, json: bool) -> None:
78
+ "Display consumers"
79
+ asyncio.run(_consumers(url, as_json=json))
80
+
81
+
82
+ @kong.command()
83
+ @click.argument("consumer")
84
+ @admin_url
85
+ def key_auth(consumer: str, url: str) -> None:
86
+ "Create or display an authentication key for a consumer"
87
+ asyncio.run(_auth_key(consumer, url))
88
+
89
+
90
+ async def _yml(yaml: Any, clear: bool, url: str) -> None:
91
+ async with Kong(url=url) as cli:
51
92
  try:
52
93
  result = await cli.apply_json(_yaml.safe_load(yaml), clear=clear)
53
- click.echo(json.dumps(result, indent=4))
94
+ display_json(result)
54
95
  except KongError as exc:
55
96
  raise click.ClickException(str(exc)) from None
56
97
 
57
98
 
58
- async def _auth_key(consumer: str) -> None:
59
- async with Kong() as cli:
99
+ async def _services(url: str, as_json: bool = False) -> None:
100
+ async with Kong(url=url) as cli:
101
+ try:
102
+ services = await cli.services.get_full_list()
103
+ except KongError as exc:
104
+ raise click.ClickException(str(exc)) from None
105
+ if as_json:
106
+ display_json(services)
107
+ return
108
+ table = Table(title="Services")
109
+ columns = ["Name", "Host", "Port", "Protocol", "Path", "Tags", "ID"]
110
+ for column in columns:
111
+ table.add_column(column)
112
+ for s in sorted(services, key=lambda s: s.name):
113
+ table.add_row(*[str_value(s.data, column) for column in columns])
114
+ console = Console()
115
+ console.print(table)
116
+
117
+
118
+ async def _routes(service: str, url: str, as_json: bool = False) -> None:
119
+ async with Kong(url=url) as cli:
120
+ try:
121
+ svc = await cli.services.get(service)
122
+ routes = await svc.routes.get_full_list()
123
+ except KongError as exc:
124
+ raise click.ClickException(str(exc)) from None
125
+ if as_json:
126
+ display_json(routes)
127
+ return
128
+ table = Table(title="Services")
129
+ columns = [
130
+ "Name",
131
+ "Hosts",
132
+ "Protocols",
133
+ "Path",
134
+ "Tags",
135
+ "Strip Path",
136
+ "Preserve Host",
137
+ "ID",
138
+ ]
139
+ for column in columns:
140
+ table.add_column(column)
141
+ for s in sorted(routes, key=lambda s: s.name):
142
+ table.add_row(*[str_value(s.data, column) for column in columns])
143
+ console = Console()
144
+ console.print(table)
145
+
146
+
147
+ async def _consumers(url: str, as_json: bool = False) -> None:
148
+ async with Kong(url=url) as cli:
149
+ try:
150
+ consumers = await cli.consumers.get_full_list()
151
+ except KongError as exc:
152
+ raise click.ClickException(str(exc)) from None
153
+ if as_json:
154
+ display_json(consumers)
155
+ return
156
+ table = Table(title="Consumers")
157
+ columns = ["Username", "ID", "Custom ID", "Tags"]
158
+ for column in columns:
159
+ table.add_column(column)
160
+ for c in sorted(consumers, key=lambda s: s.name):
161
+ table.add_row(*[str_value(c.data, column) for column in columns])
162
+ console = Console()
163
+ console.print(table)
164
+
165
+
166
+ async def _auth_key(consumer: str, admin_url: str) -> None:
167
+ async with Kong(url=admin_url) as cli:
60
168
  try:
61
169
  c = cast(Consumer, await cli.consumers.get(consumer))
62
170
  keys = await c.keyauths.get_list()
@@ -64,6 +172,32 @@ async def _auth_key(consumer: str) -> None:
64
172
  key = keys[0]
65
173
  else:
66
174
  key = await c.keyauths.create()
67
- click.echo(json.dumps(key.data, indent=4))
175
+ display_json(key)
68
176
  except KongError as exc:
69
177
  raise click.ClickException(str(exc)) from None
178
+
179
+
180
+ def str_value(data: dict, key: str) -> str:
181
+ key = key.lower().replace(" ", "_")
182
+ value = data.get(key)
183
+ if value is None:
184
+ return ""
185
+ elif isinstance(value, bool):
186
+ return ":white_check_mark:" if value else "[red]:x:"
187
+ elif isinstance(value, list):
188
+ return ", ".join(str(v) for v in value)
189
+ else:
190
+ return str(value)
191
+
192
+
193
+ def display_json(data: Any) -> None:
194
+ """Display data as JSON."""
195
+ click.echo(json.dumps(data, indent=2, default=kong_entity))
196
+
197
+
198
+ def kong_entity(obj: Any) -> Any:
199
+ if isinstance(obj, KongEntity):
200
+ return obj.data
201
+ raise TypeError(
202
+ f"Cannot serialize {type(obj).__name__} to JSON"
203
+ ) # pragma: no cover
kong/client.py CHANGED
@@ -9,7 +9,7 @@ from aiohttp import ClientResponse, ClientSession
9
9
  from . import __version__
10
10
  from .acls import Acl, Acls
11
11
  from .certificates import Certificate, Certificates
12
- from .components import KongError, KongResponseError
12
+ from .components import CrudComponent, KongError, KongResponseError
13
13
  from .consumers import Consumer, Consumers
14
14
  from .plugins import Plugin, Plugins
15
15
  from .routes import Route, Routes
@@ -23,22 +23,24 @@ DEFAULT_USER_AGENT = (
23
23
  )
24
24
 
25
25
 
26
+ def default_admin_url() -> str:
27
+ """Return the default Kong admin URL."""
28
+ return os.getenv("KONG_ADMIN_URL", os.getenv("KONG_URL", "http://127.0.0.1:8001"))
29
+
30
+
26
31
  class Kong:
27
32
  """Kong client"""
28
33
 
29
- url: str = os.getenv(
30
- "KONG_ADMIN_URL", os.getenv("KONG_URL", "http://127.0.0.1:8001")
31
- )
32
34
  content_type: str = "application/json, text/*; q=0.5"
33
35
 
34
36
  def __init__(
35
37
  self,
36
- url: str = "",
38
+ url: str | None = None,
37
39
  session: ClientSession | None = None,
38
40
  request_kwargs: dict | None = None,
39
41
  user_agent: str = DEFAULT_USER_AGENT,
40
42
  ) -> None:
41
- self.url = url or self.url
43
+ self.url = url or default_admin_url()
42
44
  self.session = session
43
45
  self.user_agent = user_agent
44
46
  self.request_kwargs = request_kwargs or {}
@@ -107,7 +109,7 @@ class Kong:
107
109
  if not isinstance(data, list):
108
110
  data = [data]
109
111
  o = getattr(self, name)
110
- if not o:
112
+ if not isinstance(o, CrudComponent):
111
113
  raise KongError("Kong object %s not available" % name)
112
114
  result[name] = await o.apply_json(data, clear=clear)
113
115
  return result
kong/services.py CHANGED
@@ -3,7 +3,7 @@ from typing import cast
3
3
  from .components import UUID, CrudComponent, JsonType, KongError
4
4
  from .plugins import KongEntityWithPlugins
5
5
  from .routes import Route, Routes
6
- from .utils import local_ip
6
+ from .utils import local_ip, uid
7
7
 
8
8
  REMOVE = frozenset(("absent", "remove"))
9
9
  LOCAL_HOST = frozenset(("localhost", "127.0.0.1"))
@@ -20,12 +20,16 @@ class Service(KongEntityWithPlugins):
20
20
  def host(self) -> str:
21
21
  return self.data.get("host", "")
22
22
 
23
+ @property
24
+ def protocol(self) -> str:
25
+ return self.data.get("protocol", "")
26
+
23
27
 
24
28
  class Services(CrudComponent[Service]):
25
29
  """Kong Services"""
26
30
 
27
31
  async def delete(self, id_: str | UUID) -> bool:
28
- srv = cast(Service, self.wrap({"id": id_}))
32
+ srv = cast(Service, self.wrap({"id": uid(id_)}))
29
33
  await srv.routes.delete_all()
30
34
  await srv.plugins.delete_all()
31
35
  return await super().delete(id_)