xtools 0.1.2__tar.gz → 0.3.0__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.
xtools-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,41 @@
1
+ Metadata-Version: 2.3
2
+ Name: xtools
3
+ Version: 0.3.0
4
+ Summary: XTools API wrapper
5
+ Author: Baptiste Fontaine
6
+ Author-email: Baptiste Fontaine <b@ptistefontaine.fr>
7
+ Classifier: Development Status :: 5 - Production/Stable
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Dist: requests>=2.28.2,<3
18
+ Requires-Python: >=3.10, <4.0
19
+ Project-URL: Homepage, https://github.com/bfontaine/xtools
20
+ Description-Content-Type: text/markdown
21
+
22
+ # xtools
23
+ [![Version on Pypi](https://img.shields.io/pypi/v/xtools.svg?label=PyPI)](https://pypi.org/project/xtools/)
24
+
25
+ **xtools** is a Python wrapper around [XTools][]’ API.
26
+
27
+ [XTools]: https://xtools.readthedocs.io/en/stable/index.html
28
+
29
+ Note this project is not affiliated with nor endorsed by XTools.
30
+
31
+ ## Install
32
+
33
+ pip install xtools
34
+
35
+ ### Requirements
36
+
37
+ Python 3.10+.
38
+
39
+ ## Usage
40
+
41
+ [Read the docs](https://python-xtools.readthedocs.io/en/latest/).
@@ -1,6 +1,5 @@
1
1
  # xtools
2
- [![Version on Pypi](https://img.shields.io/pypi/v/xtools.svg?label=PyPI)](https://pypi.org/project/coveralls/)
3
- [![Coverage Status](https://coveralls.io/repos/github/bfontaine/xtools/badge.svg?branch=master)](https://coveralls.io/github/bfontaine/xtools?branch=master)
2
+ [![Version on Pypi](https://img.shields.io/pypi/v/xtools.svg?label=PyPI)](https://pypi.org/project/xtools/)
4
3
 
5
4
  **xtools** is a Python wrapper around [XTools][]’ API.
6
5
 
@@ -14,8 +13,8 @@ Note this project is not affiliated with nor endorsed by XTools.
14
13
 
15
14
  ### Requirements
16
15
 
17
- Python 3.5+.
16
+ Python 3.10+.
18
17
 
19
18
  ## Usage
20
19
 
21
- [Read the docs](https://python-xtools.readthedocs.io/en/latest/).
20
+ [Read the docs](https://python-xtools.readthedocs.io/en/latest/).
@@ -0,0 +1,68 @@
1
+ [project]
2
+ name = "xtools"
3
+ version = "0.3.0"
4
+ description = "XTools API wrapper"
5
+ authors = [{ name = "Baptiste Fontaine", email = "b@ptistefontaine.fr" }]
6
+ requires-python = ">=3.10,<4.0"
7
+ readme = "README.md"
8
+ classifiers = [
9
+ "Development Status :: 5 - Production/Stable",
10
+ "Intended Audience :: Developers",
11
+ "License :: OSI Approved :: MIT License",
12
+ "Operating System :: OS Independent",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.10",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Programming Language :: Python :: 3.14",
19
+ ]
20
+ dependencies = ["requests>=2.28.2,<3"]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/bfontaine/xtools"
24
+
25
+ [dependency-groups]
26
+ dev = [
27
+ "types-requests>=2.28.11.17,<3",
28
+ "mypy>=2,<3",
29
+ "pytest>=9,<10",
30
+ "sphinx>=8,<9",
31
+ "requests-mock>=1.10,<2",
32
+ "pytest-cov>=7,<8",
33
+ "ruff>=0.15.17",
34
+ ]
35
+
36
+ [tool.uv]
37
+ default-groups = "all"
38
+
39
+ [tool.uv.build-backend]
40
+ module-root = ""
41
+ source-include = ["xtools/py.typed"]
42
+
43
+ [build-system]
44
+ requires = ["uv_build>=0.9.17,<0.10.0"]
45
+ build-backend = "uv_build"
46
+
47
+ [tool.ruff]
48
+ line-length = 120
49
+ target-version = "py310"
50
+ [tool.ruff.lint]
51
+ select = [
52
+ "PERF",
53
+ "W",
54
+ "F",
55
+ "UP",
56
+ "ASYNC",
57
+ # "A",
58
+ # "B",
59
+ # "PT",
60
+ "C4",
61
+ "PIE800",
62
+ "SIM",
63
+ "SLOT",
64
+ "YTT",
65
+ "PLE",
66
+ "FURB",
67
+ "PLC0207",
68
+ ]
@@ -0,0 +1,20 @@
1
+ from .exceptions import BaseXToolsException, NotFound, TooManyEdits
2
+ from .page import article_info, prose, links, top_editors, assessments
3
+ from .project import (normalize_project, namespaces, page_assessments, page_assessments_configuration, automated_tools,
4
+ admins_and_user_groups, admin_statistics, patroller_statistics, steward_statistics)
5
+ from .quote import random_quote, single_quote, all_quotes
6
+ from .user import (simple_edit_count, number_of_pages_created, pages_created, pages_created_iter,
7
+ automated_edit_counter, non_automated_edits, automated_edits, edit_summaries, top_edits,
8
+ category_edit_counter, log_counts, namespace_totals, month_counts, time_card)
9
+
10
+ __version__ = "0.3.0"
11
+
12
+ __all__ = (
13
+ "__version__", "article_info", "prose", "links", "top_editors", "assessments", "normalize_project", "namespaces",
14
+ "page_assessments", "page_assessments_configuration", "automated_tools", "admins_and_user_groups",
15
+ "admin_statistics", "patroller_statistics", "steward_statistics", "random_quote", "single_quote", "all_quotes",
16
+ "simple_edit_count", "number_of_pages_created", "pages_created", "pages_created_iter", "automated_edit_counter",
17
+ "non_automated_edits", "automated_edits", "edit_summaries", "top_edits", "category_edit_counter", "log_counts",
18
+ "namespace_totals", "month_counts", "time_card",
19
+ "BaseXToolsException", "NotFound", "TooManyEdits",
20
+ )
@@ -1,13 +1,14 @@
1
1
  """
2
2
  Internal base utilities.
3
3
  """
4
-
5
- from typing import Optional, Tuple, Any, Sequence
4
+ import time
5
+ from collections.abc import Sequence
6
6
  from json.decoder import JSONDecodeError
7
+ from typing import Any
7
8
  from urllib.parse import quote as urlquote
8
9
 
9
- import time
10
10
  import requests
11
+
11
12
  from .exceptions import BaseXToolsException, NotFound, TooManyEdits
12
13
 
13
14
  BASE_URL = "https://xtools.wmflabs.org/api"
@@ -24,14 +25,14 @@ def url(path: str) -> str:
24
25
  return BASE_URL + path
25
26
 
26
27
 
27
- def error_exception(response: dict) -> Optional[BaseXToolsException]:
28
+ def error_exception(response: dict[str, Any]) -> BaseXToolsException | None:
28
29
  """
29
30
  Return an optional Exception instance for an error response. Return ``None`` if the given response is not an error.
30
31
  :param response: response from the API.
31
32
  :return: ``None`` or a ``BaseXToolsException`` (or subclass) instance.
32
33
  """
33
34
  if "error" not in response:
34
- return
35
+ return None
35
36
 
36
37
  error = response["error"]
37
38
 
@@ -54,7 +55,7 @@ def error_exception(response: dict) -> Optional[BaseXToolsException]:
54
55
  return BaseXToolsException(str(error))
55
56
 
56
57
 
57
- def get(path: str, params=None, retry=3, retry_delay=1):
58
+ def get(path: str, params: dict[str, Any] | None = None, retry: int = 3, retry_delay: int = 1) -> dict[str, Any]:
58
59
  """
59
60
  Perform a GET request against the API.
60
61
  :param path:
@@ -72,7 +73,7 @@ def get(path: str, params=None, retry=3, retry_delay=1):
72
73
  r.raise_for_status()
73
74
 
74
75
  try:
75
- response = r.json()
76
+ response: dict[str, Any] = r.json()
76
77
  except JSONDecodeError as e:
77
78
  r.raise_for_status()
78
79
  raise e
@@ -83,7 +84,7 @@ def get(path: str, params=None, retry=3, retry_delay=1):
83
84
  return response
84
85
 
85
86
 
86
- def build_path(path_format: str, params: Sequence[Tuple[str, Any, str]]) -> str:
87
+ def build_path(path_format: str, params: Sequence[tuple[str, Any, str]]) -> str:
87
88
  """
88
89
  Build a path for the XTools API.
89
90
 
@@ -100,13 +101,10 @@ def build_path(path_format: str, params: Sequence[Tuple[str, Any, str]]) -> str:
100
101
  :param params:
101
102
  :return:
102
103
  """
103
- params_dict = {}
104
+ params_dict: dict[str, str] = {}
104
105
 
105
- def has_more(index):
106
- for _, val, _ in params[index+1:]:
107
- if val:
108
- return True
109
- return False
106
+ def has_more(index: int) -> bool:
107
+ return any(val for _, val, _ in params[index + 1:])
110
108
 
111
109
  for i, (name, value, default) in enumerate(params):
112
110
  if value:
@@ -126,4 +124,4 @@ def build_path(path_format: str, params: Sequence[Tuple[str, Any, str]]) -> str:
126
124
  params_dict[name] = urlquote(param_value)
127
125
 
128
126
  path = path_format.format(**params_dict).rstrip("/")
129
- return path
127
+ return path
@@ -1,15 +1,15 @@
1
- from typing import Optional
1
+ from typing import Any
2
2
 
3
3
  __all__ = ['BaseXToolsException', 'NotFound', 'TooManyEdits']
4
4
 
5
5
 
6
6
  class BaseXToolsException(Exception):
7
- def __init__(self, message, code: Optional[int] = None):
7
+ def __init__(self, message: str, code: int | None = None) -> None:
8
8
  super().__init__(message)
9
9
  self.message = message
10
10
  self.code = code
11
11
 
12
- def __eq__(self, other):
12
+ def __eq__(self, other: Any) -> bool:
13
13
  return isinstance(other, BaseXToolsException) \
14
14
  and str(self) == str(other) \
15
15
  and self.code == other.code
@@ -20,4 +20,4 @@ class NotFound(BaseXToolsException):
20
20
 
21
21
 
22
22
  class TooManyEdits(BaseXToolsException):
23
- pass
23
+ pass
@@ -4,13 +4,14 @@ Endpoints related to pages.
4
4
  https://xtools.readthedocs.io/en/stable/api/page.html
5
5
  """
6
6
 
7
- from typing import Optional, Sequence
7
+ from collections.abc import Sequence
8
8
  from datetime import date
9
+ from typing import Any
9
10
 
10
11
  from . import base
11
12
 
12
13
 
13
- def _get_page_dict(what: str, project: str, article: str) -> dict:
14
+ def _get_page_dict(what: str, project: str, article: str) -> dict[str, Any]:
14
15
  return base.get(base.build_path("/page/{what}/{project}/{article}", (
15
16
  ("what", what, ""),
16
17
  ("project", project, ""),
@@ -18,7 +19,7 @@ def _get_page_dict(what: str, project: str, article: str) -> dict:
18
19
  )))
19
20
 
20
21
 
21
- def article_info(project: str, article: str) -> dict:
22
+ def article_info(project: str, article: str) -> dict[str, Any]:
22
23
  """
23
24
  Return basic information about an article, such as page views, watchers, edits counts; author; assessment.
24
25
 
@@ -31,7 +32,7 @@ def article_info(project: str, article: str) -> dict:
31
32
  return _get_page_dict("articleinfo", project, article)
32
33
 
33
34
 
34
- def prose(project: str, article: str) -> dict:
35
+ def prose(project: str, article: str) -> dict[str, Any]:
35
36
  """
36
37
  Return prose information about an article, such as references and words counts.
37
38
 
@@ -58,7 +59,7 @@ def prose(project: str, article: str) -> dict:
58
59
  return _get_page_dict("prose", project, article)
59
60
 
60
61
 
61
- def links(project: str, article: str) -> dict:
62
+ def links(project: str, article: str) -> dict[str, Any]:
62
63
  """
63
64
  Return in and outgoing links counts of an article.
64
65
 
@@ -85,10 +86,10 @@ def links(project: str, article: str) -> dict:
85
86
 
86
87
 
87
88
  def top_editors(project: str, article: str,
88
- start: Optional[date] = None,
89
- end: Optional[date] = None,
90
- limit: Optional[int] = None,
91
- exclude_bots: bool = False) -> dict:
89
+ start: date | None = None,
90
+ end: date | None = None,
91
+ limit: int | None = None,
92
+ exclude_bots: bool = False) -> dict[str, Any]:
92
93
  """
93
94
  Get top editors on an article.
94
95
 
@@ -140,7 +141,7 @@ def top_editors(project: str, article: str,
140
141
 
141
142
 
142
143
  def assessments(project: str, articles: Sequence[str],
143
- class_only: bool = False) -> dict:
144
+ class_only: bool = False) -> dict[str, Any]:
144
145
  """
145
146
  Get assessment data for the given articles.
146
147
 
@@ -178,4 +179,4 @@ def assessments(project: str, articles: Sequence[str],
178
179
  return base.get(base.build_path("/page/assessments/{project}/{articles}", (
179
180
  ("project", project, ""),
180
181
  ("articles", "|".join(articles), ""),
181
- )), params)
182
+ )), params)
@@ -3,18 +3,18 @@ Endpoints related to projects.
3
3
 
4
4
  https://xtools.readthedocs.io/en/stable/api/project.html
5
5
  """
6
-
7
- from typing import Optional, Sequence
6
+ from collections.abc import Sequence
8
7
  from datetime import date
8
+ from typing import Any
9
9
 
10
10
  from . import base
11
11
 
12
12
 
13
- def _get_project_dict(what: str, project: str) -> dict:
14
- return base.get("/project/%s/%s" % (what, project))
13
+ def _get_project_dict(what: str, project: str) -> dict[str, Any]:
14
+ return base.get(f"/project/{what}/{project}")
15
15
 
16
16
 
17
- def normalize_project(project: str) -> dict:
17
+ def normalize_project(project: str) -> dict[str, Any]:
18
18
  """
19
19
  https://xtools.readthedocs.io/en/stable/api/project.html#normalize-project
20
20
 
@@ -36,7 +36,7 @@ def normalize_project(project: str) -> dict:
36
36
  return _get_project_dict("normalize", project)
37
37
 
38
38
 
39
- def namespaces(project: str) -> dict:
39
+ def namespaces(project: str) -> dict[str, Any]:
40
40
  """
41
41
  https://xtools.readthedocs.io/en/stable/api/project.html#namespaces
42
42
 
@@ -46,7 +46,7 @@ def namespaces(project: str) -> dict:
46
46
  return _get_project_dict("namespaces", project)
47
47
 
48
48
 
49
- def page_assessments(project: str) -> dict:
49
+ def page_assessments(project: str) -> dict[str, Any]:
50
50
  """
51
51
  https://xtools.readthedocs.io/en/stable/api/project.html#page-assessments
52
52
 
@@ -56,7 +56,7 @@ def page_assessments(project: str) -> dict:
56
56
  return _get_project_dict("assessments", project)
57
57
 
58
58
 
59
- def page_assessments_configuration() -> dict:
59
+ def page_assessments_configuration() -> dict[str, Any]:
60
60
  """
61
61
  Return the list of wikis that support page assessments and the configuration for each.
62
62
 
@@ -83,7 +83,7 @@ def page_assessments_configuration() -> dict:
83
83
  return base.get("/project/assessments")
84
84
 
85
85
 
86
- def automated_tools(project: str) -> dict:
86
+ def automated_tools(project: str) -> dict[str, Any]:
87
87
  """
88
88
  Return a list of known (semi-)automated tools used on the project.
89
89
 
@@ -117,7 +117,7 @@ def automated_tools(project: str) -> dict:
117
117
  return _get_project_dict("automated_tools", project)
118
118
 
119
119
 
120
- def admins_and_user_groups(project: str) -> dict:
120
+ def admins_and_user_groups(project: str) -> dict[str, Any]:
121
121
  """
122
122
  https://xtools.readthedocs.io/en/stable/api/project.html#admins-and-user-groups
123
123
 
@@ -128,9 +128,9 @@ def admins_and_user_groups(project: str) -> dict:
128
128
 
129
129
 
130
130
  def _get_project_stats_dict(what: str, project: str,
131
- start: Optional[date] = None,
132
- end: Optional[date] = None,
133
- actions: Optional[Sequence[str]] = None) -> dict:
131
+ start: date | None = None,
132
+ end: date | None = None,
133
+ actions: Sequence[str] | None = None) -> dict[str, Any]:
134
134
  path = base.build_path("/project/{what}/{project}/{start}/{end}", (
135
135
  ("what", what, ""),
136
136
  ("project", project, ""),
@@ -146,9 +146,9 @@ def _get_project_stats_dict(what: str, project: str,
146
146
 
147
147
 
148
148
  def admin_statistics(project: str,
149
- start: Optional[date] = None,
150
- end: Optional[date] = None,
151
- actions: Optional[Sequence[str]] = None) -> dict:
149
+ start: date | None = None,
150
+ end: date | None = None,
151
+ actions: Sequence[str] | None = None) -> dict[str, Any]:
152
152
  """
153
153
  Return admin users of the project with counts of the actions they took.
154
154
 
@@ -167,27 +167,27 @@ def admin_statistics(project: str,
167
167
 
168
168
 
169
169
  def patroller_statistics(project: str,
170
- start: Optional[date] = None,
171
- end: Optional[date] = None,
172
- actions: Optional[Sequence[str]] = None) -> dict:
170
+ start: date | None = None,
171
+ end: date | None = None,
172
+ actions: Sequence[str] | None = None) -> dict[str, Any]:
173
173
  """
174
174
  Same as ``admin_statistics`` with different actions.
175
175
 
176
176
  https://xtools.readthedocs.io/en/stable/api/project.html#patroller-statistics
177
177
 
178
- :param project:
179
- :param start:
180
- :param end:
178
+ :param project:
179
+ :param start:
180
+ :param end:
181
181
  :param actions: available actions include: ``patrol``, ``page-curation``, ``pc-accept``, ``pc-reject``.
182
- :return:
182
+ :return:
183
183
  """
184
184
  return _get_project_stats_dict("patroller_stats", project, start, end, actions)
185
185
 
186
186
 
187
187
  def steward_statistics(project: str,
188
- start: Optional[date] = None,
189
- end: Optional[date] = None,
190
- actions: Optional[Sequence[str]] = None) -> dict:
188
+ start: date | None = None,
189
+ end: date | None = None,
190
+ actions: Sequence[str] | None = None) -> dict[str, Any]:
191
191
  """
192
192
  Same as ``admin_statistics`` with different actions.
193
193
 
@@ -200,4 +200,4 @@ def steward_statistics(project: str,
200
200
  ``global-unblock``, ``global-rename``, ``global-rights``, ``wiki-set-change``.
201
201
  :return:
202
202
  """
203
- return _get_project_stats_dict("steward_stats", project, start, end, actions)
203
+ return _get_project_stats_dict("steward_stats", project, start, end, actions)
File without changes
@@ -6,8 +6,9 @@ A Quote is a named tuple with ``id`` and ``text`` fields.
6
6
  https://xtools.readthedocs.io/en/stable/api/quote.html
7
7
  """
8
8
 
9
- from typing import Sequence
10
9
  from collections import namedtuple
10
+ from collections.abc import Sequence
11
+ from urllib.parse import quote as urlquote
11
12
 
12
13
  from . import base
13
14
 
@@ -44,7 +45,7 @@ def single_quote(quote_id: int) -> Quote:
44
45
  :param quote_id:
45
46
  :return: Quote.
46
47
  """
47
- return _get_quotes("/quote/%d" % quote_id)[0]
48
+ return _get_quotes(f"/quote/{urlquote(str(quote_id))}")[0]
48
49
 
49
50
 
50
51
  def all_quotes() -> Sequence[Quote]:
@@ -55,4 +56,4 @@ def all_quotes() -> Sequence[Quote]:
55
56
 
56
57
  :return: sequence of Quote objects.
57
58
  """
58
- return _get_quotes("/quote/all")
59
+ return _get_quotes("/quote/all")
@@ -4,16 +4,18 @@ Endpoints related to users.
4
4
  https://xtools.readthedocs.io/en/stable/api/user.html
5
5
  """
6
6
 
7
- from typing import Optional, Generator, Sequence
7
+ from collections.abc import Generator, Sequence
8
8
  from datetime import date
9
+ from typing import Any
9
10
 
10
11
  from . import base
12
+ import contextlib
11
13
 
12
14
 
13
15
  def simple_edit_count(project: str, username: str,
14
- namespace: Optional[int] = None,
15
- start: Optional[date] = None,
16
- end: Optional[date] = None) -> dict:
16
+ namespace: int | None = None,
17
+ start: date | None = None,
18
+ end: date | None = None) -> dict[str, Any]:
17
19
  """
18
20
  https://xtools.readthedocs.io/en/stable/api/user.html#simple-edit-count
19
21
 
@@ -51,11 +53,11 @@ def simple_edit_count(project: str, username: str,
51
53
 
52
54
 
53
55
  def number_of_pages_created(project: str, username: str,
54
- namespace: Optional[str] = None,
55
- redirects: Optional[str] = None,
56
- deleted: Optional[str] = None,
57
- start: Optional[date] = None,
58
- end: Optional[date] = None) -> dict:
56
+ namespace: str | None = None,
57
+ redirects: str | None = None,
58
+ deleted: str | None = None,
59
+ start: date | None = None,
60
+ end: date | None = None) -> dict[str, Any]:
59
61
  """
60
62
  Return the number of pages created by a user along with some other info.
61
63
 
@@ -99,7 +101,7 @@ def number_of_pages_created(project: str, username: str,
99
101
  return base.get(path)
100
102
 
101
103
 
102
- def _fix_page(page: dict) -> dict:
104
+ def _fix_page(page: dict[str, Any]) -> dict[str, Any]:
103
105
  """
104
106
  Fix a page dict as returned by the API.
105
107
  :param page:
@@ -113,13 +115,13 @@ def _fix_page(page: dict) -> dict:
113
115
 
114
116
 
115
117
  def pages_created(project: str, username: str,
116
- namespace: str = None,
117
- redirects: Optional[str] = None,
118
- deleted: Optional[str] = None,
119
- start: Optional[date] = None,
120
- end: Optional[date] = None,
121
- offset: Optional[int] = None,
122
- all_times: bool = False) -> dict:
118
+ namespace: str | None = None,
119
+ redirects: str | None = None,
120
+ deleted: str | None = None,
121
+ start: date | str | None = None,
122
+ end: date | str | None = None,
123
+ offset: int | None = None,
124
+ all_times: bool = False) -> dict[str, Any]:
123
125
  """
124
126
  Return pages created by a user. This does not handle pagination; see ``pages_created_iter`` for that.
125
127
 
@@ -161,8 +163,8 @@ def pages_created(project: str, username: str,
161
163
  :param namespace: one namespace (default is ``"0"``, the main one) or ``"all"`` for all of them.
162
164
  :param redirects: either ``"noredirects"`` (default), ``"onlyredirects"``, or ``"all"`` for both.
163
165
  :param deleted: either ``"live"``, ``"deleted"``, or ``"all"`` (default).
164
- :param start:
165
- :param end:
166
+ :param start: start date. Can be a ``date`` object or a string ``"YYYY-MM-DD"``.
167
+ :param end: end date. Can be a ``date`` object or a string ``"YYYY-MM-DD"``.
166
168
  :param offset:
167
169
  :param all_times: if ``True``, fetch all pages instead of the more recent ones. This overrides ``start``
168
170
  and ``end``.
@@ -194,10 +196,8 @@ def pages_created(project: str, username: str,
194
196
  keys = sorted(pages.keys(), key=int)
195
197
 
196
198
  is_namespaced = False
197
- try:
199
+ with contextlib.suppress(StopIteration):
198
200
  is_namespaced = isinstance(next(pages.values().__iter__()), list)
199
- except StopIteration:
200
- pass
201
201
 
202
202
  if is_namespaced:
203
203
  ret["pages"] = [_fix_page(page) for k in keys for page in pages[k]]
@@ -208,12 +208,12 @@ def pages_created(project: str, username: str,
208
208
 
209
209
 
210
210
  def pages_created_iter(project: str, username: str,
211
- namespace: str = None,
212
- redirects: Optional[str] = None,
213
- deleted: Optional[str] = None,
214
- start: Optional[date] = None,
215
- end: Optional[date] = None,
216
- all_times: bool = False) -> Generator[dict, None, None]:
211
+ namespace: str | None = None,
212
+ redirects: str | None = None,
213
+ deleted: str | None = None,
214
+ start: date | None = None,
215
+ end: date | None = None,
216
+ all_times: bool = False) -> Generator[dict[str, Any]]:
217
217
  """
218
218
  Equivalent of ``pages_created`` that yields page dicts and does the pagination for you.
219
219
 
@@ -235,23 +235,22 @@ def pages_created_iter(project: str, username: str,
235
235
  and ``end``.
236
236
  :return:
237
237
  """
238
- offset = 0
238
+ offset: int | None = 0
239
239
 
240
240
  while offset is not None:
241
241
  ret = pages_created(project, username,
242
242
  namespace=namespace, redirects=redirects, deleted=deleted,
243
243
  start=start, end=end, all_times=all_times, offset=offset)
244
244
  offset = ret.get("continue")
245
- for page in ret["pages"]:
246
- yield page
245
+ yield from ret["pages"]
247
246
 
248
247
 
249
248
  def automated_edit_counter(project: str, username: str,
250
- namespace: Optional[str] = None,
251
- start: Optional[date] = None,
252
- end: Optional[date] = None,
253
- offset: Optional[int] = None,
254
- tools: bool = False) -> dict:
249
+ namespace: str | None = None,
250
+ start: date | None = None,
251
+ end: date | None = None,
252
+ offset: int | None = None,
253
+ tools: bool = False) -> dict[str, Any]:
255
254
  """
256
255
  https://xtools.readthedocs.io/en/stable/api/user.html#automated-edit-counter
257
256
 
@@ -278,10 +277,10 @@ def automated_edit_counter(project: str, username: str,
278
277
  return base.get(path)
279
278
 
280
279
 
281
- def _edits(what: str, project: str, username: str, namespace: str = "0",
282
- start: Optional[date] = None,
283
- end: Optional[date] = None,
284
- offset: Optional[int] = None) -> dict:
280
+ def _edits(what: str, project: str, username: str, namespace: str | None = None,
281
+ start: date | None = None,
282
+ end: date | None = None,
283
+ offset: int | None = None) -> dict[str, Any]:
285
284
  """
286
285
  Base function for both ``non_automated_edits`` and ``automated_edits``.
287
286
 
@@ -307,10 +306,10 @@ def _edits(what: str, project: str, username: str, namespace: str = "0",
307
306
 
308
307
 
309
308
  def non_automated_edits(project: str, username: str,
310
- namespace: Optional[str] = None,
311
- start: Optional[date] = None,
312
- end: Optional[date] = None,
313
- offset: Optional[int] = None) -> dict:
309
+ namespace: str | None = None,
310
+ start: date | None = None,
311
+ end: date | None = None,
312
+ offset: int | None = None) -> dict[str, Any]:
314
313
  """
315
314
  https://xtools.readthedocs.io/en/stable/api/user.html#non-automated-edits
316
315
 
@@ -327,10 +326,10 @@ def non_automated_edits(project: str, username: str,
327
326
 
328
327
 
329
328
  def automated_edits(project: str, username: str,
330
- namespace: Optional[str] = None,
331
- start: Optional[date] = None,
332
- end: Optional[date] = None,
333
- offset: Optional[int] = None) -> dict:
329
+ namespace: str | None = None,
330
+ start: date | None = None,
331
+ end: date | None = None,
332
+ offset: int | None = None) -> dict[str, Any]:
334
333
  """
335
334
  https://xtools.readthedocs.io/en/stable/api/user.html#automated-edits
336
335
 
@@ -347,9 +346,9 @@ def automated_edits(project: str, username: str,
347
346
 
348
347
 
349
348
  def edit_summaries(project: str, username: str,
350
- namespace: Optional[str] = None,
351
- start: Optional[date] = None,
352
- end: Optional[date] = None) -> dict:
349
+ namespace: str | None = None,
350
+ start: date | None = None,
351
+ end: date | None = None) -> dict[str, Any]:
353
352
  """
354
353
  https://xtools.readthedocs.io/en/stable/api/user.html#edit-summaries
355
354
 
@@ -371,8 +370,8 @@ def edit_summaries(project: str, username: str,
371
370
 
372
371
 
373
372
  def top_edits(project: str, username: str,
374
- namespace: Optional[str] = None,
375
- page_title: Optional[str] = None) -> dict:
373
+ namespace: str | None = None,
374
+ page_title: str | None = None) -> dict[str, Any]:
376
375
  """
377
376
  Return the top-edited pages by a user, or all edits made by a user to a specific page.
378
377
 
@@ -395,8 +394,8 @@ def top_edits(project: str, username: str,
395
394
 
396
395
 
397
396
  def category_edit_counter(project: str, username: str, categories: Sequence[str],
398
- start: Optional[date] = None,
399
- end: Optional[date] = None) -> dict:
397
+ start: date | None = None,
398
+ end: date | None = None) -> dict[str, Any]:
400
399
  """
401
400
  https://xtools.readthedocs.io/en/stable/api/user.html#category-edit-counter
402
401
 
@@ -417,7 +416,7 @@ def category_edit_counter(project: str, username: str, categories: Sequence[str]
417
416
  return base.get(path)
418
417
 
419
418
 
420
- def log_counts(project: str, username: str) -> dict:
419
+ def log_counts(project: str, username: str) -> dict[str, Any]:
421
420
  """
422
421
  Return counts of logged actions made by a user.
423
422
 
@@ -445,10 +444,10 @@ def log_counts(project: str, username: str) -> dict:
445
444
  :param username:
446
445
  :return:
447
446
  """
448
- return base.get("/user/log_counts/%s/%s" % (project, username))
447
+ return base.get(f"/user/log_counts/{project}/{username}")
449
448
 
450
449
 
451
- def namespace_totals(project: str, username: str) -> dict:
450
+ def namespace_totals(project: str, username: str) -> dict[str, Any]:
452
451
  """
453
452
  Return the edit count for each namespace with at least 1 edit.
454
453
 
@@ -458,10 +457,10 @@ def namespace_totals(project: str, username: str) -> dict:
458
457
  :param username:
459
458
  :return:
460
459
  """
461
- return base.get("/user/namespace_totals/%s/%s" % (project, username))
460
+ return base.get(f"/user/namespace_totals/{project}/{username}")
462
461
 
463
462
 
464
- def month_counts(project: str, username: str) -> dict:
463
+ def month_counts(project: str, username: str) -> dict[str, Any]:
465
464
  """
466
465
  Return the edit count of a user, grouped by namespace then year and month.
467
466
 
@@ -471,10 +470,10 @@ def month_counts(project: str, username: str) -> dict:
471
470
  :param username:
472
471
  :return:
473
472
  """
474
- return base.get("/user/month_counts/%s/%s" % (project, username))
473
+ return base.get(f"/user/month_counts/{project}/{username}")
475
474
 
476
475
 
477
- def time_card(project: str, username: str) -> dict:
476
+ def time_card(project: str, username: str) -> dict[str, Any]:
478
477
  """
479
478
  Return the relative distribution of edits made by a user based on hour of the day and day of the week.
480
479
 
@@ -501,4 +500,4 @@ def time_card(project: str, username: str) -> dict:
501
500
  :param username:
502
501
  :return:
503
502
  """
504
- return base.get("/user/timecard/%s/%s" % (project, username))
503
+ return base.get(f"/user/timecard/{project}/{username}")
xtools-0.1.2/PKG-INFO DELETED
@@ -1,42 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: xtools
3
- Version: 0.1.2
4
- Summary: XTools API wrapper
5
- Home-page: https://github.com/bfontaine/xtools
6
- Author: Baptiste Fontaine
7
- Author-email: b@ptistefontaine.fr
8
- License: MIT
9
- Description: # xtools
10
- [![Version on Pypi](https://img.shields.io/pypi/v/xtools.svg?label=PyPI)](https://pypi.org/project/coveralls/)
11
- [![Coverage Status](https://coveralls.io/repos/github/bfontaine/xtools/badge.svg?branch=master)](https://coveralls.io/github/bfontaine/xtools?branch=master)
12
-
13
- **xtools** is a Python wrapper around [XTools][]’ API.
14
-
15
- [XTools]: https://xtools.readthedocs.io/en/stable/index.html
16
-
17
- Note this project is not affiliated with nor endorsed by XTools.
18
-
19
- ## Install
20
-
21
- pip install xtools
22
-
23
- ### Requirements
24
-
25
- Python 3.5+.
26
-
27
- ## Usage
28
-
29
- [Read the docs](https://python-xtools.readthedocs.io/en/latest/).
30
- Platform: UNKNOWN
31
- Classifier: Development Status :: 4 - Beta
32
- Classifier: Intended Audience :: Developers
33
- Classifier: License :: OSI Approved :: MIT License
34
- Classifier: Topic :: Software Development
35
- Classifier: Programming Language :: Python
36
- Classifier: Programming Language :: Python :: 3 :: Only
37
- Classifier: Programming Language :: Python :: 3.5
38
- Classifier: Programming Language :: Python :: 3.6
39
- Classifier: Programming Language :: Python :: 3.7
40
- Classifier: Programming Language :: Python :: 3.8
41
- Classifier: Operating System :: OS Independent
42
- Description-Content-Type: text/markdown
xtools-0.1.2/setup.cfg DELETED
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
xtools-0.1.2/setup.py DELETED
@@ -1,27 +0,0 @@
1
- from setuptools import setup
2
-
3
- setup(name="xtools",
4
- version="0.1.2",
5
- author="Baptiste Fontaine",
6
- author_email="b@ptistefontaine.fr",
7
- description="XTools API wrapper",
8
- long_description=open('README.md').read(),
9
- long_description_content_type='text/markdown',
10
- url="https://github.com/bfontaine/xtools",
11
- install_requires=['requests'],
12
- license="MIT",
13
- packages=['xtools'],
14
- # https://pypi.org/pypi?%3Aaction=list_classifiers
15
- classifiers=[
16
- 'Development Status :: 4 - Beta',
17
- 'Intended Audience :: Developers',
18
- 'License :: OSI Approved :: MIT License',
19
- 'Topic :: Software Development',
20
- 'Programming Language :: Python',
21
- 'Programming Language :: Python :: 3 :: Only',
22
- 'Programming Language :: Python :: 3.5',
23
- 'Programming Language :: Python :: 3.6',
24
- 'Programming Language :: Python :: 3.7',
25
- 'Programming Language :: Python :: 3.8',
26
- 'Operating System :: OS Independent',
27
- ])
@@ -1,11 +0,0 @@
1
- from .page import article_info, prose, links, top_editors, assessments
2
- from .project import (normalize_project, namespaces, page_assessments, page_assessments_configuration, automated_tools,
3
- admins_and_user_groups, admin_statistics, patroller_statistics, steward_statistics)
4
- from .quote import random_quote, single_quote, all_quotes
5
- from .user import (simple_edit_count, number_of_pages_created, pages_created, pages_created_iter,
6
- automated_edit_counter, non_automated_edits, automated_edits, edit_summaries, top_edits,
7
- category_edit_counter, log_counts, namespace_totals, month_counts, time_card)
8
-
9
- from .exceptions import BaseXToolsException, NotFound, TooManyEdits
10
-
11
- __version__ = "0.1.2"
@@ -1,18 +0,0 @@
1
- """
2
- Internal unit-testing utilities.
3
- """
4
-
5
- import unittest
6
-
7
- from xtools import base
8
-
9
- TEST_URL_PREFIX = "m://x"
10
-
11
-
12
- class TestCase(unittest.TestCase):
13
- def setUp(self):
14
- self.base_url = base.BASE_URL
15
- setattr(base, "BASE_URL", TEST_URL_PREFIX)
16
-
17
- def tearDown(self):
18
- setattr(base, "BASE_URL", self.base_url)
@@ -1,42 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: xtools
3
- Version: 0.1.2
4
- Summary: XTools API wrapper
5
- Home-page: https://github.com/bfontaine/xtools
6
- Author: Baptiste Fontaine
7
- Author-email: b@ptistefontaine.fr
8
- License: MIT
9
- Description: # xtools
10
- [![Version on Pypi](https://img.shields.io/pypi/v/xtools.svg?label=PyPI)](https://pypi.org/project/coveralls/)
11
- [![Coverage Status](https://coveralls.io/repos/github/bfontaine/xtools/badge.svg?branch=master)](https://coveralls.io/github/bfontaine/xtools?branch=master)
12
-
13
- **xtools** is a Python wrapper around [XTools][]’ API.
14
-
15
- [XTools]: https://xtools.readthedocs.io/en/stable/index.html
16
-
17
- Note this project is not affiliated with nor endorsed by XTools.
18
-
19
- ## Install
20
-
21
- pip install xtools
22
-
23
- ### Requirements
24
-
25
- Python 3.5+.
26
-
27
- ## Usage
28
-
29
- [Read the docs](https://python-xtools.readthedocs.io/en/latest/).
30
- Platform: UNKNOWN
31
- Classifier: Development Status :: 4 - Beta
32
- Classifier: Intended Audience :: Developers
33
- Classifier: License :: OSI Approved :: MIT License
34
- Classifier: Topic :: Software Development
35
- Classifier: Programming Language :: Python
36
- Classifier: Programming Language :: Python :: 3 :: Only
37
- Classifier: Programming Language :: Python :: 3.5
38
- Classifier: Programming Language :: Python :: 3.6
39
- Classifier: Programming Language :: Python :: 3.7
40
- Classifier: Programming Language :: Python :: 3.8
41
- Classifier: Operating System :: OS Independent
42
- Description-Content-Type: text/markdown
@@ -1,15 +0,0 @@
1
- README.md
2
- setup.py
3
- xtools/__init__.py
4
- xtools/base.py
5
- xtools/exceptions.py
6
- xtools/page.py
7
- xtools/project.py
8
- xtools/quote.py
9
- xtools/tests.py
10
- xtools/user.py
11
- xtools.egg-info/PKG-INFO
12
- xtools.egg-info/SOURCES.txt
13
- xtools.egg-info/dependency_links.txt
14
- xtools.egg-info/requires.txt
15
- xtools.egg-info/top_level.txt
@@ -1 +0,0 @@
1
- requests
@@ -1 +0,0 @@
1
- xtools