menda-cli 0.2.3__tar.gz → 0.2.4__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 (70) hide show
  1. {menda_cli-0.2.3 → menda_cli-0.2.4}/.github/workflows/ci.yml +3 -0
  2. {menda_cli-0.2.3 → menda_cli-0.2.4}/CHANGELOG.md +6 -0
  3. {menda_cli-0.2.3 → menda_cli-0.2.4}/PKG-INFO +1 -1
  4. {menda_cli-0.2.3 → menda_cli-0.2.4}/pyproject.toml +1 -1
  5. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/auth/api.py +1 -1
  6. menda_cli-0.2.4/src/menda/auth/http.py +98 -0
  7. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/auth/oauth2/client.py +1 -3
  8. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/logging.py +7 -1
  9. {menda_cli-0.2.3 → menda_cli-0.2.4}/uv.lock +1 -1
  10. menda_cli-0.2.3/src/menda/auth/http.py +0 -35
  11. {menda_cli-0.2.3 → menda_cli-0.2.4}/.cruft.json +0 -0
  12. {menda_cli-0.2.3 → menda_cli-0.2.4}/.github/workflows/lint-pr.yml +0 -0
  13. {menda_cli-0.2.3 → menda_cli-0.2.4}/.github/workflows/prepare-release.yml +0 -0
  14. {menda_cli-0.2.3 → menda_cli-0.2.4}/.github/workflows/release.yml +0 -0
  15. {menda_cli-0.2.3 → menda_cli-0.2.4}/.gitignore +0 -0
  16. {menda_cli-0.2.3 → menda_cli-0.2.4}/README.md +0 -0
  17. {menda_cli-0.2.3 → menda_cli-0.2.4}/cliff.toml +0 -0
  18. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/__init__.py +0 -0
  19. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/auth/__init__.py +0 -0
  20. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/auth/env.py +0 -0
  21. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/auth/exceptions.py +0 -0
  22. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/auth/inquirer.py +0 -0
  23. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/auth/mendacfg.py +0 -0
  24. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/auth/models.py +0 -0
  25. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/auth/oauth2/__init__.py +0 -0
  26. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/auth/oauth2/token.py +0 -0
  27. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/auth/pkce.py +0 -0
  28. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/auth/task.py +0 -0
  29. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/__init__.py +0 -0
  30. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/auth/__init__.py +0 -0
  31. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/auth/commands.py +0 -0
  32. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/console.py +0 -0
  33. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/cookiecutters/databricks-default/cookiecutter.json +0 -0
  34. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/cookiecutters/databricks-default/{{cookiecutter.project_name}}/.gitignore +0 -0
  35. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/cookiecutters/databricks-default/{{cookiecutter.project_name}}/README.md +0 -0
  36. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/cookiecutters/databricks-default/{{cookiecutter.project_name}}/menda.yaml +0 -0
  37. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/cookiecutters/databricks-default/{{cookiecutter.project_name}}/pyproject.toml +0 -0
  38. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/cookiecutters/databricks-default/{{cookiecutter.project_name}}/src/{{cookiecutter.project_slug}}/__init__.py +0 -0
  39. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/cookiecutters/databricks-default/{{cookiecutter.project_name}}/src/{{cookiecutter.project_slug}}/dp/.gitkeep +0 -0
  40. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/cookiecutters/databricks-default/{{cookiecutter.project_name}}/src/{{cookiecutter.project_slug}}/orchestration/.gitkeep +0 -0
  41. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/discovery.py +0 -0
  42. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/exceptions.py +0 -0
  43. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/init/__init__.py +0 -0
  44. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/init/commands.py +0 -0
  45. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/init/prompts.py +0 -0
  46. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/init/templates.py +0 -0
  47. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/main.py +0 -0
  48. {menda_cli-0.2.3 → menda_cli-0.2.4}/src/menda/cli/params.py +0 -0
  49. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/auth/__init__.py +0 -0
  50. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/auth/oauth2/test_client.py +0 -0
  51. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/auth/test_env.py +0 -0
  52. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/auth/test_exceptions.py +0 -0
  53. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/auth/test_mendacfg.py +0 -0
  54. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/auth/test_models.py +0 -0
  55. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/auth/test_pkce.py +0 -0
  56. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/auth/test_task.py +0 -0
  57. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/auth/test_token.py +0 -0
  58. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/cli/.gitkeep +0 -0
  59. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/cli/auth/test_configure.py +0 -0
  60. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/cli/auth/test_login.py +0 -0
  61. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/cli/auth/test_whoami.py +0 -0
  62. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/cli/init/__init__.py +0 -0
  63. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/cli/init/test_commands.py +0 -0
  64. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/cli/init/test_templates.py +0 -0
  65. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/cli/test_console.py +0 -0
  66. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/cli/test_discovery.py +0 -0
  67. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/cli/test_exceptions.py +0 -0
  68. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/cli/test_ipython.py +0 -0
  69. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/cli/test_main.py +0 -0
  70. {menda_cli-0.2.3 → menda_cli-0.2.4}/tests/cli/test_params.py +0 -0
