libmozdata 0.2.15__tar.gz → 0.2.18__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 (38) hide show
  1. {libmozdata-0.2.15/libmozdata.egg-info → libmozdata-0.2.18}/PKG-INFO +1 -1
  2. libmozdata-0.2.18/VERSION +1 -0
  3. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/HGFileInfo.py +1 -3
  4. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/bugzilla.py +16 -21
  5. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/connection.py +2 -1
  6. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/hgmozilla.py +2 -4
  7. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/lando.py +5 -6
  8. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/phabricator.py +7 -27
  9. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/redash.py +1 -3
  10. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/socorro.py +3 -5
  11. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/utils.py +50 -7
  12. {libmozdata-0.2.15 → libmozdata-0.2.18/libmozdata.egg-info}/PKG-INFO +1 -1
  13. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata.egg-info/requires.txt +1 -1
  14. {libmozdata-0.2.15 → libmozdata-0.2.18}/requirements.txt +1 -1
  15. libmozdata-0.2.15/VERSION +0 -1
  16. {libmozdata-0.2.15 → libmozdata-0.2.18}/MANIFEST.in +0 -0
  17. {libmozdata-0.2.15 → libmozdata-0.2.18}/README.md +0 -0
  18. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/BZInfo.py +0 -0
  19. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/FXRevision.py +0 -0
  20. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/FileStats.py +0 -0
  21. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/__init__.py +0 -0
  22. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/buildhub.py +0 -0
  23. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/clouseau.py +0 -0
  24. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/config.py +0 -0
  25. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/fx_trains.py +0 -0
  26. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/handler.py +0 -0
  27. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/modules.json +0 -0
  28. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/modules.py +0 -0
  29. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/patchanalysis.py +0 -0
  30. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/vcs_map.py +0 -0
  31. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/versions.py +0 -0
  32. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata/wiki_parser.py +0 -0
  33. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata.egg-info/SOURCES.txt +0 -0
  34. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata.egg-info/dependency_links.txt +0 -0
  35. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata.egg-info/not-zip-safe +0 -0
  36. {libmozdata-0.2.15 → libmozdata-0.2.18}/libmozdata.egg-info/top_level.txt +0 -0
  37. {libmozdata-0.2.15 → libmozdata-0.2.18}/setup.cfg +0 -0
  38. {libmozdata-0.2.15 → libmozdata-0.2.18}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.1
2
2
  Name: libmozdata
3
- Version: 0.2.15
3
+ Version: 0.2.18
4
4
  Summary: Library to access and aggregate several Mozilla data sources.
5
5
  Home-page: https://github.com/mozilla/libmozdata
6
6
  Author: Mozilla Release Management
@@ -0,0 +1 @@
1
+ 0.2.18
@@ -5,8 +5,6 @@
5
5
  import logging
6
6
  import re
7
7
 
8
- import six
9
-
10
8
  from . import hgmozilla
11
9
  from .connection import Query
12
10
 
@@ -32,7 +30,7 @@ class HGFileInfo(object):
32
30
  self.node = node
33
31
  self.date_type = "date" if date_type == "creation" else "pushdate"
34
32
  self.data = {}
35
- self.paths = [paths] if isinstance(paths, six.string_types) else paths
33
+ self.paths = [paths] if isinstance(paths, str) else paths
36
34
  for p in self.paths:
37
35
  self.data[p] = []
38
36
  self.bug_pattern = re.compile(r"[\t ]*[Bb][Uu][Gg][\t ]*([0-9]+)")
@@ -6,7 +6,6 @@ import functools
6
6
  import re
7
7
 
8
8
  import requests
9
- import six
10
9
  from requests import HTTPError
11
10
 
12
11
  import libmozdata.versions
@@ -75,7 +74,7 @@ class Bugzilla(BugzillaBase):
75
74
  super(Bugzilla, self).__init__(Bugzilla.URL, queries=queries, **kwargs)
76
75
  else:
77
76
  super(Bugzilla, self).__init__(Bugzilla.URL, **kwargs)
78
- if isinstance(bugids, six.string_types) or isinstance(bugids, dict):
77
+ if isinstance(bugids, str) or isinstance(bugids, dict):
79
78
  self.bugids = [bugids]
80
79
  elif isinstance(bugids, int):
81
80
  self.bugids = [str(bugids)]
@@ -196,9 +195,9 @@ class Bugzilla(BugzillaBase):
196
195
 
197
196
  def __merge_fields(f1, f2):
198
197
  if f1:
