mreg-cli 1.12.0__tar.gz → 1.12.2__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 (108) hide show
  1. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/CHANGELOG.md +16 -0
  2. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/PKG-INFO +2 -2
  3. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/ci/testsuite-result.json +73 -73
  4. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/_version.py +3 -3
  5. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/cli.py +2 -2
  6. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/client.py +6 -13
  7. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/exceptions.py +20 -16
  8. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/main.py +2 -8
  9. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/utilities/api.py +2 -0
  10. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli.egg-info/PKG-INFO +2 -2
  11. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli.egg-info/requires.txt +1 -1
  12. mreg_cli-1.12.2/mreg_cli.egg-info/scm_version.json +8 -0
  13. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/pyproject.toml +1 -1
  14. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/test_exceptions.py +93 -16
  15. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/uv.lock +4 -4
  16. mreg_cli-1.12.0/mreg_cli.egg-info/scm_version.json +0 -8
  17. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/.github/workflows/publish.yml +0 -0
  18. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/.github/workflows/test.yml +0 -0
  19. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/.gitignore +0 -0
  20. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/.markdownlint.json +0 -0
  21. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/AUTHORS +0 -0
  22. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/CLAUDE.md +0 -0
  23. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/CONTRIBUTING +0 -0
  24. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/LICENSE +0 -0
  25. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/README.md +0 -0
  26. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/ci/Dockerfile +0 -0
  27. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/ci/diff.py +0 -0
  28. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/ci/docker-compose.yml +0 -0
  29. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/ci/run_testsuite_and_record_V2.sh +0 -0
  30. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/ci/testsuite +0 -0
  31. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/data/mreg-cli.conf +0 -0
  32. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/__about__.py +0 -0
  33. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/__init__.py +0 -0
  34. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/__main__.py +0 -0
  35. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/choices.py +0 -0
  36. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/__init__.py +0 -0
  37. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/base.py +0 -0
  38. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/cache.py +0 -0
  39. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/dhcp.py +0 -0
  40. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/group.py +0 -0
  41. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/help.py +0 -0
  42. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/host.py +0 -0
  43. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/host_submodules/__init__.py +0 -0
  44. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/host_submodules/a_aaaa.py +0 -0
  45. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/host_submodules/bacnet.py +0 -0
  46. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/host_submodules/cname.py +0 -0
  47. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/host_submodules/core.py +0 -0
  48. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/host_submodules/rr.py +0 -0
  49. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/label.py +0 -0
  50. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/logging.py +0 -0
  51. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/network.py +0 -0
  52. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/permission.py +0 -0
  53. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/policy.py +0 -0
  54. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/recording.py +0 -0
  55. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/registry.py +0 -0
  56. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/root.py +0 -0
  57. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/commands/zone.py +0 -0
  58. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/config.py +0 -0
  59. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/dirs.py +0 -0
  60. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/errorbuilder.py +0 -0
  61. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/help_formatter.py +0 -0
  62. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/log.py +0 -0
  63. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/output/__init__.py +0 -0
  64. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/output/base.py +0 -0
  65. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/output/cache.py +0 -0
  66. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/output/group.py +0 -0
  67. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/output/history.py +0 -0
  68. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/output/host.py +0 -0
  69. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/output/meta.py +0 -0
  70. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/output/network.py +0 -0
  71. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/output/policy.py +0 -0
  72. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/output/zone.py +0 -0
  73. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/outputmanager.py +0 -0
  74. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/prompt.py +0 -0
  75. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/py.typed +0 -0
  76. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/tags.txt +0 -0
  77. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/tokenfile.py +0 -0
  78. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/types.py +0 -0
  79. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/utilities/__init__.py +0 -0
  80. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/utilities/fs.py +0 -0
  81. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/utilities/shared.py +0 -0
  82. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli/utilities/validators.py +0 -0
  83. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli.egg-info/SOURCES.txt +0 -0
  84. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli.egg-info/dependency_links.txt +0 -0
  85. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli.egg-info/entry_points.txt +0 -0
  86. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli.egg-info/scm_file_list.json +0 -0
  87. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/mreg_cli.egg-info/top_level.txt +0 -0
  88. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/run.py +0 -0
  89. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/setup.cfg +0 -0
  90. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/__init__.py +0 -0
  91. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/api/__init__.py +0 -0
  92. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/api/test_client.py +0 -0
  93. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/commands/__init__.py +0 -0
  94. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/commands/test_host.py +0 -0
  95. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/commands/test_network.py +0 -0
  96. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/commands/test_rr.py +0 -0
  97. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/conftest.py +0 -0
  98. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/test_config.py +0 -0
  99. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/test_errorbuilder.py +0 -0
  100. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/test_log.py +0 -0
  101. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/test_outputmanager.py +0 -0
  102. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/test_prompt.py +0 -0
  103. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/test_tokenfile.py +0 -0
  104. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/test_types.py +0 -0
  105. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/utilities/__init__.py +0 -0
  106. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/utilities/test_fs.py +0 -0
  107. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tests/utils.py +0 -0
  108. {mreg_cli-1.12.0 → mreg_cli-1.12.2}/tox.ini +0 -0
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  <!-- ## Unreleased -->
9
9
 
