libmozdata 0.2.12__tar.gz → 0.2.14__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.
- {libmozdata-0.2.12/libmozdata.egg-info → libmozdata-0.2.14}/PKG-INFO +1 -1
- libmozdata-0.2.14/VERSION +1 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/FileStats.py +3 -2
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/bugzilla.py +6 -3
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/config.py +0 -2
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/connection.py +1 -1
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/patchanalysis.py +7 -3
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/phabricator.py +29 -8
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/utils.py +13 -11
- {libmozdata-0.2.12 → libmozdata-0.2.14/libmozdata.egg-info}/PKG-INFO +1 -1
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata.egg-info/SOURCES.txt +0 -1
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata.egg-info/requires.txt +2 -2
- {libmozdata-0.2.12 → libmozdata-0.2.14}/requirements.txt +2 -2
- libmozdata-0.2.12/VERSION +0 -1
- libmozdata-0.2.12/libmozdata/release_owners.py +0 -86
- {libmozdata-0.2.12 → libmozdata-0.2.14}/MANIFEST.in +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/README.md +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/BZInfo.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/FXRevision.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/HGFileInfo.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/__init__.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/buildhub.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/clouseau.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/fx_trains.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/handler.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/hgmozilla.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/lando.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/modules.json +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/modules.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/redash.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/socorro.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/vcs_map.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/versions.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata/wiki_parser.py +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata.egg-info/dependency_links.txt +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata.egg-info/not-zip-safe +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/libmozdata.egg-info/top_level.txt +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/setup.cfg +0 -0
- {libmozdata-0.2.12 → libmozdata-0.2.14}/setup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.2.14
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
6
|
import numbers
|
|
7
|
-
from datetime import datetime, timedelta
|
|
7
|
+
from datetime import datetime, timedelta, timezone
|
|
8
8
|
from pprint import pprint
|
|
9
9
|
|
|
10
10
|
from . import config, modules, utils
|
|
@@ -36,7 +36,8 @@ class FileStats(object):
|
|
|
36
36
|
)
|
|
37
37
|
self.utc_ts_from = (
|
|
38
38
|
utils.get_timestamp(
|
|
39
|
-
datetime.
|
|
39
|
+
datetime.fromtimestamp(utc_ts, tz=timezone.utc)
|
|
40
|
+
+ timedelta(-self.max_days)
|
|
40
41
|
)
|
|
41
42
|
if isinstance(utc_ts, numbers.Number) and utc_ts > 0
|
|
42
43
|
else None
|
|
@@ -561,7 +561,8 @@ class Bugzilla(BugzillaBase):
|
|
|
561
561
|
res: result
|
|
562
562
|
"""
|
|
563
563
|
if res.status_code == 200:
|
|
564
|
-
|
|
564
|
+
data = res.json()
|
|
565
|
+
for bug in data["bugs"]:
|
|
565
566
|
self.bughandler.handle(bug)
|
|
566
567
|
elif self.RAISE_ERROR:
|
|
567
568
|
res.raise_for_status()
|
|
@@ -616,7 +617,8 @@ class Bugzilla(BugzillaBase):
|
|
|
616
617
|
timeout=self.TIMEOUT,
|
|
617
618
|
)
|
|
618
619
|
if r.ok:
|
|
619
|
-
|
|
620
|
+
data = r.json()
|
|
621
|
+
count = data["bug_count"]
|
|
620
622
|
del params["count_only"]
|
|
621
623
|
params["limit"] = Bugzilla.BUGZILLA_CHUNK_SIZE
|
|
622
624
|
params["order"] = "bug_id"
|
|
@@ -652,7 +654,8 @@ class Bugzilla(BugzillaBase):
|
|
|
652
654
|
|
|
653
655
|
def cb(res, *args, **kwargs):
|
|
654
656
|
if res.status_code == 200:
|
|
655
|
-
|
|
657
|
+
data = res.json()
|
|
658
|
+
for bug in data["bugs"]:
|
|
656
659
|
_list.add(bug["id"])
|
|
657
660
|
elif self.RAISE_ERROR:
|
|
658
661
|
res.raise_for_status()
|
|
@@ -78,7 +78,6 @@ def set_config(conf):
|
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
def get(section, option, default=None, type=str, required=False):
|
|
81
|
-
global __config
|
|
82
81
|
value = __config.get(section, option, default=default, type=type)
|
|
83
82
|
if required:
|
|
84
83
|
assert value is not None, f"Option {option} in section {section} is not set"
|
|
@@ -86,5 +85,4 @@ def get(section, option, default=None, type=str, required=False):
|
|
|
86
85
|
|
|
87
86
|
|
|
88
87
|
def set_default_value(section, option, value):
|
|
89
|
-
global __config
|
|
90
88
|
__config.set_default(section, option, value)
|
|
@@ -4,7 +4,7 @@ import numbers
|
|
|
4
4
|
import re
|
|
5
5
|
import warnings
|
|
6
6
|
import weakref
|
|
7
|
-
from datetime import datetime, timedelta
|
|
7
|
+
from datetime import datetime, timedelta, timezone
|
|
8
8
|
|
|
9
9
|
import whatthepatch
|
|
10
10
|
|
|
@@ -665,7 +665,9 @@ def bug_analysis(bug, uplift_channel=None, author_cache={}, reviewer_cache={}):
|
|
|
665
665
|
hgmozilla.RawRevision.get_revision(obj["channel"], rev),
|
|
666
666
|
author_names,
|
|
667
667
|
reviewers,
|
|
668
|
-
utils.as_utc(
|
|
668
|
+
utils.as_utc(
|
|
669
|
+
datetime.fromtimestamp(obj["creation_date"], tz=timezone.utc)
|
|
670
|
+
),
|
|
669
671
|
)
|
|
670
672
|
)
|
|
671
673
|
else:
|
|
@@ -976,7 +978,9 @@ def get_patch_info(
|
|
|
976
978
|
bug_pattern = re.compile(r"[\t ]*[Bb][Uu][Gg][\t ]*([0-9]+)")
|
|
977
979
|
|
|
978
980
|
def handler_revision(json, data):
|
|
979
|
-
data["date"] = utils.as_utc(
|
|
981
|
+
data["date"] = utils.as_utc(
|
|
982
|
+
datetime.fromtimestamp(json["pushdate"][0], tz=timezone.utc)
|
|
983
|
+
)
|
|
980
984
|
data["backedout"] = json.get("backedoutby", "") != ""
|
|
981
985
|
m = bug_pattern.search(json["desc"])
|
|
982
986
|
if not m or m.group(1) != data["bugid"]:
|
|
@@ -7,6 +7,8 @@ import collections
|
|
|
7
7
|
import enum
|
|
8
8
|
import json
|
|
9
9
|
import logging
|
|
10
|
+
import math
|
|
11
|
+
import time
|
|
10
12
|
from functools import cached_property
|
|
11
13
|
from urllib.parse import urlencode, urlparse
|
|
12
14
|
|
|
@@ -208,12 +210,18 @@ class PhabricatorAPI(object):
|
|
|
208
210
|
Phabricator Rest API client
|
|
209
211
|
"""
|
|
210
212
|
|
|
211
|
-
def __init__(self, api_key, url=MOZILLA_PHABRICATOR_PROD):
|
|
213
|
+
def __init__(self, api_key, url=MOZILLA_PHABRICATOR_PROD, max_retries=5):
|
|
212
214
|
self.USER_AGENT = config.get("User-Agent", "name", required=True)
|
|
213
215
|
self.api_key = api_key
|
|
214
216
|
self.url = url
|
|
215
217
|
assert self.url.endswith("/api/"), "Phabricator API must end with /api/"
|
|
216
218
|
|
|
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
|
+
|
|
217
225
|
@cached_property
|
|
218
226
|
def user(self):
|
|
219
227
|
"""
|
|
@@ -236,7 +244,7 @@ class PhabricatorAPI(object):
|
|
|
236
244
|
diff_id=None,
|
|
237
245
|
revision_phid=None,
|
|
238
246
|
output_cursor=False,
|
|
239
|
-
**params
|
|
247
|
+
**params,
|
|
240
248
|
):
|
|
241
249
|
"""
|
|
242
250
|
Find details of differential diffs from a Differential diff or revision
|
|
@@ -661,12 +669,25 @@ class PhabricatorAPI(object):
|
|
|
661
669
|
# Add api token to payload
|
|
662
670
|
payload["__conduit__"] = {"token": self.api_key}
|
|
663
671
|
|
|
664
|
-
# Run POST request on api
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
|
689
|
+
|
|
690
|
+
# Raise immediate 40x errors or out-of-retries 50x
|
|
670
691
|
response.raise_for_status()
|
|
671
692
|
|
|
672
693
|
# Check response
|
|
@@ -7,7 +7,7 @@ import math
|
|
|
7
7
|
import operator
|
|
8
8
|
import os.path
|
|
9
9
|
import random
|
|
10
|
-
from datetime import date, datetime, timedelta
|
|
10
|
+
from datetime import date, datetime, timedelta, timezone
|
|
11
11
|
from itertools import count
|
|
12
12
|
|
|
13
13
|
import dateutil.parser
|
|
@@ -44,7 +44,7 @@ def get_timestamp(dt):
|
|
|
44
44
|
int: the corresponding timestamp
|
|
45
45
|
"""
|
|
46
46
|
if isinstance(dt, six.string_types):
|
|
47
|
-
dt = datetime.
|
|
47
|
+
dt = datetime.now(timezone.utc) if dt == "now" else get_date_ymd(dt)
|
|
48
48
|
return int(calendar.timegm(dt.timetuple()))
|
|
49
49
|
|
|
50
50
|
|
|
@@ -57,7 +57,7 @@ def get_date_from_timestamp(ts):
|
|
|
57
57
|
Returns:
|
|
58
58
|
datetime.datetime: the corresponding datetime in UTC tz
|
|
59
59
|
"""
|
|
60
|
-
return datetime.
|
|
60
|
+
return datetime.fromtimestamp(ts, tz=pytz.utc)
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
def get_date_ymd(dt):
|
|
@@ -75,13 +75,13 @@ def get_date_ymd(dt):
|
|
|
75
75
|
return as_utc(dt)
|
|
76
76
|
|
|
77
77
|
if dt == "today":
|
|
78
|
-
today = datetime.
|
|
78
|
+
today = datetime.now(timezone.utc)
|
|
79
79
|
return pytz.utc.localize(datetime(today.year, today.month, today.day))
|
|
80
80
|
elif dt == "tomorrow":
|
|
81
|
-
tomorrow = datetime.
|
|
81
|
+
tomorrow = datetime.now(timezone.utc) + timedelta(1)
|
|
82
82
|
return pytz.utc.localize(datetime(tomorrow.year, tomorrow.month, tomorrow.day))
|
|
83
83
|
elif dt == "yesterday":
|
|
84
|
-
yesterday = datetime.
|
|
84
|
+
yesterday = datetime.now(timezone.utc) - timedelta(1)
|
|
85
85
|
return pytz.utc.localize(
|
|
86
86
|
datetime(yesterday.year, yesterday.month, yesterday.day)
|
|
87
87
|
)
|
|
@@ -131,7 +131,7 @@ def get_now_timestamp():
|
|
|
131
131
|
Returns:
|
|
132
132
|
int: timestamp for now
|
|
133
133
|
"""
|
|
134
|
-
return get_timestamp(datetime.
|
|
134
|
+
return get_timestamp(datetime.now(timezone.utc))
|
|
135
135
|
|
|
136
136
|
|
|
137
137
|
def is64(cpu_name):
|
|
@@ -327,10 +327,12 @@ def get_params_for_url(params):
|
|
|
327
327
|
"?"
|
|
328
328
|
+ "&".join(
|
|
329
329
|
[
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
330
|
+
(
|
|
331
|
+
quote(name) + "=" + quote(str(value))
|
|
332
|
+
if not isinstance(value, list)
|
|
333
|
+
else "&".join(
|
|
334
|
+
[quote(name) + "=" + quote(str(intValue)) for intValue in value]
|
|
335
|
+
)
|
|
334
336
|
)
|
|
335
337
|
for name, value in sorted(params.items(), key=lambda p: p[0])
|
|
336
338
|
if value is not None
|
libmozdata-0.2.12/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.2.12
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
-
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
3
|
-
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
4
|
-
|
|
5
|
-
import requests
|
|
6
|
-
from requests.exceptions import HTTPError
|
|
7
|
-
|
|
8
|
-
from . import config, utils
|
|
9
|
-
from .wiki_parser import InvalidWiki, WikiParser
|
|
10
|
-
|
|
11
|
-
OWNERS_URL = "https://wiki.mozilla.org/Release_Management/Release_owners"
|
|
12
|
-
_OWNERS = None
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def _get_sub_versions(s):
|
|
16
|
-
s = s.strip()
|
|
17
|
-
s = s.split(".")
|
|
18
|
-
return [int(v.split(" ", 1)[0]) for v in s]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def get_versions(s):
|
|
22
|
-
fx = "Firefox "
|
|
23
|
-
if not s.startswith(fx):
|
|
24
|
-
raise InvalidWiki('Invalid version format, expect: "Firefox ..."')
|
|
25
|
-
N = len(fx)
|
|
26
|
-
version = s[N:]
|
|
27
|
-
versions = version.split(";")
|
|
28
|
-
return [_get_sub_versions(v) for v in versions]
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _get_list_people(s):
|
|
32
|
-
return [x.strip() for x in s.split(",")]
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def get_owners():
|
|
36
|
-
global _OWNERS
|
|
37
|
-
if _OWNERS is not None:
|
|
38
|
-
return _OWNERS
|
|
39
|
-
|
|
40
|
-
resp = requests.get(
|
|
41
|
-
OWNERS_URL,
|
|
42
|
-
headers={"User-Agent": config.get("User-Agent", "name", required=True)},
|
|
43
|
-
)
|
|
44
|
-
try:
|
|
45
|
-
resp.raise_for_status()
|
|
46
|
-
except HTTPError as e:
|
|
47
|
-
raise InvalidWiki("Failed to load wiki data") from e
|
|
48
|
-
|
|
49
|
-
html = resp.text.encode("ascii", errors="ignore")
|
|
50
|
-
|
|
51
|
-
parser = WikiParser(tables=[0])
|
|
52
|
-
try:
|
|
53
|
-
parser.feed(html)
|
|
54
|
-
except StopIteration:
|
|
55
|
-
table = parser.get_tables()[0]
|
|
56
|
-
if [
|
|
57
|
-
"Firefox Version",
|
|
58
|
-
"Owner",
|
|
59
|
-
"Secondary",
|
|
60
|
-
"Engineering REO",
|
|
61
|
-
"Release Duty",
|
|
62
|
-
"Corresponding ESR",
|
|
63
|
-
"Release Date",
|
|
64
|
-
] != table[0]:
|
|
65
|
-
raise InvalidWiki("Column headers are wrong")
|
|
66
|
-
|
|
67
|
-
_OWNERS = []
|
|
68
|
-
for row in table[1:]:
|
|
69
|
-
try:
|
|
70
|
-
# sometimes the date is 2019-XX-XX (when the date is not known)
|
|
71
|
-
release_date = utils.get_date_ymd(row[6])
|
|
72
|
-
except (AssertionError, ValueError):
|
|
73
|
-
continue
|
|
74
|
-
|
|
75
|
-
_OWNERS.append(
|
|
76
|
-
{
|
|
77
|
-
"version": get_versions(row[0])[0][0],
|
|
78
|
-
"owner": row[1],
|
|
79
|
-
"secondary": row[2],
|
|
80
|
-
"engineering reo": row[3],
|
|
81
|
-
"release duty": _get_list_people(row[4]),
|
|
82
|
-
"corresponding esr": row[5],
|
|
83
|
-
"release date": release_date,
|
|
84
|
-
}
|
|
85
|
-
)
|
|
86
|
-
return _OWNERS
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|