199
- f1 = {f1} if isinstance(f1, six.string_types) else set(f1)
198
+ f1 = {f1} if isinstance(f1, str) else set(f1)
200
199
  if f2:
201
- f2 = {f2} if isinstance(f2, six.string_types) else set(f2)
200
+ f2 = {f2} if isinstance(f2, str) else set(f2)
202
201
  return list(f1.union(f2))
203
202
  else:
204
203
  return f1
@@ -257,7 +256,7 @@ class Bugzilla(BugzillaBase):
257
256
 
258
257
  @staticmethod
259
258
  def get_links(bugids):
260
- if isinstance(bugids, six.string_types) or isinstance(bugids, int):
259
+ if isinstance(bugids, str) or isinstance(bugids, int):
261
260
  return "https://bugzilla.mozilla.org/" + str(bugids)
262
261
  else:
263
262
  return ["https://bugzilla.mozilla.org/" + str(bugid) for bugid in bugids]
@@ -594,7 +593,7 @@ class Bugzilla(BugzillaBase):
594
593
  header = self.get_header()
595
594
  specials = {"count_only", "limit", "order", "offset"}
596
595
  for query in self.bugids:
597
- if isinstance(query, six.string_types):
596
+ if isinstance(query, str):
598
597
  url = Bugzilla.API_URL + "?" + query