10
+ ## [1.12.2](https://github.com/unioslo/mreg-cli/releases/tag/1.12.2) - 2026-10-16
11
+
12
+ ### Changed
13
+
14
+ - Updated `mreg-api` dependency to 0.8.0.
15
+
16
+ ### Fixed
17
+
18
+ - Commands that resolve hosts and networks throwing generic "not found" error instead of prompting for re-login on expired token.
19
+
20
+ ## [1.12.1](https://github.com/unioslo/mreg-cli/releases/tag/1.12.1) - 2026-10-08
21
+
22
+ ### Changed
23
+
24
+ - Updated `mreg-api` dependency to 0.6.1. This removes some custom error handling code for M2M errors (hostgroups->owners,hosts; roles->atoms,hosts) and instead uses the server's own error messages.
25
+
10
26
  ## [1.12.0](https://github.com/unioslo/mreg-cli/releases/tag/1.12.0) - 2026-10-05
11
27
 
12
28
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mreg-cli
3
- Version: 1.12.0
3
+ Version: 1.12.2
4
4
  Summary: MREG interactive command line client
5
5
  Author: Others (see AUTHORS)
6
6
  Author-email: Øyvind Kolbu <oyvind.kolbu@usit.uio.no>, Safet A <amedov@usit.uio.no>, Magnus Hirth <mhmorgan42@gmail.com>, Nico <nicolaydmohebi@gmail.com>, Marius Bakke <marius.bakke@usit.uio.no>, Fredrik Larsen <fredrik.larsen@usit.uio.no>, Paal Braathen <paal.braathen@usit.uio.no>, Nils Hiorth <nils@nls.me>, Dmytro Karpenko <dmytrok@usit.uio.no>
@@ -26,7 +26,7 @@ Requires-Dist: pydantic-extra-types>=2.1.0
26
26
  Requires-Dist: platformdirs>=4.3.0
27
27
  Requires-Dist: pydantic-settings>=2.10.1
28
28
  Requires-Dist: rich>=13.9.4
29
- Requires-Dist: mreg-api<0.7.0,>=0.6.0
29
+ Requires-Dist: mreg-api<0.9.0,>=0.8.0
30
30
  Dynamic: license-file
31
31
 
32
32
  # MREG CLI [![Build Status](https://github.com/unioslo/mreg-cli/actions/workflows/test.yml/badge.svg)](https://github.com/unioslo/mreg-cli/actions/workflows/test.yml)
@@ -9898,10 +9898,10 @@
9898
9898
  "command_filter_negate": false,
9899
9899
  "command_issued": "host add somehost -ip 10.0.1.20 -contact support@example.org -force # should fail because ip is in excluded range",
9900
9900
  "ok": [],
