python-gitea 0.12.0__py3-none-any.whl → 0.12.2__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.
@@ -7,9 +7,13 @@ and one that does not rejects the whole request. Resolving first also means an
7
7
  unknown name is an error before anything is written, rather than a label silently
8
8
  not applied, and before the first of several issues has been changed.
9
9
 
10
- A value made only of ASCII digits is taken to be an ID and is sent as it is,
11
- without being looked up. That is what lets a label defined on the organization,
12
- which the repository's listing does not include, be applied at all.
10
+ A value made only of ASCII digits is taken to be an ID. An ID is checked too,
11
+ against the repository's labels and then, for one not among them, against the
12
+ labels of the organization owning the repository, which the repository's listing
13
+ does not include. An ID found in neither is an error before anything is written:
14
+ Gitea drops an ID it cannot apply without saying so, so an ID copied from another
15
+ repository would otherwise be a silent no-op for `add`, and for `set` would leave
16
+ the issue with no labels at all.
13
17
 
14
18
  Every command takes one `--issue-id` or several. With one, `data` is that issue's
15
19
  labels, as `gitea-cli issue label list` emits them. With several, `data` holds one
@@ -25,6 +29,8 @@ import re
25
29
  from collections.abc import Callable
26
30
  from typing import TYPE_CHECKING, Any
27
31
 
32
+ from requests import HTTPError
33
+
28
34
  from gitea.cli.utils.errors import CommandError
29
35
  from gitea.utils.pagination import PAGE_SIZE, collect_all_pages
30
36
 
@@ -37,6 +43,7 @@ ISSUE_IDS_HELP = "Issue number shown in the web UI. Repeat the option to act on
37
43
  LABEL_HELP = "Label name or numeric ID. Repeat the option for several labels."
38
44
 
39
45
  _NUMERIC_ID = re.compile(r"[0-9]+")
46
+ _NOT_FOUND = 404
40
47
 
41
48
 
42
49
  def require_issue_ids(issue_ids: list[int] | None, *, command: str) -> list[int]:
@@ -79,14 +86,41 @@ def require_labels(labels: list[str] | None, *, command: str, hint: str = "") ->
79
86
  return labels
80
87
 
81
88
 
89
+ def _organization_label_ids(client: Gitea, owner: str) -> set[int]:
90
+ """Read the IDs of the labels defined on the organization owning a repository.
91
+
92
+ Args:
93
+ client: The client to list the labels with.
94
+ owner: The owner of the repository.
95
+
96
+ Returns:
97
+ The IDs, or none when the owner is a user rather than an organization,
98
+ which Gitea answers with a 404.
99
+
100
+ Raises:
101
+ HTTPError: If the listing failed for any other reason.
102
+
103
+ """
104
+ try:
105
+ defined, _ = collect_all_pages(
106
+ lambda page: client.label.list_organization_labels(organization=owner, page=page, limit=PAGE_SIZE)
107
+ )
108
+ except HTTPError as e:
109
+ response = getattr(e, "response", None)
110
+ if response is None or response.status_code != _NOT_FOUND:
111
+ raise
112
+ return set()
113
+ return {label["id"] for label in defined}
114
+
115
+
82
116
  def resolve_label_ids(client: Gitea, owner: str, repository: str, labels: list[str], *, command: str) -> list[int]:
83
- """Resolve the labels given on the command line to their IDs.
117
+ """Resolve the labels given on the command line to their IDs, and check that each exists.
84
118
 
85
- The repository's labels are listed only when a name has to be resolved, and
86
- then only once for all of them.
119
+ The repository's labels are listed once for all of them. The organization's
120
+ are listed only when an ID is not among the repository's.
87
121
 
88
122
  Args:
89
- client: The client to list the repository's labels with.
123
+ client: The client to list the labels with.
90
124
  owner: The owner of the repository.
91
125
  repository: The name of the repository.
92
126
  labels: The labels, each a name or a numeric ID.
