wlc 2.2.0__tar.gz → 2.2.1__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 (136) hide show
  1. wlc-2.2.0/CHANGES.rst → wlc-2.2.1/CHANGES.md +62 -84
  2. {wlc-2.2.0 → wlc-2.2.1}/MANIFEST.in +1 -1
  3. {wlc-2.2.0 → wlc-2.2.1}/PKG-INFO +1 -1
  4. {wlc-2.2.0 → wlc-2.2.1}/THREAT_MODEL.md +8 -6
  5. {wlc-2.2.0 → wlc-2.2.1}/pyproject.toml +34 -34
  6. {wlc-2.2.0 → wlc-2.2.1}/tests/test_base.py +2 -2
  7. {wlc-2.2.0 → wlc-2.2.1}/tests/test_download.py +99 -10
  8. {wlc-2.2.0 → wlc-2.2.1}/tests/test_errors.py +58 -6
  9. {wlc-2.2.0 → wlc-2.2.1}/tests/test_main.py +12 -1
  10. {wlc-2.2.0 → wlc-2.2.1}/tests/test_wlc.py +54 -0
  11. {wlc-2.2.0 → wlc-2.2.1}/wlc/client.py +12 -4
  12. {wlc-2.2.0 → wlc-2.2.1}/wlc/const.py +1 -1
  13. {wlc-2.2.0 → wlc-2.2.1}/wlc/main.py +26 -20
  14. {wlc-2.2.0 → wlc-2.2.1}/wlc/models.py +5 -1
  15. {wlc-2.2.0 → wlc-2.2.1}/wlc.egg-info/PKG-INFO +1 -1
  16. {wlc-2.2.0 → wlc-2.2.1}/wlc.egg-info/SOURCES.txt +1 -1
  17. {wlc-2.2.0 → wlc-2.2.1}/LICENSE +0 -0
  18. {wlc-2.2.0 → wlc-2.2.1}/LICENSES/CC0-1.0.txt +0 -0
  19. {wlc-2.2.0 → wlc-2.2.1}/LICENSES/GPL-3.0-or-later.txt +0 -0
  20. {wlc-2.2.0 → wlc-2.2.1}/README.md +0 -0
  21. {wlc-2.2.0 → wlc-2.2.1}/SECURITY.md +0 -0
  22. {wlc-2.2.0 → wlc-2.2.1}/completion/wlc +0 -0
  23. {wlc-2.2.0 → wlc-2.2.1}/setup.cfg +0 -0
  24. {wlc-2.2.0 → wlc-2.2.1}/tests/__init__.py +0 -0
  25. {wlc-2.2.0 → wlc-2.2.1}/tests/test_config.py +0 -0
  26. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/.weblate +0 -0
  27. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/categories +0 -0
  28. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/categories-1 +0 -0
  29. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/changes +0 -0
  30. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components +0 -0
  31. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-android +0 -0
  32. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-android-file +0 -0
  33. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-android-file--GET--format=zip +0 -0
  34. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-olderweblate +0 -0
  35. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate +0 -0
  36. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate--DELETE-- +0 -0
  37. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate--PATCH--priority=80 +0 -0
  38. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-changes +0 -0
  39. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-file +0 -0
  40. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-file--GET--format=zip +0 -0
  41. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-lock +0 -0
  42. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-lock--POST--lock=0 +0 -0
  43. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-lock--POST--lock=1 +0 -0
  44. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-lock--POST--lock=false +0 -0
  45. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-lock--POST--lock=true +0 -0
  46. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-repository +0 -0
  47. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-repository--POST--operation=cleanup +0 -0
  48. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-repository--POST--operation=commit +0 -0
  49. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-repository--POST--operation=pull +0 -0
  50. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-repository--POST--operation=push +0 -0
  51. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-repository--POST--operation=reset +0 -0
  52. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-statistics +0 -0
  53. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-statistics--GET--page=2 +0 -0
  54. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-statistics--GET--page=3 +0 -0
  55. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-translations +0 -0
  56. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-translations--GET--page=2 +0 -0
  57. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/components-hello-weblate-translations--POST--language_code=nl_BE +0 -0
  58. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/languages +0 -0
  59. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/languages--GET--page=2 +0 -0
  60. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/languages--GET--page=3 +0 -0
  61. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/languages--POST--code=tst--name=Test-Language--direction=rtl--plural=number=2--formula=n-!=-1 +0 -0
  62. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects +0 -0
  63. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects--POST--name=Hello--slug=hello--web=http---example.com---source_language=name=Malayalam--code=ml +0 -0
  64. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects--POST--name=denied_json--slug=denide_json--web=http---some-weblate-app.com- +0 -0
  65. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-acl +0 -0
  66. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-empty +0 -0
  67. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-empty-components +0 -0
  68. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello +0 -0
  69. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello--DELETE-- +0 -0
  70. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello-categories +0 -0
  71. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello-changes +0 -0
  72. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello-components +0 -0
  73. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello-components--POST--3fcd0ab4 +0 -0
  74. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello-components--POST--c9c26539 +0 -0
  75. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello-languages +0 -0
  76. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello-repository +0 -0
  77. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello-repository--POST--operation=cleanup +0 -0
  78. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello-repository--POST--operation=commit +0 -0
  79. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello-repository--POST--operation=pull +0 -0
  80. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello-repository--POST--operation=push +0 -0
  81. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello-repository--POST--operation=reset +0 -0
  82. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-hello-statistics +0 -0
  83. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/projects-invalid +0 -0
  84. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations +0 -0
  85. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations--GET--page=2 +0 -0
  86. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations--GET--page=3 +0 -0
  87. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-android-en-units +0 -0
  88. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-android-en-units--POST--key=test-monolingual--value=-test-me- +0 -0
  89. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs +0 -0
  90. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs--DELETE-- +0 -0
  91. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-changes +0 -0
  92. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-file +0 -0
  93. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-file--GET--format=csv +0 -0
  94. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-file--POST--3748506a +0 -0
  95. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-file--POST--5c6a101f +0 -0
  96. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-file--POST--85762547 +0 -0
  97. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-file--POST--90a4957d +0 -0
  98. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-file--POST--f8672b4c +0 -0
  99. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-file--POST--fe040a0c +0 -0
  100. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-repository +0 -0
  101. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-repository--POST--operation=cleanup +0 -0
  102. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-repository--POST--operation=commit +0 -0
  103. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-repository--POST--operation=pull +0 -0
  104. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-repository--POST--operation=push +0 -0
  105. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-repository--POST--operation=reset +0 -0
  106. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-statistics +0 -0
  107. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-units +0 -0
  108. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/translations-hello-weblate-cs-units--GET--q=source%3A%3D%22mr%22 +0 -0
  109. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/units +0 -0
  110. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/units-123 +0 -0
  111. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/units-123--DELETE-- +0 -0
  112. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/units-123--PATCH--target=-foo---state=30 +0 -0
  113. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/api/units-123--PUT--target=-foo---state=30 +0 -0
  114. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/mock/project-local-file.pot +0 -0
  115. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/section +0 -0
  116. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/weblate.ini +0 -0
  117. {wlc-2.2.0 → wlc-2.2.1}/tests/test_data/wlc +0 -0
  118. {wlc-2.2.0 → wlc-2.2.1}/tests/test_insecure_http.py +0 -0
  119. {wlc-2.2.0 → wlc-2.2.1}/tests/test_insecure_ssl.py +0 -0
  120. {wlc-2.2.0 → wlc-2.2.1}/tests/test_package.py +0 -0
  121. {wlc-2.2.0 → wlc-2.2.1}/tests/test_statistics.py +0 -0
  122. {wlc-2.2.0 → wlc-2.2.1}/tests/test_urls.py +0 -0
  123. {wlc-2.2.0 → wlc-2.2.1}/tests/test_utils.py +0 -0
  124. {wlc-2.2.0 → wlc-2.2.1}/wl +0 -0
  125. {wlc-2.2.0 → wlc-2.2.1}/wlc/__init__.py +0 -0
  126. {wlc-2.2.0 → wlc-2.2.1}/wlc/base.py +0 -0
  127. {wlc-2.2.0 → wlc-2.2.1}/wlc/config.py +0 -0
  128. {wlc-2.2.0 → wlc-2.2.1}/wlc/exceptions.py +0 -0
  129. {wlc-2.2.0 → wlc-2.2.1}/wlc/http_debug.py +0 -0
  130. {wlc-2.2.0 → wlc-2.2.1}/wlc/output.py +0 -0
  131. {wlc-2.2.0 → wlc-2.2.1}/wlc/py.typed +0 -0
  132. {wlc-2.2.0 → wlc-2.2.1}/wlc/utils.py +0 -0
  133. {wlc-2.2.0 → wlc-2.2.1}/wlc.egg-info/dependency_links.txt +0 -0
  134. {wlc-2.2.0 → wlc-2.2.1}/wlc.egg-info/entry_points.txt +0 -0
  135. {wlc-2.2.0 → wlc-2.2.1}/wlc.egg-info/requires.txt +0 -0
  136. {wlc-2.2.0 → wlc-2.2.1}/wlc.egg-info/top_level.txt +0 -0