599
598
  self.bugs_results.append(
600
599
  self.session.get(
@@ -730,7 +729,7 @@ class Bugzilla(BugzillaBase):
730
729
  bugs = json["bugs"]
731
730
  if bugs:
732
731
  for key in bugs.keys():
733
- if isinstance(key, six.string_types) and key.isdigit():
732
+ if isinstance(key, str) and key.isdigit():
734
733
  comments = bugs[key]
735
734
  self.commenthandler.handle(comments, key)
736
735
  elif self.RAISE_ERROR:
@@ -775,7 +774,7 @@ class Bugzilla(BugzillaBase):
775
774
  bugs = json["bugs"]
776
775
  if bugs:
777
776
  for key in bugs.keys():
778
- if isinstance(key, six.string_types) and key.isdigit():
777
+ if isinstance(key, str) and key.isdigit():
779
778
  attachments = bugs[key]
780
779
  self.attachmenthandler.handle(attachments, key)
781
780
  elif self.RAISE_ERROR:
@@ -861,7 +860,7 @@ class BugzillaUser(BugzillaBase):
861
860
  self.fault_user_handler = Handler.get(fault_user_handler, user_data)
862
861
 
863
862
  if user_names is not None:
864
- if isinstance(user_names, six.string_types) or isinstance(user_names, int):
863
+ if isinstance(user_names, str) or isinstance(user_names, int):
865
864
  user_names = [user_names]
866
865
 
867
866
  params = [
@@ -870,8 +869,7 @@ class BugzillaUser(BugzillaBase):
870
869
  "names": [
871
870
  user_name
872
871
  for user_name in user_names
873
- if isinstance(user_name, six.string_types)
874
- and not user_name.isdigit()
872
+ if isinstance(user_name, str) and not user_name.isdigit()
875
873
  ],
876
874
  "ids": [
877
875
  str(user_id)
@@ -891,7 +889,7 @@ class BugzillaUser(BugzillaBase):
891
889
  **kwargs,
892
890
  )
893
891
  elif search_strings is not None:
894
- if isinstance(search_strings, six.string_types):
892
+ if isinstance(search_strings, str):
895
893
  search_strings = [search_strings]
896
894
 
897
895
  queries = []
@@ -952,9 +950,9 @@ class BugzillaProduct(BugzillaBase):
952
950
 
953
951
  if include_fields:
954
952
  if "include_fields" in params:
955
- if isinstance(include_fields, six.string_types):
953
+ if isinstance(include_fields, str):
956
954
  include_fields = [include_fields]
957
- if isinstance(params.get("include_fields"), six.string_types):
955
+ if isinstance(params.get("include_fields"), str):
958
956
  params["include_fields"] = [params["include_fields"]]
959
957
 
960
958
  params["include_fields"] = list(
@@ -965,9 +963,9 @@ class BugzillaProduct(BugzillaBase):
965
963
 
966
964
  if product_types:
967
965
  if "type" in params:
968
- if isinstance(product_types, six.string_types):
966
+ if isinstance(product_types, str):
969
967
  product_types = [product_types]
970
- if isinstance(params.get("type"), six.string_types):
968
+ if isinstance(params.get("type"), str):
971
969
  params["type"] = [params["type"]]
972
970
 
973
971
  params["type"] = list(set(params["type"]).union(product_types))
@@ -975,9 +973,7 @@ class BugzillaProduct(BugzillaBase):
975
973
  params["type"] = product_types
976
974
 
977
975
  elif product_names is not None:
978
- if isinstance(product_names, six.string_types) or isinstance(
979
- product_names, int
980
- ):
976
+ if isinstance(product_names, str) or isinstance(product_names, int):
981
977
  product_names = [product_names]
982
978
 
983
979
  params = {
@@ -986,8 +982,7 @@ class BugzillaProduct(BugzillaBase):
986
982
  "names": [
987
983
  product_name
988
984
  for product_name in product_names
989
- if isinstance(product_name, six.string_types)
990
- and not product_name.isdigit()
985
+ if isinstance(product_name, str) and not product_name.isdigit()
991
986
  ],
992
987
  "ids": [
993
988
  str(product_id)
@@ -61,7 +61,8 @@ class Connection(object):
61
61
  )
62
62
 
63
63
  # Error 429 is for 'Too many requests' => we retry
64
- STATUS_FORCELIST = [429]
64
+ # Also retry on server errors.
65
+ STATUS_FORCELIST = [429, 500, 502, 503, 504]
65
66
 
66
67
  def __init__(self, base_url, queries=None, **kwargs):
67
68
  """Constructor
@@ -2,8 +2,6 @@
2
2
  # License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
3
  # You can obtain one at http://mozilla.org/MPL/2.0/.
4
4
 
5
- import six
6
-
7
5
  from . import config
8
6
  from .connection import Connection, Query
9
7
 
@@ -244,7 +242,7 @@ class FileInfo(Mercurial):
244
242
 
245
243
  __base = {"node": node, "file": None}
246
244
 
247
- if isinstance(paths, six.string_types):
245
+ if isinstance(paths, str):
248
246
  __base["file"] = paths
249
247
  _dict = {}
250
248
  data[paths] = _dict
@@ -334,7 +332,7 @@ class Annotate(Mercurial):
334
332
 
335
333
  __base = {"node": node, "file": None}
336
334
 
337
- if isinstance(paths, six.string_types):
335
+ if isinstance(paths, str):
338
336
  __base["file"] = paths
339
337
  _dict = {}
340
338
  data[paths] = _dict
@@ -6,9 +6,8 @@
6
6
  from collections import namedtuple
7
7
  from urllib.parse import urljoin
8
8
 
9
- import requests
10
-
11
9
  from . import config
10
+ from .utils import get_session
12
11
 
13
12
 
14
13
  class LandoWarnings(object):
@@ -28,7 +27,7 @@ class LandoWarnings(object):
28
27
  for warning in warnings:
29
28
  warning_id = warning["id"]
30
29
 
31
- response = requests.delete(
30
+ response = get_session("lando").delete(
32
31
  f"{self.api_url}{warning_id}",
33
32
  headers={
34
33
  "X-Phabricator-API-Key": self.api_key,
@@ -45,7 +44,7 @@ class LandoWarnings(object):
45
44
  """
46
45
  Adds a warning to Lando
47
46
  """
48
- response = requests.post(
47
+ response = get_session("lando").post(
49
48
  self.api_url,
50
49
  json={
51
50
  "revision_id": revision_id,
@@ -67,7 +66,7 @@ class LandoWarnings(object):
67
66
  """
68
67
  Gets a list of warnings
69
68
  """
70
- response = requests.get(
69
+ response = get_session("lando").get(
71
70
  self.api_url,
72
71
  params={
73
72
  "revision_id": revision_id,
@@ -122,7 +121,7 @@ class LandoCommitMapAPI:
122
121
  with both full hashes
123
122
  """
124
123
  url = urljoin(self.api_url, f"{method}/{repository}/{revision}")
125
- resp = requests.get(
124
+ resp = get_session("lando").get(
126
125
  url,
127
126
  headers={
128
127
  "User-Agent": self.USER_AGENT,
@@ -7,15 +7,13 @@ import collections
7
7
  import enum
8
8
  import json
9
9
  import logging
10
- import math
11
- import time
12
10
  from functools import cached_property
13
11
  from urllib.parse import urlencode, urlparse
14
12
 
15
13
  import hglib
16
- import requests
17
14
 
18
15
  from . import config
16
+ from .utils import get_session
19
17
 
20
18
  HGMO_JSON_REV_URL_TEMPLATE = "https://hg.mozilla.org/mozilla-central/json-rev/{}"
21
19
  MOZILLA_PHABRICATOR_PROD = "https://phabricator.services.mozilla.com/api/"
@@ -210,18 +208,12 @@ class PhabricatorAPI(object):
210
208
  Phabricator Rest API client
211
209
  """
212
210
 
213
- def __init__(self, api_key, url=MOZILLA_PHABRICATOR_PROD, max_retries=5):
211
+ def __init__(self, api_key, url=MOZILLA_PHABRICATOR_PROD):
214
212
  self.USER_AGENT = config.get("User-Agent", "name", required=True)
215
213
  self.api_key = api_key
216
214
  self.url = url
217
215
  assert self.url.endswith("/api/"), "Phabricator API must end with /api/"
218
216
 
219
- # Number of API calls retries on 50x before raising an error
220
- self.max_retries = max_retries
221
- assert (
222
- isinstance(self.max_retries, int) and self.max_retries > 0
223
- ), "Invalid max_retries parameter"
224
-
225
217
  @cached_property
226
218
  def user(self):
227
219
  """
@@ -669,23 +661,11 @@ class PhabricatorAPI(object):
669
661
  # Add api token to payload
670
662
  payload["__conduit__"] = {"token": self.api_key}
671
663
 
672
- # Run POST request on api, retrying on 50x errors
673
- for nb_try in range(1, self.max_retries + 1):
674
- response = requests.post(
675
- self.url + path,
676
- headers=self.get_header(),
677
- data=urlencode({"params": json.dumps(payload), "output": "json"}),
678
- )
679
- if response.status_code >= 500:
680
- # Retry after a while on 50x using exponential backoff
681
- backoff = math.exp(nb_try)
682
- logger.info(
683
- f"Phabricator failed with status code {response.status_code}, retrying in {backoff:.1f} seconds..."
684
- )
685
- time.sleep(backoff)
686
- else:
687
- # Stop retrying on the first successful try or other HTTP errors
688
- break
664
+ response = get_session("phabricator").post(
665
+ self.url + path,
666
+ headers=self.get_header(),
667
+ data=urlencode({"params": json.dumps(payload), "output": "json"}),
668
+ )
689
669
 
690
670
  # Raise immediate 40x errors or out-of-retries 50x
691
671
  response.raise_for_status()
@@ -6,8 +6,6 @@ import functools
6
6
  import re
7
7
  from datetime import timedelta
8
8
 
9
- import six
10
-
11
9
  from . import config, utils
12
10
  from .connection import Connection, Query
13
11
 
@@ -93,7 +91,7 @@ class Redash(Connection):
93
91
  dict: containing result in json for each query
94
92
  """
95
93
  data = {}
96
- if isinstance(query_ids, six.string_types):
94
+ if isinstance(query_ids, str):
97
95
  url = Redash.API_URL + "/" + query_ids + "/results.json"
98
96
  Redash(
99
97
  Query(
@@ -2,8 +2,6 @@
2
2
  # License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
3
  # You can obtain one at http://mozilla.org/MPL/2.0/.
4
4
 
5
- import six
6
-
7
5
  from . import config, utils
8
6
  from .connection import Connection, Query
9
7
 
@@ -85,7 +83,7 @@ class SuperSearch(Socorro):
85
83
  "url" in k
86
84
  or "email" in k
87
85
  or (
88
- (isinstance(v, list) or isinstance(v, six.string_types))
86
+ (isinstance(v, list) or isinstance(v, str))
89
87
  and ("url" in v or "email" in v)
90
88
  )
91
89
  ):
@@ -175,7 +173,7 @@ class ProcessedCrash(Socorro):
175
173
  data = {}
176
174
  __base = {"crash_id": None, "datatype": "processed"}
177
175
 
178
- if isinstance(crashids, six.string_types):
176
+ if isinstance(crashids, str):
179
177
  __base["crash_id"] = crashids
180
178
  _dict = {}
181
179
  data[crashids] = _dict
@@ -243,7 +241,7 @@ class Bugs(Socorro):
243
241
  if signature in data:
244
242
  data[signature].add(hit["id"])
245
243
 
246
- if isinstance(signatures, six.string_types):
244
+ if isinstance(signatures, str):
247
245
  data = {signatures: set()}
248
246
  Bugs(
249
247
  params={"signatures": signatures},
@@ -8,17 +8,60 @@ import operator
8
8
  import os.path
9
9
  import random
10
10
  from datetime import date, datetime, timedelta, timezone
11
+ from functools import cache
11
12
  from itertools import count
12
13
 
13
14
  import dateutil.parser
14
15
  import pytz
15
- import six
16
+ import requests
16
17
  from dateutil.relativedelta import relativedelta
17
18
  from requests.utils import quote
19
+ from urllib3.util.retry import Retry
18
20
 
19
21
  __pacific = pytz.timezone("US/Pacific")
20
22
 
21
23
 
24
+ @cache
25
+ def get_session(name: str) -> requests.Session:
26
+ """Get a `requests` session with retries enabled on connection errors and
27
+ 50x HTTP error codes, using exponential backoff.
28
+
29
+ Sessions are cached by name, so calling this function multiple times with
30
+ the same name will return the same session.
31
+
32
+ Args:
33
+ name (str): an arbitrary name identifying the caller, used to cache
34
+ the session
35
+
36
+ Returns:
37
+ requests.Session: a session with retries enabled
38
+ """
39
+ session = requests.Session()
40
+
41
+ retry = Retry(
42
+ total=8,
43
+ backoff_factor=1,
44
+ status_forcelist=[429, 500, 502, 503, 504],
45
+ # Also retry non-idempotent methods (e.g. POST), since urllib3
46
+ # excludes them by default.
47
+ allowed_methods=None,
48
+ # Return the last response instead of raising, once retries are
49
+ # exhausted, so callers can keep inspecting status_code/text.
50
+ raise_on_status=False,
51
+ )
52
+
53
+ # Default HTTPAdapter uses 10 connections. Mount custom adapter to increase
54
+ # that limit. Connections are established as needed, so using a large value
55
+ # should not negatively impact performance.
56
+ http_adapter = requests.adapters.HTTPAdapter(
57
+ pool_connections=50, pool_maxsize=50, max_retries=retry
58
+ )
59
+ session.mount("https://", http_adapter)
60
+ session.mount("http://", http_adapter)
61
+
62
+ return session
63
+
64
+
22
65
  def get_best(stats):
23
66
  """Get the key which has the higher value
24
67
 
@@ -43,7 +86,7 @@ def get_timestamp(dt):
43
86
  Returns:
44
87
  int: the corresponding timestamp
45
88
  """
46
- if isinstance(dt, six.string_types):
89
+ if isinstance(dt, str):
47
90
  dt = datetime.now(timezone.utc) if dt == "now" else get_date_ymd(dt)
48
91
  return int(calendar.timegm(dt.timetuple()))
49
92
 
@@ -118,7 +161,7 @@ def get_date(_date, delta=None):
118
161
  Returns:
119
162
  str: the date as a string 'Year-month-day'
120
163
  """
121
- if isinstance(_date, six.string_types):
164
+ if isinstance(_date, str):
122
165
  _date = get_date_ymd(_date)
123
166
  if delta:
124
167
  _date -= timedelta(delta)
@@ -307,16 +350,16 @@ def get_channels():
307
350
 
308
351
 
309
352
  def get_str_list(x):
310
- if isinstance(x, six.string_types):
353
+ if isinstance(x, str):
311
354
  return [x]
312
- if isinstance(x, six.integer_types):
355
+ if isinstance(x, int):
313
356
  return [str[x]]
314
357
  if isinstance(x, list) or isinstance(x, set):
315
- return [str(y) if isinstance(y, six.integer_types) else y for y in x]
358
+ return [str(y) if isinstance(y, int) else y for y in x]
316
359
 
317
360
 
318
361
  def get_x_fwed_for_str(s):
319
- if isinstance(s, six.string_types):
362
+ if isinstance(s, str):
320
363
  return ", ".join(map(lambda x: x.strip(" \t"), s.split(",")))
321
364
  else:
322
365
  return ", ".join(map(lambda x: x.strip(" \t"), s))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.1
2
2
  Name: libmozdata
3
- Version: 0.2.15
3
+ Version: 0.2.18
4
4
  Summary: Library to access and aggregate several Mozilla data sources.
5
5
  Home-page: https://github.com/mozilla/libmozdata
6
6
  Author: Mozilla Release Management
@@ -5,5 +5,5 @@ pytz>=2022
5
5
  requests-futures>=0.9.8
6
6
  requests[security]>=2.7.0
7
7
  setuptools>=83.0.0
8
- six>=1.17.0
8
+ urllib3>=1.26.0
9
9
  whatthepatch>=0.0.4
@@ -5,5 +5,5 @@ pytz>=2022
5
5
  requests-futures>=0.9.8
6
6
  requests[security]>=2.7.0
7
7
  setuptools>=83.0.0
8
- six>=1.17.0
8
+ urllib3>=1.26.0
9
9
  whatthepatch>=0.0.4
libmozdata-0.2.15/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.2.15
File without changes
File without changes
File without changes
File without changes