@@ -10,3 +10,6 @@ on:
10
10
  jobs:
11
11
  ci:
12
12
  uses: mendaml/menda-gh-actions/.github/workflows/python-ci.yml@v1
13
+ permissions:
14
+ contents: read
15
+ id-token: write
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.2.4] - 2026-04-21
6
+
7
+ ### Features
8
+
9
+ - Menda 622 fix device auth login issues (#18) ([59cb101](https://github.com/mendaml/menda-cli/commit/59cb1015bc7535c1bcaa0134b19d05d1ece7b44e))
10
+
5
11
  ## [0.2.3] - 2026-04-14
6
12
 
7
13
  ### Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: menda-cli
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: A CLI for building, deploying and managing menda data products.
5
5
  Author-email: jpm2617 <jose.moreno@mendaml.com>
6
6
  Requires-Python: >=3.11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "menda-cli"
3
- version = "0.2.3"
3
+ version = "0.2.4"
4
4
  description = "A CLI for building, deploying and managing menda data products."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -6,7 +6,7 @@ AUTHORIZATION_PATH = "/auth/device/authorize"
6
6
  TOKEN_PATH = "/auth/device/token" # noqa: S105
7
7
  WHOAMI_PATH = "/auth/whoami"
8
8
  OAUTH2_TOKEN_PATH = "/auth/oauth2/token" # noqa: S105
9
- CONFIG_PATH = "/system/config"
9
+ CONFIG_PATH = "/auth/discovery"
10
10
 
11
11
 
12
12
  class AuthRoutes:
@@ -0,0 +1,98 @@
1
+ """HTTP session factory for auth API calls."""
2
+
3
+ import typing
4
+ from functools import partial
5
+ from urllib.parse import urlparse
6
+
7
+ import requests
8
+
9
+ from menda.cli.exceptions import NetworkError
10
+
11
+ API_VERSION = "/api/v1"
12
+
13
+
14
+ class CustomRequestsSession(requests.Session):
15
+ """Requests session with custom methods."""
16
+
17
+ def delete_with_payload(self, **kwargs: typing.Any) -> requests.Response: # noqa: ANN401
18
+ """DELETE with request body support."""
19
+ return self.request(method="DELETE", **kwargs)
20
+
21
+
22
+ def _translate_connection_error(err: requests.exceptions.RequestException, base_url: str) -> NetworkError:
23
+ """Convert a low-level requests exception into a user-friendly NetworkError."""
24
+ host = urlparse(base_url).hostname or base_url
25
+
26
+ if isinstance(err, requests.exceptions.SSLError):
27
+ return NetworkError(
28
+ message=f"TLS/SSL handshake failed when contacting '{host}'.",
29
+ context=f"network:ssl:{host}",
30
+ suggestion=(
31
+ "Check that the host URL in your mendacfg profile is correct "
32
+ "and that your system clock and CA certificates are up to date."
33
+ ),
34
+ )
35
+
36
+ if isinstance(err, requests.exceptions.ConnectTimeout):
37
+ return NetworkError(
38
+ message=f"Timed out while connecting to '{host}'.",
39
+ context=f"network:timeout:{host}",
40
+ suggestion="Check your network connection or try again in a moment.",
41
+ )
42
+
43
+ if isinstance(err, requests.exceptions.ReadTimeout):
44
+ return NetworkError(
45
+ message=f"Timed out while waiting for a response from '{host}'.",
46
+ context=f"network:timeout:{host}",
47
+ suggestion="The service may be slow or unavailable — please try again.",
48
+ )
49
+
50
+ if isinstance(err, requests.exceptions.ConnectionError):
51
+ cause = str(err.__cause__) if err.__cause__ else str(err)
52
+ is_dns = "resolve" in cause.lower() or "name or service" in cause.lower() or "nodename" in cause.lower()
53
+ if is_dns:
54
+ return NetworkError(
55
+ message=f"Could not resolve '{host}'.",
56
+ context=f"network:dns:{host}",
57
+ suggestion=(
58
+ "Check the 'host' value in your mendacfg profile and confirm you have internet connectivity."
59
+ ),
60
+ )
61
+ return NetworkError(
62
+ message=f"Could not connect to '{host}'.",
63
+ context=f"network:connection:{host}",
64
+ suggestion=("Verify the host is reachable and that the 'host' value in your mendacfg profile is correct."),
65
+ )
66
+
67
+ return NetworkError(
68
+ message=f"Network error while contacting '{host}': {err}",
69
+ context=f"network:other:{host}",
70
+ suggestion="Check your network connection and profile configuration.",
71
+ )
72
+
73
+
74
+ def requests_session(base_url: str) -> CustomRequestsSession:
75
+ """Create a requests session with a base URL prepended to all requests.
76
+
77
+ Connection-layer failures (DNS, TLS, timeouts, refused connections) are
78
+ translated into NetworkError so users see a concise, actionable message
79
+ instead of a requests/urllib3 traceback.
80
+ """
81
+
82
+ def new_request(
83
+ base_url: str,
84
+ f: typing.Callable,
85
+ method: str,
86
+ url: str,
87
+ *args: typing.Any, # noqa: ANN401
88
+ **kwargs: typing.Any, # noqa: ANN401
89
+ ) -> requests.Response:
90
+ try:
91
+ return f(method, base_url + API_VERSION + url, *args, **kwargs)
92
+ except requests.exceptions.RequestException as err:
93
+ raise _translate_connection_error(err, base_url) from err
94
+
95
+ base_url = "" if base_url is None else base_url.rstrip("/")
96
+ s = CustomRequestsSession()
97
+ s.request = partial(new_request, base_url, s.request) # ty:ignore[invalid-assignment]
98
+ return s
@@ -24,9 +24,7 @@ def fetch_tenant_config(session: requests.Session, host: str) -> str:
24
24
 
25
25
  hostname = urlparse(host).hostname
26
26
  if not hostname:
27
- raise AuthenticationError(
28
- detail=f"Invalid host URL: {host}"
29
- )
27
+ raise AuthenticationError(detail=f"Invalid host URL: {host}")
30
28
  response = AuthRoutes.config(session=session, hostname=hostname)
31
29
  try:
32
30
  body = response.json()
@@ -10,6 +10,7 @@ the CLI package.
10
10
  """
11
11
 
12
12
  import structlog
13
+ from structlog.dev import ConsoleRenderer, RichTracebackFormatter, plain_traceback
13
14
  from structlog.processors import ExceptionRenderer
14
15
  from structlog.typing import Processor
15
16
 
@@ -49,9 +50,14 @@ def configure_logging(log_level: str, json_output: bool = False, show_locals: bo
49
50
  structlog.processors.JSONRenderer(),
50
51
  ]
51
52
  else:
53
+ # structlog's default rich formatter has show_locals=True, which leaks
54
+ # internal state (config values, tokens, etc.) into end-user tracebacks.
55
+ # Opt into a formatter that honors our flag and defaults to off.
56
+ exception_formatter = RichTracebackFormatter(show_locals=True) if show_locals else plain_traceback
52
57
  renderers = [
53
- structlog.dev.ConsoleRenderer(
58
+ ConsoleRenderer(
54
59
  colors=True,
60
+ exception_formatter=exception_formatter,
55
61
  level_styles={
56
62
  "debug": "\033[36m",
57
63
  "info": "\033[32m",
@@ -415,7 +415,7 @@ wheels = [
415
415
 
416
416
  [[package]]
417
417
  name = "menda-cli"
418
- version = "0.2.3"
418
+ version = "0.2.4"
419
419
  source = { editable = "." }
420
420
  dependencies = [
421
421
  { name = "click" },
@@ -1,35 +0,0 @@
1
- """HTTP session factory for auth API calls."""
2
-
3
- import typing
4
- from functools import partial
5
-
6
- import requests
7
-
8
- API_VERSION = "/api/v1"
9
-
10
-
11
- class CustomRequestsSession(requests.Session):
12
- """Requests session with custom methods."""
13
-
14
- def delete_with_payload(self, **kwargs: typing.Any) -> requests.Response: # noqa: ANN401
15
- """DELETE with request body support."""
16
- return self.request(method="DELETE", **kwargs)
17
-
18
-
19
- def requests_session(base_url: str) -> CustomRequestsSession:
20
- """Create a requests session with a base URL prepended to all requests."""
21
-
22
- def new_request(
23
- base_url: str,
24
- f: typing.Callable,
25
- method: str,
26
- url: str,
27
- *args: typing.Any, # noqa: ANN401
28
- **kwargs: typing.Any, # noqa: ANN401
29
- ) -> requests.Response:
30
- return f(method, base_url + API_VERSION + url, *args, **kwargs)
31
-
32
- base_url = "" if base_url is None else base_url.rstrip("/")
33
- s = CustomRequestsSession()
34
- s.request = partial(new_request, base_url, s.request) # ty:ignore[invalid-assignment]
35
- return s
File without changes
File without changes
File without changes
File without changes
File without changes