@@ -1,8 +1,20 @@
1
- 2.2.0
2
- -----
1
+ # Changelog
2
+
3
+ ## 2.2.1
4
+
5
+ * Released on 24th September 2026.
6
+ * Prevented API keys from appearing in CLI request errors and rejected API keys
7
+ containing line breaks.
8
+ * Fixed component downloads when a slug contains `repository` and avoided
9
+ relisting an already resolved component.
10
+ * Reported local filesystem failures as CLI errors and validated component
11
+ download output paths before fetching data.
12
+ * Updated development dependencies, CI actions, and Docker base images.
13
+
14
+ ## 2.2.0
3
15
 
4
16
  * Released on 1st September 2026.
5
- * Hardened authentication by disabling automatic ``netrc`` credentials,
17
+ * Hardened authentication by disabling automatic `netrc` credentials,
6
18
  rejecting credentials embedded in URLs, and redacting credentials from HTTP
7
19
  failure logs.
8
20
  * Scoped insecure HTTP and TLS exceptions to explicitly trusted origins and
@@ -14,8 +26,7 @@
14
26
  * Added a threat model and clarified configuration discovery and precedence.
15
27
  * Updated development dependencies, CI actions, and Docker base images.
16
28
 
17
- 2.1.1
18
- -----
29
+ ## 2.1.1
19
30
 