9901
- "warning": [
9901
+ "warning": [],
9902
+ "error": [
9902
9903
  "400 Bad Request: POST http://127.0.0.1:8000/api/v1/hosts/\n1 error:\n non_field_errors: IP 10.0.1.20 in an excluded range: 10.0.1.0/24 -> [10.0.1.20 -> [10.0.1.30] (invalid)"
9903
9904
  ],
9904
- "error": [],
9905
9905
  "output": [],
9906
9906
  "api_requests": [
9907
9907
  {
@@ -9945,18 +9945,18 @@
9945
9945
  "zone": {
9946
9946
  "nameservers": [
9947
9947
  {
9948
- "created_at": "2026-09-23T14:32:35.657121+02:00",
9949
- "updated_at": "2026-09-23T14:32:35.657200+02:00",
9948
+ "created_at": "2026-10-16T12:53:57.454405+02:00",
9949
+ "updated_at": "2026-10-16T12:53:57.454474+02:00",
9950
9950
  "name": "ns2.example.org",
9951
9951
  "ttl": null
9952
9952
  }
9953
9953
  ],
9954
- "created_at": "2026-09-23T14:32:33.995871+02:00",
9955
- "updated_at": "2026-09-23T14:32:41.586500+02:00",
9954
+ "created_at": "2026-10-16T12:53:57.061219+02:00",
9955
+ "updated_at": "2026-10-16T12:54:02.548724+02:00",
9956
9956
  "updated": true,
9957
9957
  "primary_ns": "ns2.example.org",
9958
9958
  "email": "hostperson@example.org",
9959
- "serialno_updated_at": "2026-09-23T14:32:37.698376+02:00",
9959
+ "serialno_updated_at": "2026-10-16T12:53:57.561895+02:00",
9960
9960
  "refresh": 360,
9961
9961
  "retry": 1800,
9962
9962
  "expire": 2400,
@@ -9974,8 +9974,8 @@
9974
9974
  "response": {
9975
9975
  "excluded_ranges": [
9976
9976
  {
9977
- "created_at": "2026-09-23T14:32:42.228959+02:00",
9978
- "updated_at": "2026-09-23T14:32:42.229024+02:00",
9977
+ "created_at": "2026-10-16T12:54:03.158958+02:00",
9978
+ "updated_at": "2026-10-16T12:54:03.159059+02:00",
9979
9979
  "start_ip": "10.0.1.20",
9980
9980
  "end_ip": "10.0.1.30",
9981
9981
  "network": 3
@@ -9983,8 +9983,8 @@
9983
9983
  ],
9984
9984
  "policy": null,
9985
9985
  "communities": [],
9986
- "created_at": "2026-09-23T14:32:41.879909+02:00",
9987
- "updated_at": "2026-09-23T14:32:41.990455+02:00",
9986
+ "created_at": "2026-10-16T12:54:02.849972+02:00",
9987
+ "updated_at": "2026-10-16T12:54:02.949770+02:00",
9988
9988
  "network": "10.0.1.0/24",
9989
9989
  "description": "Frozzzen",
9990
9990
  "vlan": null,
@@ -11176,10 +11176,10 @@
11176
11176
  "command_filter_negate": false,
11177
11177
  "command_issued": "host add otherhost -ip 10.0.1.20 -contact support@example.org # fails because reserved range",
11178
11178
  "ok": [],
11179
- "warning": [
11179
+ "warning": [],
11180
+ "error": [
11180
11181
  "400 Bad Request: POST http://127.0.0.1:8000/api/v1/hosts/\n1 error:\n non_field_errors: IP 10.0.1.20 in an excluded range: 10.0.1.0/24 -> [10.0.1.20 -> [10.0.1.30] (invalid)"
11181
11182
  ],
11182
- "error": [],
11183
11183
  "output": [],
11184
11184
  "api_requests": [
11185
11185
  {
@@ -11223,18 +11223,18 @@
11223
11223
  "zone": {
11224
11224
  "nameservers": [
11225
11225
  {
11226
- "created_at": "2026-09-23T14:32:35.657121+02:00",
11227
- "updated_at": "2026-09-23T14:32:35.657200+02:00",
11226
+ "created_at": "2026-10-16T12:53:57.454405+02:00",
11227
+ "updated_at": "2026-10-16T12:53:57.454474+02:00",
11228
11228
  "name": "ns2.example.org",
11229
11229
  "ttl": null
11230
11230
  }
11231
11231
  ],
11232
- "created_at": "2026-09-23T14:32:33.995871+02:00",
11233
- "updated_at": "2026-09-23T14:32:46.806823+02:00",
11232
+ "created_at": "2026-10-16T12:53:57.061219+02:00",
11233
+ "updated_at": "2026-10-16T12:54:06.763646+02:00",
11234
11234
  "updated": true,
11235
11235
  "primary_ns": "ns2.example.org",
11236
11236
  "email": "hostperson@example.org",
11237
- "serialno_updated_at": "2026-09-23T14:32:37.698376+02:00",
11237
+ "serialno_updated_at": "2026-10-16T12:53:57.561895+02:00",
11238
11238
  "refresh": 360,
11239
11239
  "retry": 1800,
11240
11240
  "expire": 2400,
@@ -11252,8 +11252,8 @@
11252
11252
  "response": {
11253
11253
  "excluded_ranges": [
11254
11254
  {
11255
- "created_at": "2026-09-23T14:32:42.228959+02:00",
11256
- "updated_at": "2026-09-23T14:32:42.229024+02:00",
11255
+ "created_at": "2026-10-16T12:54:03.158958+02:00",
11256
+ "updated_at": "2026-10-16T12:54:03.159059+02:00",
11257
11257
  "start_ip": "10.0.1.20",
11258
11258
  "end_ip": "10.0.1.30",
11259
11259
  "network": 3
@@ -11261,8 +11261,8 @@
11261
11261
  ],
11262
11262
  "policy": null,
11263
11263
  "communities": [],
11264
- "created_at": "2026-09-23T14:32:41.879909+02:00",
11265
- "updated_at": "2026-09-23T14:32:46.986540+02:00",
11264
+ "created_at": "2026-10-16T12:54:02.849972+02:00",
11265
+ "updated_at": "2026-10-16T12:54:06.888787+02:00",
11266
11266
  "network": "10.0.1.0/24",
11267
11267
  "description": "Frozen but has one host",
11268
11268
  "vlan": 1234,
@@ -11862,10 +11862,10 @@
11862
11862
  "command_filter_negate": false,
11863
11863
  "command_issued": "host add somehost -ip 2001:db8::20 -contact support@example.org -force # should fail, because the address is in a reserved range",
11864
11864
  "ok": [],
11865
- "warning": [
11865
+ "warning": [],
11866
+ "error": [
11866
11867
  "400 Bad Request: POST http://127.0.0.1:8000/api/v1/hosts/\n1 error:\n non_field_errors: IP 2001:db8::20 in an excluded range: 2001:db8::/64 -> [2001:db8::20 -> [2001:db8::30] (invalid)"
11867
11868
  ],
11868
- "error": [],
11869
11869
  "output": [],
11870
11870
  "api_requests": [
11871
11871
  {
@@ -11909,18 +11909,18 @@
11909
11909
  "zone": {
11910
11910
  "nameservers": [
11911
11911
  {
11912
- "created_at": "2026-09-23T14:32:35.657121+02:00",
11913
- "updated_at": "2026-09-23T14:32:35.657200+02:00",
11912
+ "created_at": "2026-10-16T12:53:57.454405+02:00",
11913
+ "updated_at": "2026-10-16T12:53:57.454474+02:00",
11914
11914
  "name": "ns2.example.org",
11915
11915
  "ttl": null
11916
11916
  }
11917
11917
  ],
11918
- "created_at": "2026-09-23T14:32:33.995871+02:00",
11919
- "updated_at": "2026-09-23T14:32:49.223843+02:00",
11918
+ "created_at": "2026-10-16T12:53:57.061219+02:00",
11919
+ "updated_at": "2026-10-16T12:54:08.066493+02:00",
11920
11920
  "updated": true,
11921
11921
  "primary_ns": "ns2.example.org",
11922
11922
  "email": "hostperson@example.org",
11923
- "serialno_updated_at": "2026-09-23T14:32:37.698376+02:00",
11923
+ "serialno_updated_at": "2026-10-16T12:53:57.561895+02:00",
11924
11924
  "refresh": 360,
11925
11925
  "retry": 1800,
11926
11926
  "expire": 2400,
@@ -11938,8 +11938,8 @@
11938
11938
  "response": {
11939
11939
  "excluded_ranges": [
11940
11940
  {
11941
- "created_at": "2026-09-23T14:32:49.735938+02:00",
11942
- "updated_at": "2026-09-23T14:32:49.736006+02:00",
11941
+ "created_at": "2026-10-16T12:54:08.538757+02:00",
11942
+ "updated_at": "2026-10-16T12:54:08.538830+02:00",
11943
11943
  "start_ip": "2001:db8::20",
11944
11944
  "end_ip": "2001:db8::30",
11945
11945
  "network": 4
@@ -11947,8 +11947,8 @@
11947
11947
  ],
11948
11948
  "policy": null,
11949
11949
  "communities": [],
11950
- "created_at": "2026-09-23T14:32:49.397695+02:00",
11951
- "updated_at": "2026-09-23T14:32:49.502026+02:00",
11950
+ "created_at": "2026-10-16T12:54:08.253602+02:00",
11951
+ "updated_at": "2026-10-16T12:54:08.349296+02:00",
11952
11952
  "network": "2001:db8::/64",
11953
11953
  "description": "Lorem ipsum dolor sit amet",
11954
11954
  "vlan": null,
@@ -22009,10 +22009,10 @@
22009
22009
  "command_filter_negate": false,
22010
22010
  "command_issued": "host set_contact bar \"I'm the contact\" # fails because invalid email address",
22011
22011
  "ok": [],
22012
- "warning": [
22012
+ "warning": [],
22013
+ "error": [
22013
22014
  "400 Bad Request: PATCH http://127.0.0.1:8000/api/v1/hosts/bar.example.org\n1 error:\n contacts: Invalid email address(es): I'm the contact (invalid)"
22014
22015
  ],
22015
- "error": [],
22016
22016
  "output": [],
22017
22017
  "api_requests": [
22018
22018
  {
@@ -22024,8 +22024,8 @@
22024
22024
  "ipaddresses": [
22025
22025
  {
22026
22026
  "macaddress": "11:22:33:aa:bb:cc",
22027
- "created_at": "2026-09-24T12:38:16.662475+02:00",
22028
- "updated_at": "2026-09-24T12:38:16.872318+02:00",
22027
+ "created_at": "2026-10-16T12:54:34.323181+02:00",
22028
+ "updated_at": "2026-10-16T12:54:34.531313+02:00",
22029
22029
  "ipaddress": "10.0.0.10",
22030
22030
  "host": 19
22031
22031
  }
@@ -22034,8 +22034,8 @@
22034
22034
  "mxs": [],
22035
22035
  "txts": [
22036
22036
  {
22037
- "created_at": "2026-09-24T12:38:16.484425+02:00",
22038
- "updated_at": "2026-09-24T12:38:16.484490+02:00",
22037
+ "created_at": "2026-10-16T12:54:34.189966+02:00",
22038
+ "updated_at": "2026-10-16T12:54:34.190031+02:00",
22039
22039
  "txt": "v=spf1 -all",
22040
22040
  "host": 19
22041
22041
  }
@@ -22053,12 +22053,12 @@
22053
22053
  "contacts": [
22054
22054
  {
22055
22055
  "email": "hi@ho.com",
22056
- "created_at": "2026-09-24T12:38:16.505257+02:00",
22057
- "updated_at": "2026-09-24T12:38:16.505343+02:00"
22056
+ "created_at": "2026-10-16T12:54:34.213061+02:00",
22057
+ "updated_at": "2026-10-16T12:54:34.213129+02:00"
22058
22058
  }
22059
22059
  ],
22060
- "created_at": "2026-09-24T12:38:16.471534+02:00",
22061
- "updated_at": "2026-09-24T12:38:20.920521+02:00",
22060
+ "created_at": "2026-10-16T12:54:34.177486+02:00",
22061
+ "updated_at": "2026-10-16T12:54:37.163243+02:00",
22062
22062
  "name": "bar.example.org",
22063
22063
  "ttl": null,
22064
22064
  "comment": "This is the comment",
@@ -26113,10 +26113,10 @@
26113
26113
  "command_filter_negate": false,
26114
26114
  "command_issued": "host srv_add -name \"whatever\" -priority 1 -weight 1 -port 80 -host baz # doesn't work",
26115
26115
  "ok": [],
26116
- "warning": [
26116
+ "warning": [],
26117
+ "error": [
26117
26118
  "400 Bad Request: POST http://127.0.0.1:8000/api/v1/srvs/\n1 error:\n name: Must match: ^_[a-z0-9]+(([a-z0-9][_-]?)+[a-z0-9]+)?._(tcp|tls|udp)\\. (invalid)"
26118
26119
  ],
26119
- "error": [],
26120
26120
  "output": [],
26121
26121
  "api_requests": [
26122
26122
  {
@@ -26128,15 +26128,15 @@
26128
26128
  "ipaddresses": [
26129
26129
  {
26130
26130
  "macaddress": "11:22:33:aa:bb:cc",
26131
- "created_at": "2026-09-24T12:38:16.662475+02:00",
26132
- "updated_at": "2026-09-24T12:38:25.375340+02:00",
26131
+ "created_at": "2026-10-16T12:54:34.323181+02:00",
26132
+ "updated_at": "2026-10-16T12:54:40.816516+02:00",
26133
26133
  "ipaddress": "10.0.0.10",
26134
26134
  "host": 20
26135
26135
  },
26136
26136
  {
26137
26137
  "macaddress": "11:22:33:44:55:67",
26138
- "created_at": "2026-09-24T12:38:26.218910+02:00",
26139
- "updated_at": "2026-09-24T12:38:29.491713+02:00",
26138
+ "created_at": "2026-10-16T12:54:41.501492+02:00",
26139
+ "updated_at": "2026-10-16T12:54:43.873479+02:00",
26140
26140
  "ipaddress": "2001:db8::14",
26141
26141
  "host": 20
26142
26142
  }
@@ -26145,8 +26145,8 @@
26145
26145
  "mxs": [],
26146
26146
  "txts": [
26147
26147
  {
26148
- "created_at": "2026-09-24T12:38:24.816917+02:00",
26149
- "updated_at": "2026-09-24T12:38:24.816973+02:00",
26148
+ "created_at": "2026-10-16T12:54:40.368541+02:00",
26149
+ "updated_at": "2026-10-16T12:54:40.368622+02:00",
26150
26150
  "txt": "v=spf1 -all",
26151
26151
  "host": 20
26152
26152
  }
@@ -26162,8 +26162,8 @@
26162
26162
  "bacnetid": null,
26163
26163
  "communities": [],
26164
26164
  "contacts": [],
26165
- "created_at": "2026-09-24T12:38:24.804365+02:00",
26166
- "updated_at": "2026-09-24T12:38:24.804423+02:00",
26165
+ "created_at": "2026-10-16T12:54:40.355234+02:00",
26166
+ "updated_at": "2026-10-16T12:54:40.355299+02:00",
26167
26167
  "name": "baz.example.org",
26168
26168
  "ttl": null,
26169
26169
  "comment": "",
@@ -26180,18 +26180,18 @@
26180
26180
  "zone": {
26181
26181
  "nameservers": [
26182
26182
  {
26183
- "created_at": "2026-09-24T12:37:29.215087+02:00",
26184
- "updated_at": "2026-09-24T12:37:29.215169+02:00",
26183
+ "created_at": "2026-10-16T12:53:57.454405+02:00",
26184
+ "updated_at": "2026-10-16T12:53:57.454474+02:00",
26185
26185
  "name": "ns2.example.org",
26186
26186
  "ttl": null
26187
26187
  }
26188
26188
  ],
26189
- "created_at": "2026-09-24T12:37:28.770845+02:00",
26190
- "updated_at": "2026-09-24T12:38:36.267338+02:00",
26189
+ "created_at": "2026-10-16T12:53:57.061219+02:00",
26190
+ "updated_at": "2026-10-16T12:54:49.256682+02:00",
26191
26191
  "updated": true,
26192
26192
  "primary_ns": "ns2.example.org",
26193
26193
  "email": "hostperson@example.org",
26194
- "serialno_updated_at": "2026-09-24T12:37:32.222443+02:00",
26194
+ "serialno_updated_at": "2026-10-16T12:53:57.561895+02:00",
26195
26195
  "refresh": 360,
26196
26196
  "retry": 1800,
26197
26197
  "expire": 2400,
@@ -26210,18 +26210,18 @@
26210
26210
  "zone": {
26211
26211
  "nameservers": [
26212
26212
  {
26213
- "created_at": "2026-09-24T12:37:29.215087+02:00",
26214
- "updated_at": "2026-09-24T12:37:29.215169+02:00",
26213
+ "created_at": "2026-10-16T12:53:57.454405+02:00",
26214
+ "updated_at": "2026-10-16T12:53:57.454474+02:00",
26215
26215
  "name": "ns2.example.org",
26216
26216
  "ttl": null
26217
26217
  }
26218
26218
  ],
26219
- "created_at": "2026-09-24T12:37:28.770845+02:00",
26220
- "updated_at": "2026-09-24T12:38:36.267338+02:00",
26219
+ "created_at": "2026-10-16T12:53:57.061219+02:00",
26220
+ "updated_at": "2026-10-16T12:54:49.256682+02:00",
26221
26221
  "updated": true,
26222
26222
  "primary_ns": "ns2.example.org",
26223
26223
  "email": "hostperson@example.org",
26224
- "serialno_updated_at": "2026-09-24T12:37:32.222443+02:00",
26224
+ "serialno_updated_at": "2026-10-16T12:53:57.561895+02:00",
26225
26225
  "refresh": 360,
26226
26226
  "retry": 1800,
26227
26227
  "expire": 2400,
@@ -36964,10 +36964,10 @@
36964
36964
  "command_filter_negate": false,
36965
36965
  "command_issued": "network community_host_add tinierhost guests",
36966
36966
  "ok": [],
36967
- "warning": [
36967
+ "warning": [],
36968
+ "error": [
36968
36969
  "406 Not Acceptable: POST http://127.0.0.1:8000/api/v1/networks/10.0.2.0/28/communities/1/hosts/\n1 error:\n The IP must have a MAC address to bind it to a community. (not_acceptable)"
36969
36970
  ],
36970
- "error": [],
36971
36971
  "output": [],
36972
36972
  "api_requests": [
36973
36973
  {
@@ -36979,8 +36979,8 @@
36979
36979
  "ipaddresses": [
36980
36980
  {
36981
36981
  "macaddress": "",
36982
- "created_at": "2026-09-24T12:39:13.141038+02:00",
36983
- "updated_at": "2026-09-24T12:39:13.141093+02:00",
36982
+ "created_at": "2026-10-16T12:55:17.277750+02:00",
36983
+ "updated_at": "2026-10-16T12:55:17.277816+02:00",
36984
36984
  "ipaddress": "10.0.2.5",
36985
36985
  "host": 31
36986
36986
  }
@@ -36989,8 +36989,8 @@
36989
36989
  "mxs": [],
36990
36990
  "txts": [
36991
36991
  {
36992
- "created_at": "2026-09-24T12:39:12.984388+02:00",
36993
- "updated_at": "2026-09-24T12:39:12.984445+02:00",
36992
+ "created_at": "2026-10-16T12:55:17.136938+02:00",
36993
+ "updated_at": "2026-10-16T12:55:17.136998+02:00",
36994
36994
  "txt": "v=spf1 -all",
36995
36995
  "host": 31
36996
36996
  }
@@ -37008,12 +37008,12 @@
37008
37008
  "contacts": [
37009
37009
  {
37010
37010
  "email": "tinierhost@example.org",
37011
- "created_at": "2026-09-24T12:39:13.009905+02:00",
37012
- "updated_at": "2026-09-24T12:39:13.009988+02:00"
37011
+ "created_at": "2026-10-16T12:55:17.156360+02:00",
37012
+ "updated_at": "2026-10-16T12:55:17.156429+02:00"
37013
37013
  }
37014
37014
  ],
37015
- "created_at": "2026-09-24T12:39:12.969826+02:00",
37016
- "updated_at": "2026-09-24T12:39:12.969886+02:00",
37015
+ "created_at": "2026-10-16T12:55:17.125199+02:00",
37016
+ "updated_at": "2026-10-16T12:55:17.125265+02:00",
37017
37017
  "name": "tinierhost.example.org",
37018
37018
  "ttl": null,
37019
37019
  "comment": "",
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.12.0'
22
- __version_tuple__ = version_tuple = (1, 12, 0)
21
+ __version__ = version = '1.12.2'
22
+ __version_tuple__ = version_tuple = (1, 12, 2)
23
23
 
24
- __commit_id__ = commit_id = 'g28d209847'
24
+ __commit_id__ = commit_id = 'gf1f8228d3'
@@ -40,7 +40,7 @@ from mreg_cli.commands.zone import ZoneCommands
40
40
  from mreg_cli.config import MregCliConfig
41
41
 
42
42
  # Import other mreg_cli modules
43
- from mreg_cli.exceptions import CliError, CliExit, CliWarning, handle_exception, handle_exceptions
43
+ from mreg_cli.exceptions import CliError, CliExit, CliWarning, handle_and_exit, handle_exception
44
44
  from mreg_cli.help_formatter import CustomHelpFormatter
45
45
  from mreg_cli.outputmanager import OutputManager
46
46
  from mreg_cli.types import CommandFunc, Flag
@@ -198,7 +198,7 @@ class Command(Completer):
198
198
  if isinstance(exc, mreg_api.exceptions.ResponseError):
199
199
  # Retry command after re-authenticating if we got a 401 Unauthorized
200
200
  if exc.response.status_code == 401 and interactive and not retry:
201
- with handle_exceptions(json=False):
201
+ with handle_and_exit(json=False):
202
202
  prompt_for_password_and_try_update_token()
203
203
  return self.parse(command, interactive=interactive, retry=True)
204
204
  handle_exception(exc)
@@ -176,7 +176,7 @@ class MregCliClient(MregClient, metaclass=_AutoListenersMeta):
176
176
  host = getter(ident_str)
177
177
  if host is not None:
178
178
  return host
179
- except Exception:
179
+ except ValueError: # invalid identifier for this getter
180
180
  pass
181
181
 
182
182
  # Fall back to CNAME
@@ -230,25 +230,18 @@ class MregCliClient(MregClient, metaclass=_AutoListenersMeta):
230
230
  Resolution order: IP address → CIDR / network address → numeric id.
231
231
  """
232
232
  network: Network | None = None
233
+
233
234
  try:
234
- # Try as IP first
235
235
  network = self.network.get_by_ip(identifier, required=False)
236
- except Exception:
236
+ except ValueError: # invalid IP address
237
237
  pass
238
238
 
239
239
  if network is None:
240
- try:
241
- # Try as CIDR / network address
242
- network = self.network.get(identifier, required=False)
243
- except Exception:
244
- pass
240
+ # Try as CIDR / network address
241
+ network = self.network.get(identifier, required=False)
245
242
 
246
243
  if network is None and identifier.isdecimal():
247
- try:
248
- network = self.network.first(id=int(identifier), required=False)
249
- # network = self.network.get(int(identifier), required=False)
250
- except Exception:
251
- pass
244
+ network = self.network.first(id=int(identifier), required=False)
252
245
 
253
246
  if network is None and required:
254
247
  raise CliEntityNotFound(f"Network {identifier!r} not found.")
@@ -106,32 +106,28 @@ class TooManyResults(CliWarning):
106
106
  """API returned too many results."""
107
107
 
108
108
 
109
- # FIXME: Inconsistent handling of HTTP errors in the original CLI implementation
110
- # DELETE errors were considered errors, while other HTTP errors were
111
- # considered warnings.
112
109
  _MREG_API_ERROR_EXCEPTIONS = (
113
- mreg_api.exceptions.DeleteError,
110
+ mreg_api.exceptions.ResponseError,
114
111
  mreg_api.exceptions.InternalError,
115
112
  mreg_api.exceptions.MregValidationError,
116
113
  )
117
114
 
118
115
 
119
- def is_error(exc: Exception) -> bool:
116
+ def is_error(exc: BaseException) -> bool:
120
117
  """Determine if an exception should produce an error or warning."""
121
118
  # mreg_cli errors
122
119
  if isinstance(exc, CliError):
123
120
  return True
124
121
  elif isinstance(exc, CliWarning):
125
122
  return False
126
-
127
123
  # mreg_api exceptions that should be treated as errors
128
- if isinstance(exc, _MREG_API_ERROR_EXCEPTIONS):
124
+ elif isinstance(exc, _MREG_API_ERROR_EXCEPTIONS):
129
125
  return True
130
126
 
131
127
  return False
132
128
 
133
129
 
134
- def get_exception_message(exc: Exception, *, json: bool = False) -> str:
130
+ def get_exception_message(exc: BaseException, *, json: bool = False) -> str:
135
131
  """Get the plain text message from an exception.
136
132
 
137
133
  :param exc: The exception to get the message from.
@@ -159,14 +155,15 @@ class ExceptionHandler:
159
155
  is_validation_error: True if the exception is a ValidationError.
160
156
  message: The plain text message from the exception.
161
157
  formatted_message: HTML-formatted message for prompt_toolkit display.
158
+ json: Whether to display mreg_api exceptions as their original JSON representation.
162
159
 
163
160
  """
164
161
 
165
- def __init__(self, exc: Exception, *, json: bool = False) -> None:
162
+ def __init__(self, exc: BaseException, *, json: bool = False) -> None:
166
163
  """Initialize the handler with an exception.
167
164
 
168
165
  :param exc: The exception to handle.
169
- :param json: Whether to include JSON details for mreg_api exceptions.
166
+ :param json: Display mreg_api exceptions as their original JSON representation.
170
167
  """
171
168
  self.original_exception = exc
172
169
  self.exception = self._transform_exception(exc)
@@ -180,7 +177,7 @@ class ExceptionHandler:
180
177
  self.message = get_exception_message(self.exception, json=self._json)
181
178
  self.formatted_message = self.get_formatted_message()
182
179
 
183
- def _transform_exception(self, exc: Exception) -> Exception:
180
+ def _transform_exception(self, exc: BaseException) -> BaseException:
184
181
  """Transform an exception into an appropriate mreg_cli exception if needed."""
185
182
  if isinstance(exc, ValidationError):
186
183
  return mreg_api.exceptions.MregValidationError.from_pydantic(exc)
@@ -235,7 +232,7 @@ class ExceptionHandler:
235
232
  self.print()
236
233
 
237
234
 
238
- def handle_exception(exc: Exception) -> None:
235
+ def handle_exception(exc: BaseException, json: bool = False) -> None:
239
236
  """Log and print an exception with appropriate formatting.
240
237
 
241
238
  This is the main entry point for exception handling.
@@ -244,17 +241,24 @@ def handle_exception(exc: Exception) -> None:
244
241
  """
245
242
  # TODO: add JSON toggle via config here
246
243
  # possibly via verbosity level/debug flag
247
- ExceptionHandler(exc).handle()
244
+ ExceptionHandler(exc, json=json).handle()
248
245
 
249
246
 
250
247
  @contextmanager
251
- def handle_exceptions(json: bool = False) -> Generator[None, None, None]:
248
+ def handle_and_exit(
249
+ *exc_cls: type[BaseException], json: bool = False
250
+ ) -> Generator[None, None, None]:
252
251
  """Context manager to handle exceptions using ExceptionHandler and exit.
253
252
 
253
+ Suppresses traceback and shows a formatted message in the console.
254
+
255
+ :param exc_cls: The exception class(es) to handle. Defaults to (Exception,).
254
256
  :param json: Whether to include JSON details for mreg_api exceptions.
255
257
  """
258
+ if not exc_cls:
259
+ exc_cls = (Exception,)
256
260
  try:
257
261
  yield
258
- except Exception as exc:
259
- ExceptionHandler(exc, json=json).handle()
262
+ except exc_cls as exc:
263
+ handle_exception(exc, json=json)
260
264
  sys.exit(1)
@@ -14,7 +14,7 @@ from mreg_cli.__about__ import __version__
14
14
  from mreg_cli.cli import cli, get_cli_history, source
15
15
  from mreg_cli.client import init_client
16
16
  from mreg_cli.config import MregCliConfig
17
- from mreg_cli.exceptions import CliException, LoginFailedError, handle_exception
17
+ from mreg_cli.exceptions import CliException, handle_and_exit, handle_exception
18
18
  from mreg_cli.log import MregCliLogger
19
19
  from mreg_cli.outputmanager import OutputManager
20
20
  from mreg_cli.prompt import get_prompt_message
@@ -183,18 +183,12 @@ def main():
183
183
  # Bootstrap the application-wide client object
184
184
  client = init_client(config)
185
185
 
186
- try:
186
+ with handle_and_exit(Exception, KeyboardInterrupt):
187
187
  try_token_or_login(
188
188
  config.user,
189
189
  config.url,
190
190
  fail_without_token=config.token_only,
191
191
  )
192
- except (EOFError, KeyboardInterrupt, LoginFailedError) as e:
193
- if isinstance(e, LoginFailedError):
194
- handle_exception(e)
195
- else:
196
- print(e)
197
- raise SystemExit() from None
198
192
 
199
193
  if not client.get_token():
200
194
  logger.error("No valid token after login, exiting")
@@ -117,7 +117,9 @@ def auth_and_update_token(username: str, password: str) -> None:
117
117
  except httpx.HTTPError as e:
118
118
  raise CliError(str(e)) from e # should be unreachable
119
119
  except mreg_api.exceptions.LoginFailedError as e:
120
+ # wrap the server API error in a CliError to classify as error + hide URL & drf details
120
121
  raise LoginFailedError(e.error_message) from e
122
+ # Other MREG API exceptions fall through
121
123
  else:
122
124
  TokenFile.set_entry(username, base_url, token)
123
125
  logger.info("Token updated for %s @ %s", username, tokenurl)