@@ -96,18 +130,19 @@ def resolve_label_ids(client: Gitea, owner: str, repository: str, labels: list[s
96
130
  The label IDs, in the order given, each once.
97
131
 
98
132
  Raises:
99
- CommandError: If a name matches no label of the repository, or more than one.
133
+ CommandError: If a name matches no label of the repository, or more than
134
+ one, or an ID is neither a label of the repository nor of the
135
+ organization owning it.
100
136
 
101
137
  """
102
- names = [label for label in labels if not _NUMERIC_ID.fullmatch(label)]
138
+ defined, _ = collect_all_pages(
139
+ lambda page: client.label.list_labels(owner=owner, repository=repository, page=page, limit=PAGE_SIZE)
140
+ )
103
141
  by_name: dict[str, list[int]] = {}
104
- if names:
105
- defined, _ = collect_all_pages(
106
- lambda page: client.label.list_labels(owner=owner, repository=repository, page=page, limit=PAGE_SIZE)
107
- )
108
- for label in defined:
109
- by_name.setdefault(label["name"], []).append(label["id"])
142
+ for label in defined:
143
+ by_name.setdefault(label["name"], []).append(label["id"])
110
144
 
145
+ names = [label for label in labels if not _NUMERIC_ID.fullmatch(label)]
111
146
  unknown = [name for name in names if name not in by_name]
112
147
  if unknown:
113
148
  raise CommandError(
@@ -122,8 +157,23 @@ def resolve_label_ids(client: Gitea, owner: str, repository: str, labels: list[s
122
157
  f"in {owner}/{repository}. Pass the numeric ID of the one you mean."
123
158
  )
124
159
 
125
- ids = [int(label) if _NUMERIC_ID.fullmatch(label) else by_name[label][0] for label in labels]
126
- return list(dict.fromkeys(ids))
160
+ repository_ids = {label["id"] for label in defined}
161
+ ids = [int(label) for label in labels if _NUMERIC_ID.fullmatch(label)]
162
+ elsewhere = [label_id for label_id in dict.fromkeys(ids) if label_id not in repository_ids]
163
+ if elsewhere:
164
+ organization_ids = _organization_label_ids(client, owner)
165
+ missing = [label_id for label_id in elsewhere if label_id not in organization_ids]
166
+ if missing:
167
+ raise CommandError(
168
+ f"'{command}': no label with ID {', '.join(str(label_id) for label_id in missing)} "
169
+ f"in {owner}/{repository} or on its owner {owner}. "
170
+ f"A label's ID belongs to the repository or organization defining it, so an ID read from "
171
+ f"another repository names a different label there, or none; nothing was changed. "
172
+ f"Pass the label's name instead; 'gitea-cli label list' shows the repository's labels."
173
+ )
174
+
175
+ resolved = [int(label) if _NUMERIC_ID.fullmatch(label) else by_name[label][0] for label in labels]
176
+ return list(dict.fromkeys(resolved))
127
177
 
128
178
 
129
179
  def for_each_issue(
@@ -74,6 +74,63 @@ class AsyncLabel(BaseLabel, AsyncResource):
74
74
  data, status_code = await process_async_response(response, default=[])
75
75
  return cast(list[dict[str, Any]], data), {"status_code": status_code}
76
76
 
77
+ async def _list_organization_labels(
78
+ self,
79
+ organization: str,
80
+ page: int | None = None,
81
+ limit: int | None = None,
82
+ **kwargs: Any,
83
+ ) -> ClientResponse:
84
+ """List the labels defined on an organization.
85
+
86
+ Args:
87
+ organization: The name of the organization.
88
+ page: The page number for pagination.
89
+ limit: The number of labels per page.
90
+ **kwargs: Additional arguments for the request.
91
+
92
+ Returns:
93
+ The HTTP response object.
94
+
95
+ """
96
+ endpoint, params = self._list_organization_labels_helper(
97
+ organization=organization,
98
+ page=page,
99
+ limit=limit,
100
+ )
101
+ return await self._get(endpoint=endpoint, params=params, **kwargs)
102
+
103
+ async def list_organization_labels(
104
+ self,
105
+ organization: str,
106
+ page: int | None = None,
107
+ limit: int | None = None,
108
+ **kwargs: Any,
109
+ ) -> tuple[list[dict[str, Any]], dict[str, Any]]:
110
+ """List the labels defined on an organization.
111
+
112
+ These are the labels every repository of the organization can use, and
113
+ which the listing of a repository's own labels does not include.
114
+
115
+ Args:
116
+ organization: The name of the organization.
117
+ page: The page number for pagination.
118
+ limit: The number of labels per page.
119
+ **kwargs: Additional arguments for the request.
120
+
121
+ Returns:
122
+ A tuple containing a list of labels as dictionaries and a dictionary with metadata.
123
+
124
+ """
125
+ response = await self._list_organization_labels(
126
+ organization=organization,
127
+ page=page,
128
+ limit=limit,
129
+ **kwargs,
130
+ )
131
+ data, status_code = await process_async_response(response, default=[])
132
+ return cast(list[dict[str, Any]], data), {"status_code": status_code}
133
+
77
134
  async def _create_label(
78
135
  self,
79
136
  owner: str,
gitea/label/base.py CHANGED
@@ -50,6 +50,45 @@ class BaseLabel:
50
50
 
51
51
  return endpoint, params
52
52
 
53
+ def _list_organization_labels_endpoint(self, organization: str) -> str:
54
+ """Construct the endpoint URL for listing the labels defined on an organization.
55
+
56
+ Args:
57
+ organization: The name of the organization.
58
+
59
+ Returns:
60
+ The endpoint URL for listing the organization's labels.
61
+
62
+ """
63
+ return f"/orgs/{organization}/labels"
64
+
65
+ def _list_organization_labels_helper(
66
+ self,
67
+ organization: str,
68
+ page: int | None = None,
69
+ limit: int | None = None,
70
+ ) -> tuple[str, dict[str, Any]]:
71
+ """Get the endpoint and parameters for listing the labels defined on an organization.
72
+
73
+ Args:
74
+ organization: The name of the organization.
75
+ page: The page number for pagination.
76
+ limit: The number of labels per page.
77
+
78
+ Returns:
79
+ A tuple containing the endpoint and the request arguments.
80
+
81
+ """
82
+ endpoint = self._list_organization_labels_endpoint(organization=organization)
83
+
84
+ params = {}
85
+ if page is not None:
86
+ params["page"] = page
87
+ if limit is not None:
88
+ params["limit"] = limit
89
+
90
+ return endpoint, params
91
+
53
92
  def _create_label_endpoint(self, owner: str, repository: str) -> str:
54
93
  """Construct the endpoint URL for creating a label in a repository.
55
94
 
gitea/label/label.py CHANGED
@@ -74,6 +74,63 @@ class Label(BaseLabel, Resource):
74
74
  data, status_code = process_response(response, default=[])
75
75
  return cast(list[dict[str, Any]], data), {"status_code": status_code}
76
76
 
77
+ def _list_organization_labels(
78
+ self,
79
+ organization: str,
80
+ page: int | None = None,
81
+ limit: int | None = None,
82
+ **kwargs: Any,
83
+ ) -> Response:
84
+ """List the labels defined on an organization.
85
+
86
+ Args:
87
+ organization: The name of the organization.
88
+ page: The page number for pagination.
89
+ limit: The number of labels per page.
90
+ **kwargs: Additional arguments for the request.
91
+
92
+ Returns:
93
+ The HTTP response object.
94
+
95
+ """
96
+ endpoint, params = self._list_organization_labels_helper(
97
+ organization=organization,
98
+ page=page,
99
+ limit=limit,
100
+ )
101
+ return self._get(endpoint=endpoint, params=params, **kwargs)
102
+
103
+ def list_organization_labels(
104
+ self,
105
+ organization: str,
106
+ page: int | None = None,
107
+ limit: int | None = None,
108
+ **kwargs: Any,
109
+ ) -> tuple[list[dict[str, Any]], dict[str, Any]]:
110
+ """List the labels defined on an organization.
111
+
112
+ These are the labels every repository of the organization can use, and
113
+ which the listing of a repository's own labels does not include.
114
+
115
+ Args:
116
+ organization: The name of the organization.
117
+ page: The page number for pagination.
118
+ limit: The number of labels per page.
119
+ **kwargs: Additional arguments for the request.
120
+
121
+ Returns:
122
+ A tuple containing a list of labels as dictionaries and a dictionary with metadata.
123
+
124
+ """
125
+ response = self._list_organization_labels(
126
+ organization=organization,
127
+ page=page,
128
+ limit=limit,
129
+ **kwargs,
130
+ )
131
+ data, status_code = process_response(response, default=[])
132
+ return cast(list[dict[str, Any]], data), {"status_code": status_code}
133
+
77
134
  def _create_label(
78
135
  self,
79
136
  owner: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-gitea
3
- Version: 0.12.0
3
+ Version: 0.12.2
4
4
  Summary: A Python package for interacting with the Gitea API, offering a simple interface to access repositories, users, organizations, issues, and more for automation and data management.
5
5
  Project-URL: Documentation, https://isaac-cf-wong.github.io/python-gitea
6
6
  Project-URL: Source, https://github.com/isaac-cf-wong/python-gitea
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Programming Language :: Python :: 3.14
19
19
  Requires-Python: >=3.12
20
20
  Requires-Dist: aiohttp>=3.13.3
21
- Requires-Dist: platformdirs>=4.3.6
21
+ Requires-Dist: platformdirs>=4.4.0
22
22
  Requires-Dist: pydantic>=2.12.5
23
23
  Requires-Dist: pyyaml>=6.0.2
24
24
  Requires-Dist: requests>=2.33.0
@@ -89,7 +89,7 @@ gitea/cli/issue/dependency/remove.py,sha256=1Adml7ATeO7RoOOYb6ZR9K1qBpbqgVXfW1F0
89
89
  gitea/cli/issue/label/__init__.py,sha256=og5McB5fVl_BTSBBNGnYad_NAiy_M6-Djg2LtyUYzxo,54
90
90
  gitea/cli/issue/label/add.py,sha256=N75TrExobdWf_LdPQdyoiyc_jcQjBjvg3OL2YSelP2Q,3652
91
91
  gitea/cli/issue/label/clear.py,sha256=dGPlT4neZt454XlnzdIZOzJgs8IckaqlpaRT0uhM8g0,3500
92
- gitea/cli/issue/label/common.py,sha256=oSFasD7xvyLYV5pjS10GI1QuchePQFacbygzgJK_f-A,8951
92
+ gitea/cli/issue/label/common.py,sha256=Q9LC0MhYF0AIm2ddKVCloLfEE-JjOz5RfayxnDPwSps,11237
93
93
  gitea/cli/issue/label/list.py,sha256=GqSkZjRXn7-wAWVa65cajV3uLxh63IT00R2VFM2nQR4,3127
94
94
  gitea/cli/issue/label/main.py,sha256=vLSjxNpP8ymLwIEUxrOPnYciUOmdwb8Jo1o_PersxLc,1179
95
95
  gitea/cli/issue/label/remove.py,sha256=AIW1_Rzz444An-iuMM7bPH2kI6SFmLy_JkEY3F7Y8ds,3644
@@ -167,9 +167,9 @@ gitea/issue/base.py,sha256=Q13QLwxKGozvmd7oPiex9Kw5unsdvCKF-GCvdg0-zJQ,15298
167
167
  gitea/issue/issue.py,sha256=wpXoGH9JQps3ypbGyzOkqKacGGnDxXxwFWrPEtORXfw,28894
168
168
  gitea/issue/project_column.py,sha256=7TpnQcq9-hqc9eEciluplHQmSLiEBkRBr-lH7y6L9s8,15005
169
169
  gitea/label/__init__.py,sha256=cHdpsWsPWOX-q6k1JRzPOFSRafJUj907lmndLntjZjQ,690
170
- gitea/label/async_label.py,sha256=RtjH8ySiZraJKe_pYjn_xEAndgGlwsDt1tUoienWiI4,8133
171
- gitea/label/base.py,sha256=wlgx_TcA9P8Hj31tELi9MgC48fVlZk1sRmWd7UHsfBU,5277
172
- gitea/label/label.py,sha256=7q54hFUe1lSUZOKUziLsToFhA_rTwxPVBt0osDzU9qw,7907
170
+ gitea/label/async_label.py,sha256=11UPD9T15Z3_Z2f63a9oxnwy__qDXTrIECZ03S0ecgE,10049
171
+ gitea/label/base.py,sha256=8zD3r7n6iC4d4bnnDgTB8yfuu0GuZQFoNTC6T7xUcQ4,6483
172
+ gitea/label/label.py,sha256=sIA4f08R1jN30jIMH3h92nq7953rdh2da3TeHJQB2_o,9781
173
173
  gitea/milestone/__init__.py,sha256=N5IclwSWdsJXJLK0vDtVIajk-TWstEsfom5FMN4AB50,750
174
174
  gitea/milestone/async_milestone.py,sha256=2ofOOtfYp3nyOxEu021mEUsbsclflCr8C4oZVjAj2Ck,5197
175
175
  gitea/milestone/base.py,sha256=ZWCP6UungakNkfdezfbMpDDgXr_5Fu7K5ceXgNF3s0g,3437
@@ -210,8 +210,8 @@ gitea/utils/response.py,sha256=dDk-joN6TA3blefFGTxWTs41dHOUapQs3OxoOqiMg6o,5945
210
210
  gitea/watch/__init__.py,sha256=xeLLpoldkTAJda4WPOVa6NvE1i_X2VcUcgiMFoK9ImU,1422
211
211
  gitea/watch/changes.py,sha256=lSeq3-V8IjvcoZFp5teciiezOlQa89vhaWju80JEHO4,14418
212
212
  gitea/watch/state.py,sha256=ZmwPnTRavdyIJuXIY0QllVCh56O_p9oPdVzS-pt0oCs,17856
213
- python_gitea-0.12.0.dist-info/METADATA,sha256=DLS01Nx4zpg4tFhSaI57H2x0WVGeboy69aXAADfYJiQ,7634
214
- python_gitea-0.12.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
215
- python_gitea-0.12.0.dist-info/entry_points.txt,sha256=m5yt-1RMRZRFMj_M-Un5E4eOkM113S9EppAHp0aCzEY,49
216
- python_gitea-0.12.0.dist-info/licenses/LICENSE,sha256=4dW9zcCxiQKAu4CyNKJTRmvzdJCCHDxbPGuQ-YALpsY,1137
217
- python_gitea-0.12.0.dist-info/RECORD,,
213
+ python_gitea-0.12.2.dist-info/METADATA,sha256=PgXEdpd3tfl6AKhOdc6MqZ-XrpRqAkb0wluhPpmf9Go,7634
214
+ python_gitea-0.12.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
215
+ python_gitea-0.12.2.dist-info/entry_points.txt,sha256=m5yt-1RMRZRFMj_M-Un5E4eOkM113S9EppAHp0aCzEY,49
216
+ python_gitea-0.12.2.dist-info/licenses/LICENSE,sha256=4dW9zcCxiQKAu4CyNKJTRmvzdJCCHDxbPGuQ-YALpsY,1137
217
+ python_gitea-0.12.2.dist-info/RECORD,,