20
31
  * Released on 25th July 2026.
21
32
  * Prevented URL parser differential SSRF in server-provided URLs by validating
@@ -23,80 +34,71 @@
23
34
  * Improved type annotations.
24
35
  * Updated development dependencies, CI actions, and Docker base images.
25
36
 
26
- 2.1.0
27
- -----
37
+ ## 2.1.0
28
38
 
29
39
  * Released on 30th June 2026.
30
- * Refuse sending API keys over non-local ``http://`` URLs by default, while
40
+ * Refuse sending API keys over non-local `http://` URLs by default, while
31
41
  keeping loopback HTTP supported for local development and adding explicit
32
- opt-in via ``--allow-insecure-http``, ``WLC_ALLOW_INSECURE_HTTP``, or trusted
42
+ opt-in via `--allow-insecure-http`, `WLC_ALLOW_INSECURE_HTTP`, or trusted
33
43
  configuration.
34
44
  * Fixed paginated listings with query parameters to avoid duplicating
35
- parameters from API-provided ``next`` URLs.
36
- * Updated minimum supported ``requests`` and ``urllib3`` versions.
45
+ parameters from API-provided `next` URLs.
46
+ * Updated minimum supported `requests` and `urllib3` versions.
37
47
  * Improved inline API documentation and output handling internals.
38
48
  * Updated development dependencies, CI actions, and Docker base images.
39
49
 
40
- 2.0.1
41
- -----
50
+ ## 2.0.1
42
51
 
43
52
  * Released on 26th June 2026.
44
53
  * Require explicit URL pinning for unscoped API keys when the API URL comes
45
- from automatically discovered project configuration: ``WLC_KEY`` requires
46
- ``WLC_URL`` and ``--key`` requires ``--url``.
47
- * Removed ``TRACE`` from the default retryable HTTP methods.
54
+ from automatically discovered project configuration: `WLC_KEY` requires
55
+ `WLC_URL` and `--key` requires `--url`.
56
+ * Removed `TRACE` from the default retryable HTTP methods.
48
57
  * Added package and container provenance attestations and SBOM publishing for
49
58
  release artifacts.
50
59
  * Updated development dependencies, CI actions, and Docker base images.
51
60
 
52
- 2.0.0
53
- -----
61
+ ## 2.0.0
54
62
 
55
63
  * Released on 21st April 2026.
56
64
  * Possibly breaking changes:
57
65
 
58
- * Renamed the retry configuration option from ``method_whitelist`` to ``allowed_methods``.
59
- * Tightened configuration loading: explicit ``--config`` is authoritative, missing explicit config files fail, and project discovery uses the nearest config file.
66
+ * Renamed the retry configuration option from `method_whitelist` to `allowed_methods`.
67
+ * Tightened configuration loading: explicit `--config` is authoritative, missing explicit config files fail, and project discovery uses the nearest config file.
60
68
  * Tightened URL, download, and output safety: invalid or cross-origin URLs are rejected, binary downloads to terminals are refused, and text, CSV, and HTML output is escaped.
61
-
62
- * Added ``WLC_URL`` and ``WLC_KEY`` environment variable support.
69
+ * Added `WLC_URL` and `WLC_KEY` environment variable support.
63
70
  * Added unit-level CLI commands.
64
71
  * Updated models and statistics for the current Weblate API while preserving older statistics responses.
65
72
  * Fixed retry configuration and request adapter wiring.
