cmem-cmemc 26.1.0rc3__py3-none-any.whl → 26.1.0rc4__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.
cmem_cmemc/cli.py CHANGED
@@ -68,7 +68,21 @@ CONTEXT_SETTINGS = {"auto_envvar_prefix": "CMEMC", "help_option_names": ["-h", "
68
68
  )
69
69
  @click.option("-q", "--quiet", is_flag=True, help="Suppress any non-error info messages.")
70
70
  @click.option(
71
- "-d", "--debug", is_flag=True, help="Output debug messages and stack traces after errors."
71
+ "-d",
72
+ "--debug",
73
+ is_flag=True,
74
+ help="Output debug messages and stack traces after errors. The log level "
75
+ "can be set in the config via the CMEMC_LOG_LEVEL environment variable. "
76
+ "Options are: 'TRACE', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'"
77
+ " [default: DEBUG]",
78
+ )
79
+ @click.option(
80
+ "--log-level",
81
+ type=click.Choice(
82
+ ["TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], case_sensitive=False
83
+ ),
84
+ help="Set the log level when --debug is enabled. "
85
+ "Can also be set via CMEMC_LOG_LEVEL environment variable.",
72
86
  )
73
87
  @click.option(
74
88
  "--external-http-timeout",
@@ -89,6 +103,7 @@ def cli( # noqa: PLR0913
89
103
  config_file: str,
90
104
  connection: str,
91
105
  external_http_timeout: int,
106
+ log_level: str,
92
107
  ) -> None:
93
108
  """Eccenca Corporate Memory Control (cmemc).
94
109
 
@@ -111,7 +126,7 @@ def cli( # noqa: PLR0913
111
126
 
112
127
  cmemc is © 2026 eccenca GmbH, licensed under the Apache License 2.0.
113
128
  """
114
- _ = connection, debug, quiet, config_file, external_http_timeout
129
+ _ = connection, debug, quiet, config_file, external_http_timeout, log_level
115
130
  if " ".join(sys.argv).find("config edit") != -1:
116
131
  app = ApplicationContext(config_file=config_file, debug=debug, quiet=quiet)
117
132
  else:
@@ -103,7 +103,7 @@ def _complete_marketplace_package_ids(
103
103
  ApplicationContext.set_connection_from_params(ctx.find_root().params)
104
104
  client = Client.from_cmempy()
105
105
  marketplace = Marketplace(client=client)
106
- candidates = [(_.id, f"{_.name} ({_.type})") for _ in marketplace.get_available_packages()]
106
+ candidates = [(_.id, f"{_.name}") for _ in marketplace.get_available_packages()]
107
107
  return completion.finalize_completion(candidates=candidates, incomplete=incomplete)
108
108
 
109
109
 
@@ -499,6 +499,58 @@ def publish_command(app: ApplicationContext, package_archive: str, marketplace_u
499
499
  app.echo_success("done")
500
500
 
501
501
 
502
+ @click.command(cls=CmemcCommand, name="search")
503
+ @click.argument(
504
+ "SEARCH_TERMS",
505
+ nargs=-1,
506
+ type=click.STRING,
507
+ )
508
+ @click.option("--raw", is_flag=True, help="Outputs raw JSON.")
509
+ @click.pass_obj
510
+ def search_command(app: ApplicationContext, search_terms: tuple[str], raw: bool) -> None:
511
+ """Search for available packages with a given search text."""
512
+ packages = app.client.marketplace.get_available_packages()
513
+
514
+ available_packages = []
515
+ search_terms_lower = [term.lower() for term in search_terms]
516
+
517
+ if search_terms == "":
518
+ available_packages = packages
519
+ else:
520
+ for package in packages:
521
+ searchable_text = (
522
+ f"{package.id.lower()} {package.name.lower()} "
523
+ f"{package.description.lower()} {package.type.value.lower()}"
524
+ )
525
+ if all(term in searchable_text for term in search_terms_lower):
526
+ available_packages.append(package)
527
+
528
+ if raw:
529
+ package_data = [
530
+ json.loads(metadata.model_dump_json(indent=2)) for metadata in available_packages
531
+ ]
532
+ app.echo_info_json(package_data)
533
+ return
534
+
535
+ table = [
536
+ (
537
+ _.id,
538
+ _.name,
539
+ _.description,
540
+ _.type.value,
541
+ )
542
+ for _ in available_packages
543
+ ]
544
+ app.echo_info_table(
545
+ table,
546
+ headers=["ID", "Name", "Description", "Type"],
547
+ sort_column=0,
548
+ empty_table_message="No available packages found.",
549
+ caption=f"{len(available_packages)} package{'' if len(available_packages) == 1 else "s" } "
550
+ f"found on marketplace.eccenca.dev",
551
+ )
552
+
553
+
502
554
  @click.group(cls=CmemcGroup)
503
555
  def package_group() -> CmemcGroup: # type: ignore[empty-body]
504
556
  """List, (un)install, export, create, or inspect packages."""
@@ -511,3 +563,4 @@ package_group.add_command(uninstall_command)
511
563
  package_group.add_command(export_command)
512
564
  package_group.add_command(build_command)
513
565
  package_group.add_command(publish_command)
566
+ package_group.add_command(search_command)
cmem_cmemc/context.py CHANGED
@@ -3,6 +3,7 @@
3
3
  import ast
4
4
  import configparser
5
5
  import json
6
+ import logging
6
7
  import os
7
8
  import re
8
9
  import subprocess # nosec
@@ -57,6 +58,19 @@ KNOWN_SECRET_KEYS = ("OAUTH_PASSWORD", "OAUTH_CLIENT_SECRET", "OAUTH_ACCESS_TOKE
57
58
  SSL_VERIFY_WARNING = "SSL verification is disabled (SSL_VERIFY=False)."
58
59
 
59
60
 
61
+ class ClickHandler(logging.Handler):
62
+ """Custom logging handler that outputs using click.secho with dimming."""
63
+
64
+ def emit(self, record: logging.LogRecord) -> None:
65
+ """Emit a log record using click.secho."""
66
+ try:
67
+ now = datetime.now(tz=timezone.utc)
68
+ msg = self.format(record)
69
+ click.secho(f"[{now!s}] {msg}", err=True, dim=True)
70
+ except Exception: # noqa: BLE001
71
+ self.handleError(record)
72
+
73
+
60
74
  def build_caption(
61
75
  count: int,
62
76
  item_name: str,
@@ -101,12 +115,14 @@ class ApplicationContext:
101
115
  connection: str | None
102
116
  console: Console
103
117
  console_width: int | None = None
118
+ log_level: str | None = None
104
119
 
105
120
  def __init__(
106
121
  self,
107
122
  config_file: str,
108
123
  connection: str | None = None,
109
124
  debug: bool = False,
125
+ log_level: str | None = None,
110
126
  quiet: bool = False,
111
127
  ):
112
128
  """Initialize main context."""
@@ -114,6 +130,7 @@ class ApplicationContext:
114
130
  self.app_name = "cmemc"
115
131
  self.set_debug(debug)
116
132
  self.set_quiet(quiet)
133
+ self.log_level = log_level
117
134
  self.ensure_app_config_dir()
118
135
  self.set_connection(connection)
119
136
  self.set_external_http_timeout(self.DEFAULT_EXTERNAL_HTTP_TIMEOUT)
@@ -123,7 +140,18 @@ class ApplicationContext:
123
140
  @property
124
141
  def client(self) -> Client:
125
142
  """The cmem_client Client object."""
126
- return Client.from_cmempy()
143
+ client = Client.from_cmempy()
144
+ if self.debug:
145
+ level = self.log_level or "DEBUG"
146
+ handler = ClickHandler()
147
+ formatter = logging.Formatter("%(name)s - [%(levelname)s] - %(message)s")
148
+ handler.setFormatter(formatter)
149
+ client.configure_client_logger(
150
+ level=level,
151
+ format_string="%(name)s - [%(levelname)s] - %(message)s",
152
+ handlers=[handler],
153
+ )
154
+ return client
127
155
 
128
156
  @staticmethod
129
157
  def from_params(params: dict) -> "ApplicationContext":
@@ -134,6 +162,7 @@ class ApplicationContext:
134
162
  config_file = params.get("config_file")
135
163
  debug = str_to_bool(str(params.get("debug")))
136
164
  quiet = str_to_bool(str(params.get("quiet")))
165
+ log_level = str(params.get("log_level")) if params.get("log_level") else None
137
166
  external_http_timeout = (
138
167
  int(str(params.get("external_http_timeout")))
139
168
  if "external_http_timeout" in params
@@ -143,7 +172,11 @@ class ApplicationContext:
143
172
  if not config_file:
144
173
  raise ValueError("Missing required key: 'config_file' in config dictionary")
145
174
  app = ApplicationContext(
146
- config_file=config_file, connection=connection, debug=debug, quiet=quiet
175
+ config_file=config_file,
176
+ connection=connection,
177
+ debug=debug,
178
+ quiet=quiet,
179
+ log_level=log_level,
147
180
  )
148
181
  app.set_external_http_timeout(external_http_timeout)
149
182
  if not app.debug:
@@ -184,6 +217,10 @@ class ApplicationContext:
184
217
  """Set debug state"""
185
218
  self.debug = debug
186
219
 
220
+ def set_log_level(self, log_level: str) -> None:
221
+ """Set log level"""
222
+ self.log_level = log_level
223
+
187
224
  def set_quiet(self, quiet: bool = False) -> None:
188
225
  """Set quiets state"""
189
226
  self.quiet = quiet
@@ -247,6 +284,9 @@ class ApplicationContext:
247
284
  for key, value in config.items():
248
285
  if key == "CMEMC_DEBUG":
249
286
  self.set_debug(str_to_bool(value))
287
+ elif key == "CMEMC_LOG_LEVEL":
288
+ self.set_log_level(value)
289
+ self.echo_debug(f"set log level to {value}")
250
290
  elif key == "CMEMC_QUIET":
251
291
  self.set_quiet(str_to_bool(value))
252
292
  elif key == "CMEMC_CONNECTION":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmem-cmemc
3
- Version: 26.1.0rc3
3
+ Version: 26.1.0rc4
4
4
  Summary: Command line client for eccenca Corporate Memory
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -32,7 +32,7 @@ Requires-Dist: certifi (>=2024.2.2)
32
32
  Requires-Dist: click (>=8.3.0,<9.0.0)
33
33
  Requires-Dist: click-didyoumean (>=0.3.1,<0.4.0)
34
34
  Requires-Dist: click-help-colors (>=0.9.4,<0.10.0)
35
- Requires-Dist: cmem-client (==0.7.1)
35
+ Requires-Dist: cmem-client (==0.8.0)
36
36
  Requires-Dist: cmem-cmempy (==25.4.0)
37
37
  Requires-Dist: configparser (>=7.2.0,<8.0.0)
38
38
  Requires-Dist: humanize (>=4.14.0,<5.0.0)
@@ -1,6 +1,6 @@
1
1
  cmem_cmemc/__init__.py,sha256=-RPEVweA-fcmEAynszDDMKwArJgxZpGW61UBiV7O4Og,24
2
2
  cmem_cmemc/_cmemc.zsh,sha256=fmkrBHIQxus8cp2AgO1tzZ5mNZdGL_83cYz3a9uAdsg,1326
3
- cmem_cmemc/cli.py,sha256=VGfHqSIPPTvYpPHrjwbnLYLB6O0u8kwiJDXeXMlI1xU,4855
3
+ cmem_cmemc/cli.py,sha256=V0BUSN-N7HzGtNBZOZ8N6C_8FaofCTD13uuuuW9f79k,5368
4
4
  cmem_cmemc/command.py,sha256=CuaskaqD12soZLhDP1prgXOT4cRFu1CzuJm6LBp4zLM,1949
5
5
  cmem_cmemc/command_group.py,sha256=0I2Jg1lCdoozcMg7d0g9sk0zVJ8_LiKvWWwqM6tZGI0,4793
6
6
  cmem_cmemc/commands/__init__.py,sha256=NaGM5jOzf0S_-4UIAwlVDOf2AZ3mliGPoRLXQJfTyZs,22
@@ -16,7 +16,7 @@ cmem_cmemc/commands/graph_insights.py,sha256=slQXBUy7azEG-w7M-2r9ybY4J3HgV9TTWbP
16
16
  cmem_cmemc/commands/manual.py,sha256=-sZWeFL92Kj8gL3VYsbpKh2ZaVTyM3LgKaUcpNn9u3A,2179
17
17
  cmem_cmemc/commands/metrics.py,sha256=t5I6VjBzjp_bQEoGkU9cdqNu_sa_WQwiIeJA3f9KNWc,12385
18
18
  cmem_cmemc/commands/migration.py,sha256=FibmYpvZD2mrutjyRBhs7xDAZ-sjPiH9Kn3CI-zUPm0,9861
19
- cmem_cmemc/commands/package.py,sha256=iY543hVwMqTA5t7mQAe2etz9n5sNgxx5y8Y-B53kJRY,16812
19
+ cmem_cmemc/commands/package.py,sha256=m5IBnSZdwroewHBTyxDIjQrF0NSixmMdffL1OeszyQM,18480
20
20
  cmem_cmemc/commands/project.py,sha256=HRA4xpOENajyQub1iWLo5voAsXFUzFPt7kJq9sx4sx4,25066
21
21
  cmem_cmemc/commands/python.py,sha256=lcbBAYZN5NB37HLSmVPs0SXJV7Ey4xVMYQiSiuyGkvc,12225
22
22
  cmem_cmemc/commands/query.py,sha256=1cj1QbvwL98YbBGSCO0Zazbzscts_kiv0A7k75KwJXw,32231
@@ -31,7 +31,7 @@ cmem_cmemc/commands/workspace.py,sha256=IcZgBsvtulLRFofS70qpln6oKQIZunrVLfSAUeiF
31
31
  cmem_cmemc/completion.py,sha256=JbMZmTLjgu_nrIS9NuuFHqfqAFwHE1dCvFNk0g2c6d0,44805
32
32
  cmem_cmemc/config_parser.py,sha256=NduwOT-BB_uAk3pz1Y-ex18RQJW-jjHzkQKCEUUK6Hc,1276
33
33
  cmem_cmemc/constants.py,sha256=pzZYbSaTDUiWmE-VOAHB20oivHew5_FP9UTejySsVK4,550
34
- cmem_cmemc/context.py,sha256=oCcd6dFl6BdYqKsueVqzQhSEwTNW7b1MjrE4CRznxt8,23220
34
+ cmem_cmemc/context.py,sha256=miYUlpJ9QwEPoh8U8_sekBQXw3MVe9kR5wGOWCzb0Yo,24683
35
35
  cmem_cmemc/exceptions.py,sha256=c4Z6CKgymu0a7gD8MtHxzK_7WCsb9I2Zl-EgEkwu-YY,760
36
36
  cmem_cmemc/manual_helper/__init__.py,sha256=G3Lqw2aPxo8x63Tg7L0aa5VD9BMaRzZDmhrog7IuEPg,43
37
37
  cmem_cmemc/manual_helper/graph.py,sha256=dTkFXgU9fgySn54rE93t79v1MjWjQkprKRIfJhc7Jps,3655
@@ -55,8 +55,8 @@ cmem_cmemc/smart_path/clients/http.py,sha256=3clZu2v4uuOvPY4MY_8SVSy7hIXJDNooahF
55
55
  cmem_cmemc/string_processor.py,sha256=19YSLUF9PIbfTmsTm2bZslsNhFUAYx0MerWYwC3BVEo,8616
56
56
  cmem_cmemc/title_helper.py,sha256=8Cyes2U4lHTQbzYwBSYqCrZbq29_oBg6uibe7xZ6DEg,3486
57
57
  cmem_cmemc/utils.py,sha256=rs3qf5UZeiTQO0USUpFQq6upQnG_S43CW7YYUrCwmzk,18240
58
- cmem_cmemc-26.1.0rc3.dist-info/METADATA,sha256=mB1KAv64UgOmwJ_4WbdqxeH7nI6MkVgq6d96hFrBPaE,5754
59
- cmem_cmemc-26.1.0rc3.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
60
- cmem_cmemc-26.1.0rc3.dist-info/entry_points.txt,sha256=2G0AWAyz501EHpFTjIxccdlCTsHt80NT0pdUGP1QkPA,45
61
- cmem_cmemc-26.1.0rc3.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
62
- cmem_cmemc-26.1.0rc3.dist-info/RECORD,,
58
+ cmem_cmemc-26.1.0rc4.dist-info/METADATA,sha256=ViY7R6xoIspCb6bu7it8j94FJDu7ks-6t_1iotPLDw8,5754
59
+ cmem_cmemc-26.1.0rc4.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
60
+ cmem_cmemc-26.1.0rc4.dist-info/entry_points.txt,sha256=2G0AWAyz501EHpFTjIxccdlCTsHt80NT0pdUGP1QkPA,45
61
+ cmem_cmemc-26.1.0rc4.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
62
+ cmem_cmemc-26.1.0rc4.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.3.0
2
+ Generator: poetry-core 2.3.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any