66
- * Escaped generated HTML output (`GHSA-gx2m-mcc2-r4p3 <https://github.com/WeblateOrg/wlc/security/advisories/GHSA-gx2m-mcc2-r4p3>`__), hardened CSV and terminal output, and redacted authorization headers from debug logs.
73
+ * Escaped generated HTML output ([GHSA-gx2m-mcc2-r4p3](https://github.com/WeblateOrg/wlc/security/advisories/GHSA-gx2m-mcc2-r4p3)), hardened CSV and terminal output, and redacted authorization headers from debug logs.
67
74
  * Improved nested category handling, unit editing, help texts, and error messages.
68
75
  * Split implementation modules, moved tests out of the installed package, and updated packaging, dependencies, Docker images, and CI.
69
76
 
70
- 1.17.2
71
- ------
77
+ ## 1.17.2
72
78
 
73
79
  * Released on 16th January 2026.
74
80
  * Fixes path traversal: Unsanitized API slugs in download command (CVE-2026-23535).
75
81
 
76
- 1.17.1
77
- ------
82
+ ## 1.17.1
78
83
 
79
84
  * Released on 13th January 2026.
80
- * Unscoped ``key`` in configuration triggers error.
85
+ * Unscoped `key` in configuration triggers error.
81
86
 
82
- 1.17.0
83
- ------
87
+ ## 1.17.0
84
88
 
85
89
  * Released on 12th January 2026.
86
- * Configuration change might be needed to move API keys to a new location, see https://docs.weblate.org/en/latest/wlc.html#legacy-configuration.
87
- * Dropped support for unscoped ``key`` in configuration (CVE-2026-22251).
90
+ * Configuration change might be needed to move API keys to a new location, see <https://docs.weblate.org/en/latest/wlc.html#legacy-configuration>.
91
+ * Dropped support for unscoped `key` in configuration (CVE-2026-22251).
88
92
  * Tightened hostname-based SSL verification skip (CVE-2026-22250).
89
93
  * Modernized packaging.
90
94
  * Improved type annotations.
91
95
 
92
- 1.16.1
93
- ------
96
+ ## 1.16.1
94
97
 
95
98
  * Released on 10th October 2025.
96
99
  * Fixed packages publishing.
97
100
 
98
- 1.16
99
- ----
101
+ ## 1.16
100
102
 
101
103
  * Released on 10th October 2025.
102
104
  * Fixed downloading components based on configuration.
@@ -105,14 +107,12 @@
105
107
  * Dropped support for Python 3.8 and 3.9.
106
108
  * Added support for Python 3.13 and 3.14.
107
109
 
108
- 1.15
109
- ----
110
+ ## 1.15
110
111
 
111
112
  * Released on 3rd September 2024.
112
113
  * Fixed installation on certain filesystems.
113
114
 
114
- 1.14
115
- ----
115
+ ## 1.14
116
116
 
117
117
  * Released on 23rd February 2024.
118
118
  * Dropped support for Python 3.6 and 3.7.
@@ -120,8 +120,7 @@
120
120
  * Added support for categories.
121
121
  * Added support for the linked_component field.
122
122
 
123
- 1.13
124
- ----
123
+ ## 1.13
125
124
 
126
125
  * Released on 24th March 2022.
127
126
  * Support all wlc upload methods.
@@ -129,23 +128,20 @@
129
128
  * Allow uploading files when creating a component.
130
129
  * Allow downloading at component, project, and site level.
131
130
 
132
- 1.12
133
- ----
131
+ ## 1.12
134
132
 
135
133
  * Released on 19th May 2021.
136
134
  * Improved error messages when permission was denied.
137
135
  * Added type hints to the codebase.
138
136
 
139
- 1.11
140
- ----
137
+ ## 1.11
141
138
 
142
139
  * Released on 17th March 2021.
143
140
  * Fixed long filenames in test fixtures.
144
141
  * Updated base docker image.
145
142
  * Add support for specifying format when uploading files.
146
143
 
147
- 1.10
148
- ----
144
+ ## 1.10
149
145
 
150
146
  * Released on 11th February 2021.
151
147
  * Added ability to create new projects, components, and translations
@@ -158,46 +154,39 @@
158
154
  * Using requests.session to keep a single http request open instead of reinitializing them.
159
155
  * Added timeout config variable
160
156
 
161
- 1.9
162
- ---
157
+ ## 1.9
163
158
 
164
159
  * Released on 21st December 2020.
165
160
  * Added Docker image published as weblate/wlc on Docker Hub.
166
161
 
167
- 1.8
168
- ---
162
+ ## 1.8
169
163
 
170
164
  * Released on 11th September 2020.
171
165
  * Compatibility with Weblate 4.3 which moves source language from component to a project.
172
166
 
173
- 1.7
174
- ---
167
+ ## 1.7
175
168
 
176
169
  * Released on 3rd September 2020.
177
170
  * Fixed installation of wlc command-line.
178
171
 
179
- 1.6
180
- ---
172
+ ## 1.6
181
173
 
182
174
  * Released on 1st September 2020.
183
175
  * Post payload as JSON to allow handling complex data structures.
184
176
  * Added support for finding configuration file on Windows in AppData dir.
185
177
  * Improved error reporting.
186
178
 
187
- 1.5
188
- ---
179
+ ## 1.5
189
180
 
190
181
  * Released on 19th June 2020.
191
182
  * Fixed compatibility of some API calls with Weblate 4.1.
192
183
 
193
- 1.4
194
- ---
184
+ ## 1.4
195
185
 
196
186
  * Released on 3rd June 2020.
197
187
  * Fixed compatibility of some API calls with Weblate 4.1.
198
188
 
199
- 1.3
200
- ---
189
+ ## 1.3
201
190
 
202
191
  * Released on 6th May 2020.
203
192
  * Mark Python 3.8 as supported, dropped support for Python 3.5.
@@ -207,8 +196,7 @@
207
196
  * Added support for filtering components and translations.
208
197
  * Better report errors when accessing API.
209
198
 
210
- 1.2
211
- ---
199
+ ## 1.2
212
200
 
213
201
  * Released on 15th October 2019.
214
202
  * Fix stats invocation.
@@ -216,42 +204,36 @@
216
204
  * Added support for replace upload.
217
205
  * Added support for project, component and translation removal.
218
206
 
219
- 1.1
220
- ---
207
+ ## 1.1
221
208
 
222
209
  * Released on 1st February, 2019.
223
210
  * Fixed listing of language objects.
224
211
 
225
- 1.0
226
- ---
212
+ ## 1.0
227
213
 
228
214
  * Released on 31st January, 2019.
229
215
  * Added support for more parameters on file upload.
230
216
 
231
- 0.10
232
- ----
217
+ ## 0.10
233
218
 
234
219
  * Released on 21th October, 2018.
235
220
  * Fixed POST operations in the API.
236
221
  * Added --debug parameter to diagnose HTTP problems.
237
222
 
238
- 0.9
239
- ---
223
+ ## 0.9
240
224
 
241
225
  * Released on 17th October, 2018.
242
226
  * Switched to requests
243
227
  * Added support for cleanup command.
244
228
  * Added support for upload command.
245
229
 
246
- 0.8
247
- ---
230
+ ## 0.8
248
231
 
249
232
  * Released on 3rd March, 2017.
250
233
  * Various code cleanups.
251
234
  * Tested with Python 3.6.
252
235
 
253
- 0.7
254
- ---
236
+ ## 0.7
255
237
 
256
238
  * Released on 16th December, 2016.
257
239
  * Added reset operation.
@@ -259,27 +241,23 @@
259
241
  * Added changes listing.
260
242
  * Added file downloads.
261
243
 
262
- 0.6
263
- ---
244
+ ## 0.6
264
245
 
265
246
  * Released on 20th September, 2016.
266
247
  * Fixed error when invoked without command.
267
248
  * Tested on Windows and OS X (in addition to Linux).
268
249
 
269
- 0.5
270
- ---
250
+ ## 0.5
271
251
 
272
252
  * Released on 11th July, 2016.
273
253
  * Added locking commands.
274
254
 
275
- 0.4
276
- ---
255
+ ## 0.4
277
256
 
278
257
  * Released on 8th July, 2016.
279
258
  * Moved Git repository.
280
259
 
281
- 0.3
282
- ---
260
+ ## 0.3
283
261
 
284
262
  * Released on 19th May, 2016.
285
263
  * First version for general usage.
@@ -4,7 +4,7 @@ include SECURITY.md
4
4
  include LICENSE
5
5
  include wl
6
6
  recursive-include LICENSES *
7
- include CHANGES.rst
7
+ include CHANGES.md
8
8
  include MANIFEST.in
9
9
  include completion/wlc
10
10
  graft tests
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wlc
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: A command-line utility for Weblate, translation tool with tight version control integration
5
5
  Author-email: Michal Čihař <michal@weblate.org>
6
6
  License-Expression: GPL-3.0-or-later
@@ -2,11 +2,11 @@
2
2
 
3
3
  Project: wlc (Weblate command-line client and Python API client)
4
4
 
5
- Last reviewed for wlc 2.2.0 at base commit
6
- `1e63ad96951734ba26ba240f1ccfa80be4a8c645`, including the accompanying
7
- changes in this revision.
5
+ Last reviewed for wlc 2.2.1 at base commit
6
+ `b61361bc7854f084b3b91081e8c1279d1ee057b0`, including the accompanying
7
+ release changes in this revision.
8
8
 
9
- Date: 2026-09-01.
9
+ Date: 2026-09-24.
10
10
 
11
11
  Status: Accepted, 2026-09-01.
12
12
 
@@ -276,12 +276,13 @@ code, Python runtime, or operating system. *(maintainer)*
276
276
  | Server-provided request targets remain on the configured API origin after normalization by the parser used by the HTTP transport. | The request goes through the `Weblate` client. Origin means scheme, normalized host, and effective port. | Pagination, refresh, repository, upload, download, or another response-derived URL causes a request to a different origin. | High when credentials or user data are sent; otherwise Medium. | *(documented)* (source: [client implementation](wlc/client.py), [URL tests](tests/test_urls.py)) |
277
277
  | HTTP redirects are not followed automatically. | The request goes through the `Weblate` client. | A 3xx response causes a follow-up request, or is accepted as a successful API response. | High when credentials or user data cross a boundary; otherwise Medium. | *(documented)* (source: [client implementation](wlc/client.py), [URL tests](tests/test_urls.py)) |
278
278
  | API keys are not sent over non-loopback cleartext HTTP by default. | A key is present and no trusted source explicitly enabled insecure HTTP. The request goes through the `Weblate` client. | The client sends the key to a non-loopback `http://` destination. | High. | *(documented)* (source: [client implementation](wlc/client.py), [HTTP tests](tests/test_insecure_http.py)) |
279
+ | API keys containing carriage returns or line feeds are rejected before HTTP request construction. | The request goes through the `Weblate` client. | A key containing a line break reaches Requests header processing. | High. | *(documented)* (source: [client implementation](wlc/client.py), [client tests](tests/test_wlc.py), [CLI tests](tests/test_main.py)) |
279
280
  | TLS certificates are verified by default and verification exceptions are scoped to the selected origin. | The destination is HTTPS and no trusted source supplied a matching origin or explicit transient exception under the configuration rules. | Verification is disabled for an unrelated origin, or without an explicit trusted opt-in. | High. | *(documented)* (source: [configuration implementation](wlc/config.py), [client implementation](wlc/client.py), [TLS tests](tests/test_insecure_ssl.py)) |
280
281
  | Automatically discovered project configuration cannot enable authenticated insecure HTTP, disable TLS verification, or supply insecure origin entries. | The project file is discovered automatically rather than supplied as explicit configuration. | A project-only setting weakens HTTP or TLS transport, or a CLI/environment exception is accepted without a URL from the same source. | High. | *(documented)* (source: [configuration implementation](wlc/config.py), [configuration tests](tests/test_config.py)) |
281
282
  | An unscoped CLI or environment key cannot be paired with a project-selected URL unless that same source also pins the URL. | Automatic project discovery supplied the otherwise-effective URL. | `--key` is accepted without `--url`, or `WLC_KEY` is accepted without `WLC_URL`. | High. | *(documented)* (source: [configuration implementation](wlc/config.py), [configuration tests](tests/test_config.py)) |
282
283
  | Authentication is accepted only from configured API-key sources; Requests does not automatically add credentials from `.netrc` or the file named by `NETRC`, and credentials embedded in URLs are rejected. | The request goes through the `Weblate` client. | A repository-selected host causes Requests to add or replace authentication using a netrc file, or a URL containing userinfo produces an HTTP request. | High. | *(documented)* (source: [configuration documentation][configuration-docs], [client implementation](wlc/client.py), [client tests](tests/test_wlc.py), [URL tests](tests/test_urls.py)) |
283
284
  | An explicit `--config` file prevents automatic user and project configuration discovery. | The CLI successfully reads the explicit file. CLI/environment overrides still apply normally. | Settings from a discovered user or project file affect the configuration. | Medium. | *(documented)* (source: [configuration implementation](wlc/config.py), [configuration tests](tests/test_config.py)) |
284
- | wlc debug logging redacts authorization and proxy-authorization headers, scrubs their values from request-failure messages, and does not log request bodies or uploaded file contents. | Logging uses wlc's debug helpers. Parameter values can still be logged and must not be used for secrets. | A generated debug entry contains an API token, JSON/body values, or uploaded contents supplied only through the corresponding request fields. | High for credentials; otherwise Medium. | *(documented)* (source: [debug implementation](wlc/http_debug.py), [debug tests](tests/test_errors.py), [CLI debug tests](tests/test_main.py)) |
285
+ | wlc CLI request errors and debug logging redact authorization values, and debug logging does not include request bodies or uploaded file contents. | Requests use the `Weblate` client and errors are rendered by the wlc CLI or debug helpers. Parameter values can still be logged and must not be used for secrets. | A CLI request error or generated debug entry contains an API token, JSON/body values, or uploaded contents supplied only through the corresponding request fields. | High for credentials; otherwise Medium. | *(documented)* (source: [CLI implementation](wlc/main.py), [debug implementation](wlc/http_debug.py), [debug tests](tests/test_errors.py), [CLI tests](tests/test_main.py)) |
285
286
  | Automatically generated component archive filenames cannot use server-provided slugs for path traversal. | wlc chooses the project/component download filename inside a user-selected directory. | A server-provided project or component slug writes outside that directory. | High. | *(documented)* (source: [filename helper](wlc/utils.py), [CLI implementation](wlc/main.py), [download tests](tests/test_download.py)) |
286
287
  | Download writes do not follow destination symlinks or overwrite non-regular or multiply linked targets, and destination replacement is checked against races. | The CLI writes a downloaded translation or automatically named archive through its download helper. The containing directory remains under local-user control. | A preplanted or racing symlink, hard link, changed inode, or non-regular destination redirects downloaded bytes to another file or device. | High. | *(documented)* (source: [CLI implementation](wlc/main.py), [download tests](tests/test_download.py)) |
287
288
  | Raw translation bytes are not written to an interactive terminal. | A translation download would use standard output and that stream reports itself as a TTY. | Binary translation data is emitted to the interactive terminal instead of being refused. | Medium. | *(documented)* (source: [CLI implementation](wlc/main.py), [CLI tests](tests/test_main.py)) |
@@ -308,7 +309,8 @@ code, Python runtime, or operating system. *(maintainer)*
308
309
  - Debug output is diagnostic data rather than a supported structured renderer.
309
310
  Header and request-body redaction does not imply terminal-control escaping or
310
311
  secrecy for request parameter values, URLs, response reason phrases, or error
311
- messages included in diagnostics. *(maintainer)*
312
+ messages included in diagnostics other than configured authorization values.
313
+ *(maintainer)*
312
314
  - wlc does not enforce a response-size, pagination-count, aggregate-time,
313
315
  memory, disk-usage, or retry-cost limit against the selected server. The
314
316
  configured per-request timeout and retry policy are operational controls, not
@@ -4,19 +4,19 @@ requires = ["setuptools>=78.0.2"]
4
4
 
5
5
  [dependency-groups]
6
6
  dev = [
7
- {include-group = "lint"},
8
- {include-group = "test"},
9
- {include-group = "types"}
7
+ { include-group = "lint" },
8
+ { include-group = "test" },
9
+ { include-group = "types" }
10
10
  ]
11
11
  lint = [
12
- {include-group = "pre-commit"},
13
- {include-group = "pylint"}
12
+ { include-group = "pre-commit" },
13
+ { include-group = "pylint" }
14
14
  ]
15
15
  pre-commit = [
16
- "prek==0.5.0"
16
+ "prek==0.5.3"
17
17
  ]
18
18
  pylint = [
19
- "pylint==4.0.8"
19
+ "pylint==4.0.9"
20
20
  ]
21
21
  test = [
22
22
  "pytest-cov==7.1.0",
@@ -25,11 +25,11 @@ test = [
25
25
  "responses==0.26.3"
26
26
  ]
27
27
  types = [
28
- "ty==0.0.77",
28
+ "ty==0.0.83",
29
29
  "mypy==2.3.1",
30
30
  "types-pyxdg==0.28.0.20260518",
31
31
  "types-python-dateutil==2.9.0.20260807",
32
- "types-requests==2.33.0.20260712"
32
+ "types-requests==2.33.0.20260906"
33
33
  ]
34
34
 
35
35
  [project]
@@ -105,7 +105,7 @@ ignore = [
105
105
 
106
106
  [tool.check-wheel-contents]
107
107
  ignore = [
108
- "W002" # Triggered by API mocks
108
+ "W002" # Triggered by API mocks
109
109
  ]
110
110
 
111
111
  [tool.coverage.paths]
@@ -155,30 +155,30 @@ docstring-code-format = true
155
155
  [tool.ruff.lint]
156
156
  future-annotations = true
157
157
  ignore = [
158
- "D10", # TODO: we are missing many docstrings
159
- "incorrect-blank-line-before-class", # CONFIG: incompatible with D211
160
- "multi-line-summary-first-line", # CONFIG: incompatible with D213
161
- "line-too-long", # CONFIG: formatter
162
- "non-imperative-mood", # TODO: many strings need rephrasing
163
- "print", # WONTFIX: using print() (maybe add noqa)
164
- "raise-vanilla-args", # WONTFIX: Avoid specifying long messages outside the exception class
165
- "too-many-arguments", # WONTFIX: Too many arguments to function call
166
- "magic-value-comparison", # TODO: Magic value used in comparison, consider replacing 201 with a constant variable
167
- "docstring-missing-returns", # WONTFIX
168
- "docstring-missing-yields", # WONTFIX
169
- "docstring-missing-exception", # WONTFIX
170
- "too-many-statements-in-try-clause", # TODO
171
- "subclass-builtin", # TODO
172
- "no-self-use", # TODO
173
- "too-many-public-methods", # TODO
174
- "read-whole-file", # TODO
175
- "COM", # CONFIG: No trailing commas
176
- "PT", # CONFIG: Not using pytest
177
- "PTH", # TODO: Not using pathlib
178
- "EM", # TODO: Exception strings
179
- "FBT", # TODO: Boolean in function definition
180
- "ANN", # TODO: type annotations
181
- "error-suffix-on-exception-name" # TODO: exception naming
158
+ "D10", # TODO: we are missing many docstrings
159
+ "incorrect-blank-line-before-class", # CONFIG: incompatible with D211
160
+ "multi-line-summary-first-line", # CONFIG: incompatible with D213
161
+ "line-too-long", # CONFIG: formatter
162
+ "non-imperative-mood", # TODO: many strings need rephrasing
163
+ "print", # WONTFIX: using print() (maybe add noqa)
164
+ "raise-vanilla-args", # WONTFIX: Avoid specifying long messages outside the exception class
165
+ "too-many-arguments", # WONTFIX: Too many arguments to function call
166
+ "magic-value-comparison", # TODO: Magic value used in comparison, consider replacing 201 with a constant variable
167
+ "docstring-missing-returns", # WONTFIX
168
+ "docstring-missing-yields", # WONTFIX
169
+ "docstring-missing-exception", # WONTFIX
170
+ "too-many-statements-in-try-clause", # TODO
171
+ "subclass-builtin", # TODO
172
+ "no-self-use", # TODO
173
+ "too-many-public-methods", # TODO
174
+ "read-whole-file", # TODO
175
+ "COM", # CONFIG: No trailing commas
176
+ "PT", # CONFIG: Not using pytest
177
+ "PTH", # TODO: Not using pathlib
178
+ "EM", # TODO: Exception strings
179
+ "FBT", # TODO: Boolean in function definition
180
+ "ANN", # TODO: type annotations
181
+ "error-suffix-on-exception-name" # TODO: exception naming
182
182
  ]
183
183
  preview = true
184
184
  select = ["ALL"]
@@ -197,10 +197,10 @@ def register_uri(
197
197
 
198
198
 
199
199
  def raise_error(request) -> NoReturn:
200
- """Raise IOError."""
200
+ """Raise an expected request error or an unexpected programming error."""
201
201
  if "/io" in request.path_url:
202
202
  raise RequestException("Some error")
203
- raise FileNotFoundError("Bug")
203
+ raise RuntimeError("Bug")
204
204
 
205
205
 
206
206
  def register_error(
@@ -13,16 +13,20 @@ from pathlib import Path
13
13
  from tempfile import TemporaryDirectory
14
14
  from unittest.mock import patch
15
15
 
16
+ from wlc import WeblateException
17
+
16
18
  from .test_main import CLITestBase
17
19
 
18
20
 
19
21
  class TestDownloadSecurity(CLITestBase):
20
22
  """Download destination security tests."""
21
23
 
22
- def create_symlink(self, target: str | Path, link: Path) -> None:
24
+ def create_symlink(
25
+ self, target: str | Path, link: Path, *, target_is_directory: bool = False
26
+ ) -> None:
23
27
  """Create a symlink or skip on Windows without symlink privileges."""
24
28
  try:
25
- link.symlink_to(target)
29
+ link.symlink_to(target, target_is_directory=target_is_directory)
26
30
  except OSError:
27
31
  if os.name == "nt":
28
32
  self.skipTest("Symlink creation is not permitted")
@@ -80,6 +84,92 @@ class TestDownloadSecurity(CLITestBase):
80
84
  )
81
85
  self.assertTrue(destination.is_dir())
82
86
 
87
+ def test_rejects_component_output_file_before_download(self) -> None:
88
+ with TemporaryDirectory() as tmpdirname:
89
+ output = Path(tmpdirname) / "out.zip"
90
+ output.touch()
91
+
92
+ with patch("wlc.models.Component.download") as download:
93
+ error = self.execute(
94
+ ["download", "hello/weblate", "--output", str(output)],
95
+ expected=1,
96
+ )
97
+
98
+ self.assertEqual(
99
+ f"Error: Output path is not a directory: {output}\n", error
100
+ )
101
+ download.assert_not_called()
102
+
103
+ def test_rejects_dangling_component_output_symlink_before_download(self) -> None:
104
+ with TemporaryDirectory() as tmpdirname:
105
+ output = Path(tmpdirname) / "output"
106
+ self.create_symlink("missing", output)
107
+
108
+ with patch("wlc.models.Component.download") as download:
109
+ error = self.execute(
110
+ ["download", "hello/weblate", "--output", str(output)],
111
+ expected=1,
112
+ )
113
+
114
+ self.assertEqual(
115
+ f"Error: Output path is not a directory: {output}\n", error
116
+ )
117
+ download.assert_not_called()
118
+
119
+ def test_accepts_component_output_directory_symlink(self) -> None:
120
+ with TemporaryDirectory() as tmpdirname:
121
+ root = Path(tmpdirname)
122
+ output_directory = root / "output"
123
+ output_directory.mkdir()
124
+ output = root / "output-link"
125
+ self.create_symlink("output", output, target_is_directory=True)
126
+
127
+ self.execute(["download", "hello/weblate", "--output", str(output)])
128
+
129
+ self.assertTrue((output_directory / "hello-weblate.zip").is_file())
130
+
131
+ def test_reports_component_output_permission_error(self) -> None:
132
+ with TemporaryDirectory() as tmpdirname:
133
+ output_directory = Path(tmpdirname) / "protected"
134
+ permission_error = PermissionError(
135
+ errno.EACCES, "Permission denied", str(output_directory)
136
+ )
137
+
138
+ with patch.object(Path, "mkdir", side_effect=permission_error):
139
+ output = self.execute(
140
+ [
141
+ "download",
142
+ "hello/weblate",
143
+ "--output",
144
+ str(output_directory),
145
+ ],
146
+ expected=1,
147
+ )
148
+
149
+ self.assertEqual(f"Error: {permission_error}\n", output)
150
+
151
+ def test_partial_project_download_reports_completed_component(self) -> None:
152
+ with TemporaryDirectory() as tmpdirname:
153
+ output_directory = Path(tmpdirname)
154
+ with patch(
155
+ "wlc.models.Component.download",
156
+ side_effect=(b"archive", WeblateException("download failed")),
157
+ ):
158
+ output = self.execute(
159
+ ["download", "hello", "--output", str(output_directory)],
160
+ expected=1,
161
+ )
162
+
163
+ self.assertIn(
164
+ "downloaded translations for component: hello/hi/android", output
165
+ )
166
+ self.assertIn("Error: download failed", output)
167
+ self.assertNotIn("downloaded translations for project", output)
168
+ self.assertEqual(
169
+ [path.name for path in output_directory.iterdir()],
170
+ ["hello-android.zip"],
171
+ )
172
+
83
173
  def test_does_not_follow_racing_symlink(self) -> None:
84
174
  with TemporaryDirectory() as tmpdirname:
85
175
  root = Path(tmpdirname)
@@ -241,20 +331,19 @@ class TestDownloadSecurity(CLITestBase):
241
331
  def test_failure_cleans_temporary_file(self) -> None:
242
332
  with TemporaryDirectory() as tmpdirname:
243
333
  output_directory = Path(tmpdirname)
244
- with (
245
- patch(
246
- "wlc.main._replace_download_file",
247
- side_effect=OSError("replace failed"),
248
- ),
249
- self.assertRaisesRegex(OSError, "replace failed"),
334
+ with patch(
335
+ "wlc.main._replace_download_file",
336
+ side_effect=OSError("replace failed"),
250
337
  ):
251
- self.execute(
338
+ output = self.execute(
252
339
  [
253
340
  "download",
254
341
  "hello/weblate",
255
342
  "--output",
256
343
  str(output_directory),
257
- ]
344
+ ],
345
+ expected=1,
258
346
  )
259
347
 
348
+ self.assertEqual("Error: replace failed\n", output)
260
349
  self.assertEqual(list(output_directory.iterdir()), [])