ietfdata 0.7.0__tar.gz → 0.7.2__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.
- {ietfdata-0.7.0 → ietfdata-0.7.2}/PKG-INFO +1 -1
- ietfdata-0.7.2/examples/chatlogs.py +54 -0
- ietfdata-0.7.2/examples/emails-ietf-community.py +105 -0
- ietfdata-0.7.2/examples/rfc-per-year-ietf.py +62 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/ietfdata/datatracker.py +8 -6
- {ietfdata-0.7.0 → ietfdata-0.7.2}/ietfdata/mailarchive2.py +46 -43
- {ietfdata-0.7.0 → ietfdata-0.7.2}/ietfdata/rfcindex.py +52 -51
- {ietfdata-0.7.0 → ietfdata-0.7.2}/ietfdata.egg-info/PKG-INFO +1 -1
- {ietfdata-0.7.0 → ietfdata-0.7.2}/ietfdata.egg-info/SOURCES.txt +3 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/pyproject.toml +1 -1
- {ietfdata-0.7.0 → ietfdata-0.7.2}/setup.py +1 -1
- {ietfdata-0.7.0 → ietfdata-0.7.2}/tests/test_datatracker.py +57 -32
- {ietfdata-0.7.0 → ietfdata-0.7.2}/tests/test_rfcindex.py +1 -1
- {ietfdata-0.7.0 → ietfdata-0.7.2}/LICENSE +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/README.md +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/__init__.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/bluesheets.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/draft-authors.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/draft-references.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/draft-submissions-by-date.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/draft-submissions.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/drafts-for-person.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/drafts-for-rfc.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/drafts-for-wg.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/emails-per-person.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/iesg-processing-time.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/ietf-leadership.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/non-wg-standards-track-rfcs.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/org-chart.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/person-attendance.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/person-emails.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/person.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/recent-pubreq.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/rfc-data.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/rfc-per-year.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/rfc-streams.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/examples/role-names.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/ietfdata/__init__.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/ietfdata/datatracker_ext.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/ietfdata/py.typed +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/ietfdata.egg-info/dependency_links.txt +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/ietfdata.egg-info/requires.txt +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/ietfdata.egg-info/top_level.txt +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/setup.cfg +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/tests/test_datatracker_coverage.py +0 -0
- {ietfdata-0.7.0 → ietfdata-0.7.2}/tests/test_mailarchive2.py +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Copyright (C) 2024 University of Glasgow, University of St Andrews
|
|
2
|
+
#
|
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
|
4
|
+
# modification, are permitted provided that the following conditions
|
|
5
|
+
# are met:
|
|
6
|
+
#
|
|
7
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
8
|
+
# this list of conditions and the following disclaimer.
|
|
9
|
+
#
|
|
10
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
# documentation and/or other materials provided with the distribution.
|
|
13
|
+
#
|
|
14
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
15
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
16
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
17
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
18
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
19
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
20
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
21
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
22
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
23
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
24
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
25
|
+
|
|
26
|
+
import os
|
|
27
|
+
import requests
|
|
28
|
+
import sys
|
|
29
|
+
|
|
30
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
|
31
|
+
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from ietfdata.datatracker import *
|
|
34
|
+
from ietfdata.rfcindex import *
|
|
35
|
+
|
|
36
|
+
dt = DataTracker()
|
|
37
|
+
|
|
38
|
+
with requests.Session() as session:
|
|
39
|
+
print("Finding chatlogs for ohai WG:")
|
|
40
|
+
|
|
41
|
+
chatlog = dt.document_type_from_slug("chatlog")
|
|
42
|
+
ohai_wg = dt.group_from_acronym("ohai")
|
|
43
|
+
|
|
44
|
+
for doc in dt.documents(doctype = chatlog, group = ohai_wg):
|
|
45
|
+
print(doc)
|
|
46
|
+
print(f" {doc.title}")
|
|
47
|
+
print(f" {doc.url()}")
|
|
48
|
+
|
|
49
|
+
response = session.get(doc.url(), verify=True)
|
|
50
|
+
if response.status_code != 200:
|
|
51
|
+
print(f" {response.status_code}")
|
|
52
|
+
|
|
53
|
+
print("")
|
|
54
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Copyright (C) 2024 University of Glasgow
|
|
2
|
+
#
|
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
|
4
|
+
# modification, are permitted provided that the following conditions
|
|
5
|
+
# are met:
|
|
6
|
+
#
|
|
7
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
8
|
+
# this list of conditions and the following disclaimer.
|
|
9
|
+
#
|
|
10
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
# documentation and/or other materials provided with the distribution.
|
|
13
|
+
#
|
|
14
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
15
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
16
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
17
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
18
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
19
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
20
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
21
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
22
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
23
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
24
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
25
|
+
|
|
26
|
+
from datetime import datetime
|
|
27
|
+
from email.utils import parseaddr
|
|
28
|
+
|
|
29
|
+
from ietfdata.datatracker import *
|
|
30
|
+
from ietfdata.mailarchive2 import *
|
|
31
|
+
|
|
32
|
+
dt = DataTracker()
|
|
33
|
+
ma = MailArchive()
|
|
34
|
+
|
|
35
|
+
#ma.update()
|
|
36
|
+
|
|
37
|
+
def date_in_range(date):
|
|
38
|
+
if date is None:
|
|
39
|
+
return False
|
|
40
|
+
return date > datetime(2019, 6, 12) and date < datetime(2024, 6, 12)
|
|
41
|
+
|
|
42
|
+
# How many people posted to all the IETF lists?
|
|
43
|
+
|
|
44
|
+
print("Checking lists:")
|
|
45
|
+
mcount_all = 0
|
|
46
|
+
all_addrs = {}
|
|
47
|
+
for ml_name in ma.mailing_list_names():
|
|
48
|
+
if ml_name in ["dmarc-report"]:
|
|
49
|
+
print(f" {ml_name} skipped")
|
|
50
|
+
continue
|
|
51
|
+
print(f" {ml_name}")
|
|
52
|
+
ml = ma.mailing_list(ml_name)
|
|
53
|
+
for msg in ml.messages():
|
|
54
|
+
if date_in_range(msg.date()):
|
|
55
|
+
mcount_all += 1
|
|
56
|
+
addr_hdr = msg.header("from")
|
|
57
|
+
if len(addr_hdr) == 1:
|
|
58
|
+
name, addr = parseaddr(addr_hdr[0])
|
|
59
|
+
if addr in all_addrs:
|
|
60
|
+
all_addrs[addr] += 1
|
|
61
|
+
else:
|
|
62
|
+
all_addrs[addr] = 1
|
|
63
|
+
|
|
64
|
+
count_all = len(all_addrs)
|
|
65
|
+
|
|
66
|
+
print(f"Addresses posting to all lists: {count_all}")
|
|
67
|
+
|
|
68
|
+
# How many people posted to the ietf@ietf.org list?
|
|
69
|
+
|
|
70
|
+
mcount_ietf = 0
|
|
71
|
+
ietf_addrs = {}
|
|
72
|
+
ietf_list = ma.mailing_list("ietf")
|
|
73
|
+
for msg in ietf_list.messages():
|
|
74
|
+
if date_in_range(msg.date()):
|
|
75
|
+
mcount_ietf += 1
|
|
76
|
+
addr_hdr = msg.header("from")
|
|
77
|
+
if len(addr_hdr) == 1:
|
|
78
|
+
name, addr = parseaddr(addr_hdr[0])
|
|
79
|
+
if addr in ietf_addrs:
|
|
80
|
+
ietf_addrs[addr] += 1
|
|
81
|
+
else:
|
|
82
|
+
ietf_addrs[addr] = 1
|
|
83
|
+
|
|
84
|
+
count_ietf = len(ietf_addrs)
|
|
85
|
+
percentage = count_ietf / count_all * 100.0
|
|
86
|
+
|
|
87
|
+
print(f"Addresses posting to ietf@ietf.org: {count_ietf}")
|
|
88
|
+
print(f"{percentage}%")
|
|
89
|
+
print(f"")
|
|
90
|
+
|
|
91
|
+
pm = mcount_ietf / mcount_all * 100.0
|
|
92
|
+
|
|
93
|
+
print(f"Number of messages (all): {mcount_all}")
|
|
94
|
+
print(f"Number of messages (IETF): {mcount_ietf} ({pm}%)")
|
|
95
|
+
|
|
96
|
+
print("")
|
|
97
|
+
c_msg = 0
|
|
98
|
+
c_per = 0
|
|
99
|
+
for addr in sorted(ietf_addrs, key = ietf_addrs.get, reverse=True):
|
|
100
|
+
c_per += 1
|
|
101
|
+
c_msg += ietf_addrs[addr]
|
|
102
|
+
print(c_per, c_msg, c_msg/mcount_ietf * 100, addr, ietf_addrs[addr])
|
|
103
|
+
if c_msg > (mcount_ietf * 0.5):
|
|
104
|
+
break
|
|
105
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Copyright (C) 2020 University of Glasgow
|
|
2
|
+
#
|
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
|
4
|
+
# modification, are permitted provided that the following conditions
|
|
5
|
+
# are met:
|
|
6
|
+
#
|
|
7
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
8
|
+
# this list of conditions and the following disclaimer.
|
|
9
|
+
#
|
|
10
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
# documentation and/or other materials provided with the distribution.
|
|
13
|
+
#
|
|
14
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
15
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
16
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
17
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
18
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
19
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
20
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
21
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
22
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
23
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
24
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
25
|
+
|
|
26
|
+
import os
|
|
27
|
+
import sys
|
|
28
|
+
|
|
29
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
|
30
|
+
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from ietfdata.datatracker import *
|
|
33
|
+
from ietfdata.rfcindex import *
|
|
34
|
+
|
|
35
|
+
# =============================================================================
|
|
36
|
+
|
|
37
|
+
ri = RFCIndex()
|
|
38
|
+
|
|
39
|
+
rfcs_per_year = {}
|
|
40
|
+
authors_per_year = {}
|
|
41
|
+
|
|
42
|
+
for rfc in ri.rfcs():
|
|
43
|
+
if rfc.stream == "IETF":
|
|
44
|
+
if rfc.year not in rfcs_per_year:
|
|
45
|
+
rfcs_per_year[rfc.year] = 0
|
|
46
|
+
rfcs_per_year[rfc.year] += 1
|
|
47
|
+
|
|
48
|
+
if rfc.year not in authors_per_year:
|
|
49
|
+
authors_per_year[rfc.year] = []
|
|
50
|
+
for author in rfc.authors:
|
|
51
|
+
if author not in authors_per_year[rfc.year]:
|
|
52
|
+
authors_per_year[rfc.year].append(author)
|
|
53
|
+
|
|
54
|
+
rfcs = 0
|
|
55
|
+
year = 2000
|
|
56
|
+
print("# year rfcs rfc_cumulative authors")
|
|
57
|
+
while year < 2025:
|
|
58
|
+
rfcs += rfcs_per_year[year]
|
|
59
|
+
print(f" {year} {rfcs_per_year[year]:4} {rfcs:14} {len(authors_per_year[year]):7}")
|
|
60
|
+
year += 1
|
|
61
|
+
|
|
62
|
+
|
|
@@ -432,7 +432,6 @@ class Document(Resource):
|
|
|
432
432
|
rfc_number : Optional[int]
|
|
433
433
|
rev : str
|
|
434
434
|
abstract : str
|
|
435
|
-
internal_comments : str
|
|
436
435
|
note : str
|
|
437
436
|
ad : Optional[PersonURI]
|
|
438
437
|
shepherd : Optional[EmailURI]
|
|
@@ -451,7 +450,7 @@ class Document(Resource):
|
|
|
451
450
|
assert self.std_level is None or self.std_level.startswith("/api/v1/name/stdlevelname/")
|
|
452
451
|
|
|
453
452
|
def url(self) -> str:
|
|
454
|
-
# See https://
|
|
453
|
+
# See https://github.com/ietf-tools/datatracker/blob/main/ietf/settings.py and search for DOC_HREFS
|
|
455
454
|
if self.type == DocumentTypeURI(uri="/api/v1/name/doctypename/agenda/"):
|
|
456
455
|
# FIXME: should be "/meeting/{meeting.number}/materials/{doc.name}-{doc.rev}" ???
|
|
457
456
|
# FIXME: This doesn't work for interim meetings
|
|
@@ -493,7 +492,12 @@ class Document(Resource):
|
|
|
493
492
|
mtg = self.name.split("-")[1]
|
|
494
493
|
url = "https://www.ietf.org/proceedings/" + mtg + "/slides/" + self.uploaded_filename
|
|
495
494
|
elif self.type == DocumentTypeURI(uri="/api/v1/name/doctypename/statchg/"):
|
|
496
|
-
url = "https://www.ietf.org/
|
|
495
|
+
url = "https://www.ietf.org/ietf-ftp/status-changes/" + self.name + "-" + self.rev + ".txt"
|
|
496
|
+
elif self.type == DocumentTypeURI(uri="/api/v1/name/doctypename/chatlog/"):
|
|
497
|
+
mtg = self.name.split("-")[1]
|
|
498
|
+
if mtg == "interim":
|
|
499
|
+
mtg = "-".join(self.name.split("-")[1:-1])
|
|
500
|
+
url = "https://datatracker.ietf.org/meeting/" + mtg + "/materials/" + self.uploaded_filename
|
|
497
501
|
else:
|
|
498
502
|
raise NotImplementedError
|
|
499
503
|
return url
|
|
@@ -876,7 +880,6 @@ class Schedule(Resource):
|
|
|
876
880
|
visible : bool
|
|
877
881
|
public : bool
|
|
878
882
|
badness : Optional[str]
|
|
879
|
-
notes : str
|
|
880
883
|
|
|
881
884
|
|
|
882
885
|
class Meeting(Resource):
|
|
@@ -960,7 +963,6 @@ class SessionAssignment(Resource):
|
|
|
960
963
|
schedule : ScheduleURI
|
|
961
964
|
timeslot : TimeslotURI
|
|
962
965
|
modified : datetime
|
|
963
|
-
notes : str
|
|
964
966
|
pinned : bool
|
|
965
967
|
extendedfrom : Optional[str]
|
|
966
968
|
badness : int
|
|
@@ -1622,7 +1624,7 @@ class DataTracker:
|
|
|
1622
1624
|
logging.basicConfig(level=os.environ.get("IETFDATA_LOGLEVEL", "INFO"))
|
|
1623
1625
|
self.log = logging.getLogger("ietfdata")
|
|
1624
1626
|
|
|
1625
|
-
self.ua = "glasgow-ietfdata/0.7.
|
|
1627
|
+
self.ua = "glasgow-ietfdata/0.7.2" # Update when making a new relaase
|
|
1626
1628
|
self.base_url = os.environ.get("IETFDATA_DT_URL", "https://datatracker.ietf.org")
|
|
1627
1629
|
self.get_count = 0
|
|
1628
1630
|
|
|
@@ -590,49 +590,50 @@ class MailingList:
|
|
|
590
590
|
threads = {}
|
|
591
591
|
seen = {} # type: Dict[str,Envelope]
|
|
592
592
|
for msg in self.messages():
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
593
|
+
if len(msg.header("message-id")) > 0:
|
|
594
|
+
msg_id = msg.header("message-id")[0]
|
|
595
|
+
seen[msg_id] = msg
|
|
596
|
+
|
|
597
|
+
self._log.debug(f"{msg.uid():5} {msg.header('message-id')} {msg.header('subject')}")
|
|
598
|
+
|
|
599
|
+
parents = msg.in_reply_to()
|
|
600
|
+
if len(parents) == 0:
|
|
601
|
+
# This is the first message in the thread
|
|
602
|
+
if msg.header("message-id")[0] not in threads:
|
|
603
|
+
threads[msg.header("message-id")[0]] = [msg]
|
|
604
|
+
self._log.debug(" First in thread")
|
|
605
|
+
elif parents[0].header("message-id")[0] in seen:
|
|
606
|
+
# This is part of a thread we've already seen
|
|
607
|
+
self._log.debug(f" {parents[0].header('message-id')} {parents[0].header('subject')}")
|
|
608
|
+
self._log.debug(f" Continues known thread")
|
|
609
|
+
else:
|
|
610
|
+
# This is either a new thread that has been copied to this list
|
|
611
|
+
# where the earlier messages in the thread are on another list,
|
|
612
|
+
# or this message is part of an existing thread but has arrived
|
|
613
|
+
# before its parent.
|
|
614
|
+
curr = []
|
|
615
|
+
curr.append(msg)
|
|
616
|
+
while True:
|
|
617
|
+
parents = curr[0].in_reply_to()
|
|
618
|
+
|
|
619
|
+
parent_in_this_list = False
|
|
620
|
+
for p in parents:
|
|
621
|
+
if p.mailing_list() == self.name():
|
|
622
|
+
parent_in_this_list = True
|
|
623
|
+
if not parent_in_this_list and this_list_only:
|
|
624
|
+
self._log.debug(f" {parents[0].header('message-id')} {parents[0].header('subject')}")
|
|
625
|
+
self._log.debug(f" Not in this list")
|
|
626
|
+
if curr[0].header("message-id")[0] not in threads:
|
|
627
|
+
threads[curr[0].header("message-id")[0]] = curr
|
|
628
|
+
break
|
|
629
|
+
|
|
630
|
+
if len(parents) == 0:
|
|
631
|
+
self._log.debug(" First in thread")
|
|
632
|
+
if curr[0].header("message-id")[0] not in threads:
|
|
633
|
+
threads[curr[0].header("message-id")[0]] = curr
|
|
634
|
+
break
|
|
635
|
+
curr = parents
|
|
636
|
+
self._log.debug(f" {curr[0].header('message-id')} {curr[0].header('subject')}")
|
|
636
637
|
return threads
|
|
637
638
|
|
|
638
639
|
|
|
@@ -759,6 +760,8 @@ class MailArchive:
|
|
|
759
760
|
], unique=False)
|
|
760
761
|
self._db.metadata.create_index([('message_id', ASCENDING)
|
|
761
762
|
], unique=False)
|
|
763
|
+
self._db.metadata.create_index([('in_reply_to', ASCENDING)
|
|
764
|
+
], unique=False)
|
|
762
765
|
self._fs = GridFS(self._db)
|
|
763
766
|
# Create other state:
|
|
764
767
|
self._mailing_lists = {}
|
|
@@ -84,59 +84,59 @@ class RfcEntry:
|
|
|
84
84
|
self.formats = []
|
|
85
85
|
|
|
86
86
|
for elem in rfc_element:
|
|
87
|
-
if elem.tag == "{
|
|
87
|
+
if elem.tag == "{https://www.rfc-editor.org/rfc-index}doc-id":
|
|
88
88
|
assert elem.text is not None
|
|
89
89
|
self.doc_id = DocID(elem.text)
|
|
90
|
-
elif elem.tag == "{
|
|
90
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}title":
|
|
91
91
|
assert elem.text is not None
|
|
92
92
|
self.title = elem.text
|
|
93
|
-
elif elem.tag == "{
|
|
93
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}doi":
|
|
94
94
|
assert elem.text is not None
|
|
95
95
|
self.doi = elem.text
|
|
96
|
-
elif elem.tag == "{
|
|
96
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}stream":
|
|
97
97
|
assert elem.text is not None
|
|
98
98
|
self.stream = elem.text
|
|
99
|
-
elif elem.tag == "{
|
|
99
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}wg_acronym":
|
|
100
100
|
self.wg = elem.text
|
|
101
|
-
elif elem.tag == "{
|
|
101
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}area":
|
|
102
102
|
self.area = elem.text
|
|
103
|
-
elif elem.tag == "{
|
|
103
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}current-status":
|
|
104
104
|
assert elem.text is not None
|
|
105
105
|
self.curr_status = elem.text
|
|
106
|
-
elif elem.tag == "{
|
|
106
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}publication-status":
|
|
107
107
|
assert elem.text is not None
|
|
108
108
|
self.publ_status = elem.text
|
|
109
|
-
elif elem.tag == "{
|
|
109
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}author":
|
|
110
110
|
for inner in elem:
|
|
111
|
-
if inner.tag == "{
|
|
111
|
+
if inner.tag == "{https://www.rfc-editor.org/rfc-index}name":
|
|
112
112
|
assert inner.text is not None
|
|
113
113
|
self.authors.append(inner.text)
|
|
114
|
-
elif inner.tag == "{
|
|
114
|
+
elif inner.tag == "{https://www.rfc-editor.org/rfc-index}title":
|
|
115
115
|
# Ignore <title>...</title> within <author>...</author> tags
|
|
116
116
|
# (this is normally just "Editor", which isn't useful)
|
|
117
117
|
pass
|
|
118
118
|
else:
|
|
119
119
|
raise NotImplementedError
|
|
120
|
-
elif elem.tag == "{
|
|
120
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}date":
|
|
121
121
|
for inner in elem:
|
|
122
122
|
assert inner.text is not None
|
|
123
|
-
if inner.tag == "{
|
|
123
|
+
if inner.tag == "{https://www.rfc-editor.org/rfc-index}day":
|
|
124
124
|
# <day>...</day> is only included for 1 April RFCs
|
|
125
125
|
self.day = int(inner.text)
|
|
126
|
-
elif inner.tag == "{
|
|
126
|
+
elif inner.tag == "{https://www.rfc-editor.org/rfc-index}month":
|
|
127
127
|
self.month = inner.text
|
|
128
|
-
elif inner.tag == "{
|
|
128
|
+
elif inner.tag == "{https://www.rfc-editor.org/rfc-index}year":
|
|
129
129
|
self.year = int(inner.text)
|
|
130
130
|
else:
|
|
131
131
|
raise NotImplementedError
|
|
132
|
-
elif elem.tag == "{
|
|
132
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}format":
|
|
133
133
|
for inner in elem:
|
|
134
134
|
assert inner.text is not None
|
|
135
|
-
if inner.tag == "{
|
|
135
|
+
if inner.tag == "{https://www.rfc-editor.org/rfc-index}file-format":
|
|
136
136
|
self.formats.append(inner.text)
|
|
137
137
|
else:
|
|
138
138
|
raise NotImplementedError
|
|
139
|
-
elif elem.tag == "{
|
|
139
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}draft":
|
|
140
140
|
if elem.text == "rfc4049bis":
|
|
141
141
|
# RFC 6019 is RFC 4049 republished as a Proposed Standard RF.
|
|
142
142
|
# with virtually no change. It was never published as a draft,
|
|
@@ -147,63 +147,63 @@ class RfcEntry:
|
|
|
147
147
|
self.draft = None
|
|
148
148
|
else:
|
|
149
149
|
self.draft = elem.text
|
|
150
|
-
elif elem.tag == "{
|
|
150
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}keywords":
|
|
151
151
|
for inner in elem:
|
|
152
|
-
if inner.tag == "{
|
|
152
|
+
if inner.tag == "{https://www.rfc-editor.org/rfc-index}kw":
|
|
153
153
|
# Omit empty <kw></kw>
|
|
154
154
|
if inner.text is not None:
|
|
155
155
|
self.keywords.append(inner.text)
|
|
156
156
|
else:
|
|
157
157
|
raise NotImplementedError
|
|
158
|
-
elif elem.tag == "{
|
|
158
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}updates":
|
|
159
159
|
for inner in elem:
|
|
160
160
|
assert inner.text is not None
|
|
161
|
-
if inner.tag == "{
|
|
161
|
+
if inner.tag == "{https://www.rfc-editor.org/rfc-index}doc-id":
|
|
162
162
|
self.updates.append(DocID(inner.text))
|
|
163
163
|
else:
|
|
164
164
|
raise NotImplementedError
|
|
165
|
-
elif elem.tag == "{
|
|
165
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}updated-by":
|
|
166
166
|
for inner in elem:
|
|
167
167
|
assert inner.text is not None
|
|
168
|
-
if inner.tag == "{
|
|
168
|
+
if inner.tag == "{https://www.rfc-editor.org/rfc-index}doc-id":
|
|
169
169
|
self.updated_by.append(DocID(inner.text))
|
|
170
170
|
else:
|
|
171
171
|
raise NotImplementedError
|
|
172
|
-
elif elem.tag == "{
|
|
172
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}obsoletes":
|
|
173
173
|
for inner in elem:
|
|
174
174
|
assert inner.text is not None
|
|
175
|
-
if inner.tag == "{
|
|
175
|
+
if inner.tag == "{https://www.rfc-editor.org/rfc-index}doc-id":
|
|
176
176
|
self.obsoletes.append(DocID(inner.text))
|
|
177
177
|
else:
|
|
178
178
|
raise NotImplementedError
|
|
179
|
-
elif elem.tag == "{
|
|
179
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}obsoleted-by":
|
|
180
180
|
for inner in elem:
|
|
181
181
|
assert inner.text is not None
|
|
182
|
-
if inner.tag == "{
|
|
182
|
+
if inner.tag == "{https://www.rfc-editor.org/rfc-index}doc-id":
|
|
183
183
|
self.obsoleted_by.append(DocID(inner.text))
|
|
184
184
|
else:
|
|
185
185
|
raise NotImplementedError
|
|
186
|
-
elif elem.tag == "{
|
|
186
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}is-also":
|
|
187
187
|
for inner in elem:
|
|
188
188
|
assert inner.text is not None
|
|
189
|
-
if inner.tag == "{
|
|
189
|
+
if inner.tag == "{https://www.rfc-editor.org/rfc-index}doc-id":
|
|
190
190
|
self.is_also.append(DocID(inner.text))
|
|
191
191
|
else:
|
|
192
192
|
raise NotImplementedError
|
|
193
|
-
elif elem.tag == "{
|
|
193
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}see-also":
|
|
194
194
|
for inner in elem:
|
|
195
195
|
assert inner.text is not None
|
|
196
|
-
if inner.tag == "{
|
|
196
|
+
if inner.tag == "{https://www.rfc-editor.org/rfc-index}doc-id":
|
|
197
197
|
self.see_also.append(DocID(inner.text))
|
|
198
198
|
else:
|
|
199
199
|
raise NotImplementedError
|
|
200
|
-
elif elem.tag == "{
|
|
200
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}errata-url":
|
|
201
201
|
self.errata_url = elem.text
|
|
202
|
-
elif elem.tag == "{
|
|
202
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}abstract":
|
|
203
203
|
# The <abstract>...</abstract> contains formatted XML, most
|
|
204
204
|
# typically a sequence of <p>...</p> tags.
|
|
205
205
|
self.abstract = elem
|
|
206
|
-
elif elem.tag == "{
|
|
206
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}page-count":
|
|
207
207
|
assert elem.text is not None
|
|
208
208
|
self.page_count = int(elem.text)
|
|
209
209
|
else:
|
|
@@ -327,7 +327,7 @@ class RfcNotIssuedEntry:
|
|
|
327
327
|
|
|
328
328
|
def __init__(self, rfc_not_issued_element: ET.Element) -> None:
|
|
329
329
|
for elem in rfc_not_issued_element:
|
|
330
|
-
if elem.tag == "{
|
|
330
|
+
if elem.tag == "{https://www.rfc-editor.org/rfc-index}doc-id":
|
|
331
331
|
assert elem.text is not None
|
|
332
332
|
self.doc_id = DocID(elem.text)
|
|
333
333
|
else:
|
|
@@ -354,13 +354,13 @@ class BcpEntry:
|
|
|
354
354
|
self.is_also = []
|
|
355
355
|
|
|
356
356
|
for elem in bcp_element:
|
|
357
|
-
if elem.tag == "{
|
|
357
|
+
if elem.tag == "{https://www.rfc-editor.org/rfc-index}doc-id":
|
|
358
358
|
assert elem.text is not None
|
|
359
359
|
self.doc_id = DocID(elem.text)
|
|
360
|
-
elif elem.tag == "{
|
|
360
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}is-also":
|
|
361
361
|
for inner in elem:
|
|
362
362
|
assert inner.text is not None
|
|
363
|
-
if inner.tag == "{
|
|
363
|
+
if inner.tag == "{https://www.rfc-editor.org/rfc-index}doc-id":
|
|
364
364
|
self.is_also.append(DocID(inner.text))
|
|
365
365
|
else:
|
|
366
366
|
raise NotImplementedError
|
|
@@ -391,14 +391,14 @@ class StdEntry:
|
|
|
391
391
|
|
|
392
392
|
for elem in std_element:
|
|
393
393
|
assert elem.text is not None
|
|
394
|
-
if elem.tag == "{
|
|
394
|
+
if elem.tag == "{https://www.rfc-editor.org/rfc-index}doc-id":
|
|
395
395
|
self.doc_id = DocID(elem.text)
|
|
396
|
-
elif elem.tag == "{
|
|
396
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}title":
|
|
397
397
|
self.title = elem.text
|
|
398
|
-
elif elem.tag == "{
|
|
398
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}is-also":
|
|
399
399
|
for inner in elem:
|
|
400
400
|
assert inner.text is not None
|
|
401
|
-
if inner.tag == "{
|
|
401
|
+
if inner.tag == "{https://www.rfc-editor.org/rfc-index}doc-id":
|
|
402
402
|
self.is_also.append(DocID(inner.text))
|
|
403
403
|
else:
|
|
404
404
|
raise NotImplementedError
|
|
@@ -428,12 +428,12 @@ class FyiEntry:
|
|
|
428
428
|
|
|
429
429
|
for elem in fyi_element:
|
|
430
430
|
assert elem.text is not None
|
|
431
|
-
if elem.tag == "{
|
|
431
|
+
if elem.tag == "{https://www.rfc-editor.org/rfc-index}doc-id":
|
|
432
432
|
self.doc_id = DocID(elem.text)
|
|
433
|
-
elif elem.tag == "{
|
|
433
|
+
elif elem.tag == "{https://www.rfc-editor.org/rfc-index}is-also":
|
|
434
434
|
for inner in elem:
|
|
435
435
|
assert inner.text is not None
|
|
436
|
-
if inner.tag == "{
|
|
436
|
+
if inner.tag == "{https://www.rfc-editor.org/rfc-index}doc-id":
|
|
437
437
|
self.is_also.append(DocID(inner.text))
|
|
438
438
|
else:
|
|
439
439
|
raise NotImplementedError
|
|
@@ -515,22 +515,23 @@ class RFCIndex:
|
|
|
515
515
|
raise RuntimeError
|
|
516
516
|
|
|
517
517
|
for doc in ET.fromstring(xml):
|
|
518
|
-
if doc.tag == "{
|
|
518
|
+
if doc.tag == "{https://www.rfc-editor.org/rfc-index}rfc-entry":
|
|
519
519
|
rfc = RfcEntry(doc)
|
|
520
520
|
self._rfc[rfc.doc_id] = rfc
|
|
521
|
-
elif doc.tag == "{
|
|
521
|
+
elif doc.tag == "{https://www.rfc-editor.org/rfc-index}rfc-not-issued-entry":
|
|
522
522
|
rne = RfcNotIssuedEntry(doc)
|
|
523
523
|
self._rfc_not_issued[rne.doc_id] = rne
|
|
524
|
-
elif doc.tag == "{
|
|
524
|
+
elif doc.tag == "{https://www.rfc-editor.org/rfc-index}bcp-entry":
|
|
525
525
|
bcp = BcpEntry(doc)
|
|
526
526
|
self._bcp[bcp.doc_id] = bcp
|
|
527
|
-
elif doc.tag == "{
|
|
527
|
+
elif doc.tag == "{https://www.rfc-editor.org/rfc-index}std-entry":
|
|
528
528
|
std = StdEntry(doc)
|
|
529
529
|
self._std[std.doc_id] = std
|
|
530
|
-
elif doc.tag == "{
|
|
530
|
+
elif doc.tag == "{https://www.rfc-editor.org/rfc-index}fyi-entry":
|
|
531
531
|
fyi = FyiEntry(doc)
|
|
532
532
|
self._fyi[fyi.doc_id] = fyi
|
|
533
533
|
else:
|
|
534
|
+
print(f"Unexpected doc.tag: {doc.tag}")
|
|
534
535
|
raise NotImplementedError
|
|
535
536
|
|
|
536
537
|
|
|
@@ -4,6 +4,7 @@ pyproject.toml
|
|
|
4
4
|
setup.py
|
|
5
5
|
examples/__init__.py
|
|
6
6
|
examples/bluesheets.py
|
|
7
|
+
examples/chatlogs.py
|
|
7
8
|
examples/draft-authors.py
|
|
8
9
|
examples/draft-references.py
|
|
9
10
|
examples/draft-submissions-by-date.py
|
|
@@ -11,6 +12,7 @@ examples/draft-submissions.py
|
|
|
11
12
|
examples/drafts-for-person.py
|
|
12
13
|
examples/drafts-for-rfc.py
|
|
13
14
|
examples/drafts-for-wg.py
|
|
15
|
+
examples/emails-ietf-community.py
|
|
14
16
|
examples/emails-per-person.py
|
|
15
17
|
examples/iesg-processing-time.py
|
|
16
18
|
examples/ietf-leadership.py
|
|
@@ -21,6 +23,7 @@ examples/person-emails.py
|
|
|
21
23
|
examples/person.py
|
|
22
24
|
examples/recent-pubreq.py
|
|
23
25
|
examples/rfc-data.py
|
|
26
|
+
examples/rfc-per-year-ietf.py
|
|
24
27
|
examples/rfc-per-year.py
|
|
25
28
|
examples/rfc-streams.py
|
|
26
29
|
examples/role-names.py
|
|
@@ -511,7 +511,6 @@ class TestDatatracker(unittest.TestCase):
|
|
|
511
511
|
self.assertEqual(d.notify, "")
|
|
512
512
|
self.assertEqual(d.type, DocumentTypeURI(uri="/api/v1/name/doctypename/agenda/"))
|
|
513
513
|
self.assertEqual(d.rev, "2")
|
|
514
|
-
self.assertEqual(d.internal_comments, "")
|
|
515
514
|
self.assertEqual(d.id, 218)
|
|
516
515
|
self.assertEqual(d.std_level, None)
|
|
517
516
|
self.assertEqual(d.ad, None)
|
|
@@ -540,7 +539,6 @@ class TestDatatracker(unittest.TestCase):
|
|
|
540
539
|
def test_document_bluesheets(self) -> None:
|
|
541
540
|
d = self.dt.document(DocumentURI(uri="/api/v1/doc/document/bluesheets-95-xrblock-01/"))
|
|
542
541
|
if d is not None:
|
|
543
|
-
self.assertEqual(d.internal_comments, "")
|
|
544
542
|
self.assertEqual(d.id, 68163)
|
|
545
543
|
self.assertEqual(d.name, "bluesheets-95-xrblock-01")
|
|
546
544
|
self.assertEqual(d.notify, "")
|
|
@@ -577,7 +575,6 @@ class TestDatatracker(unittest.TestCase):
|
|
|
577
575
|
def test_document_charter(self) -> None:
|
|
578
576
|
d = self.dt.document(DocumentURI(uri="/api/v1/doc/document/charter-ietf-vgmib/"))
|
|
579
577
|
if d is not None:
|
|
580
|
-
self.assertEqual(d.internal_comments, "")
|
|
581
578
|
self.assertEqual(d.id, 1)
|
|
582
579
|
self.assertEqual(d.name, "charter-ietf-vgmib")
|
|
583
580
|
self.assertEqual(d.notify, "")
|
|
@@ -611,10 +608,45 @@ class TestDatatracker(unittest.TestCase):
|
|
|
611
608
|
self.fail("Cannot find document")
|
|
612
609
|
|
|
613
610
|
|
|
611
|
+
def test_document_chatlog(self) -> None:
|
|
612
|
+
d = self.dt.document(DocumentURI(uri="/api/v1/doc/document/chatlog-114-ohai-202207261330/"))
|
|
613
|
+
if d is not None:
|
|
614
|
+
self.assertEqual(d.id, 110925)
|
|
615
|
+
self.assertEqual(d.name, "chatlog-114-ohai-202207261330")
|
|
616
|
+
self.assertEqual(d.notify, "")
|
|
617
|
+
self.assertEqual(d.rev, "00")
|
|
618
|
+
self.assertEqual(d.external_url, "")
|
|
619
|
+
self.assertEqual(d.expires, None)
|
|
620
|
+
self.assertEqual(d.type, DocumentTypeURI(uri="/api/v1/name/doctypename/chatlog/"))
|
|
621
|
+
self.assertEqual(d.group, GroupURI(uri="/api/v1/group/group/2312/"))
|
|
622
|
+
self.assertEqual(d.resource_uri, DocumentURI(uri="/api/v1/doc/document/chatlog-114-ohai-202207261330/"))
|
|
623
|
+
self.assertEqual(d.title, "Chat Log IETF114: ohai: Tue 13:30")
|
|
624
|
+
self.assertEqual(d.abstract, "")
|
|
625
|
+
self.assertEqual(d.uploaded_filename, "chatlog-114-ohai-202207261330-00.json")
|
|
626
|
+
self.assertEqual(d.rfc, None)
|
|
627
|
+
self.assertEqual(d.shepherd, None)
|
|
628
|
+
self.assertEqual(d.submissions, [])
|
|
629
|
+
self.assertEqual(d.intended_std_level, None)
|
|
630
|
+
self.assertEqual(d.ad, None)
|
|
631
|
+
self.assertEqual(d.note, "")
|
|
632
|
+
self.assertEqual(d.words, None)
|
|
633
|
+
self.assertEqual(d.tags, [])
|
|
634
|
+
self.assertEqual(d.time, datetime.fromisoformat("2022-10-18T15:21:12+0000"))
|
|
635
|
+
self.assertEqual(d.pages, None)
|
|
636
|
+
self.assertEqual(d.stream, None)
|
|
637
|
+
self.assertEqual(d.std_level, None)
|
|
638
|
+
self.assertEqual(d.states, [DocumentStateURI(uri="/api/v1/doc/state/165/")])
|
|
639
|
+
|
|
640
|
+
url = d.url()
|
|
641
|
+
self.assertEqual(url, "https://datatracker.ietf.org/meeting/114/materials/chatlog-114-ohai-202207261330-00.json")
|
|
642
|
+
self.assertEqual(self.dt.session.get(url).status_code, 200)
|
|
643
|
+
else:
|
|
644
|
+
self.fail("Cannot find document")
|
|
645
|
+
|
|
646
|
+
|
|
614
647
|
def test_document_conflrev(self) -> None:
|
|
615
648
|
d = self.dt.document(DocumentURI(uri="/api/v1/doc/document/conflict-review-kiyomoto-kcipher2/"))
|
|
616
649
|
if d is not None:
|
|
617
|
-
self.assertEqual(d.internal_comments, "")
|
|
618
650
|
self.assertEqual(d.id, 17898)
|
|
619
651
|
self.assertEqual(d.name, "conflict-review-kiyomoto-kcipher2")
|
|
620
652
|
self.assertEqual(d.notify, "\"Nevil Brownlee\" <rfc-ise@rfc-editor.org>, draft-kiyomoto-kcipher2@tools.ietf.org")
|
|
@@ -651,7 +683,6 @@ class TestDatatracker(unittest.TestCase):
|
|
|
651
683
|
def test_document_draft(self) -> None:
|
|
652
684
|
d = self.dt.document(DocumentURI(uri="/api/v1/doc/document/draft-ietf-avt-rtp-new/"))
|
|
653
685
|
if d is not None:
|
|
654
|
-
self.assertEqual(d.internal_comments, "")
|
|
655
686
|
self.assertEqual(d.id, 19971)
|
|
656
687
|
self.assertEqual(d.name, "draft-ietf-avt-rtp-new")
|
|
657
688
|
self.assertEqual(d.notify, "magnus.westerlund@ericsson.com, csp@csperkins.org")
|
|
@@ -688,7 +719,6 @@ class TestDatatracker(unittest.TestCase):
|
|
|
688
719
|
def test_document_liaison(self) -> None:
|
|
689
720
|
d = self.dt.document(DocumentURI(uri="/api/v1/doc/document/liaison-2012-05-31-3gpp-mmusic-on-rtcp-bandwidth-negotiation-attachment-1/"))
|
|
690
721
|
if d is not None:
|
|
691
|
-
self.assertEqual(d.internal_comments, "")
|
|
692
722
|
self.assertEqual(d.id, 46457)
|
|
693
723
|
self.assertEqual(d.name, "liaison-2012-05-31-3gpp-mmusic-on-rtcp-bandwidth-negotiation-attachment-1")
|
|
694
724
|
self.assertEqual(d.notify, "")
|
|
@@ -725,7 +755,6 @@ class TestDatatracker(unittest.TestCase):
|
|
|
725
755
|
def test_document_liai_att(self) -> None:
|
|
726
756
|
d = self.dt.document(DocumentURI(uri="/api/v1/doc/document/liaison-2004-08-23-itu-t-ietf-liaison-statement-to-ietf-and-itu-t-study-groups-countering-spam-pdf-version-attachment-1/"))
|
|
727
757
|
if d is not None:
|
|
728
|
-
self.assertEqual(d.internal_comments, "")
|
|
729
758
|
self.assertEqual(d.id, 43519)
|
|
730
759
|
self.assertEqual(d.name, "liaison-2004-08-23-itu-t-ietf-liaison-statement-to-ietf-and-itu-t-study-groups-countering-spam-pdf-version-attachment-1")
|
|
731
760
|
self.assertEqual(d.notify, "")
|
|
@@ -762,7 +791,6 @@ class TestDatatracker(unittest.TestCase):
|
|
|
762
791
|
def test_document_minutes(self) -> None:
|
|
763
792
|
d = self.dt.document(DocumentURI(uri="/api/v1/doc/document/minutes-89-cfrg/"))
|
|
764
793
|
if d is not None:
|
|
765
|
-
self.assertEqual(d.internal_comments, "")
|
|
766
794
|
self.assertEqual(d.id, 272)
|
|
767
795
|
self.assertEqual(d.name, "minutes-89-cfrg")
|
|
768
796
|
self.assertEqual(d.notify, "")
|
|
@@ -799,7 +827,6 @@ class TestDatatracker(unittest.TestCase):
|
|
|
799
827
|
def test_document_recording(self) -> None:
|
|
800
828
|
d = self.dt.document(DocumentURI(uri="/api/v1/doc/document/recording-94-taps-1/"))
|
|
801
829
|
if d is not None:
|
|
802
|
-
self.assertEqual(d.internal_comments, "")
|
|
803
830
|
self.assertEqual(d.id, 49624)
|
|
804
831
|
self.assertEqual(d.name, "recording-94-taps-1")
|
|
805
832
|
self.assertEqual(d.notify, "")
|
|
@@ -837,7 +864,6 @@ class TestDatatracker(unittest.TestCase):
|
|
|
837
864
|
def test_document_review(self) -> None:
|
|
838
865
|
d = self.dt.document(DocumentURI(uri="/api/v1/doc/document/review-bchv-rfc6890bis-04-genart-lc-kyzivat-2017-02-28/"))
|
|
839
866
|
if d is not None:
|
|
840
|
-
self.assertEqual(d.internal_comments, "")
|
|
841
867
|
self.assertEqual(d.id, 69082)
|
|
842
868
|
self.assertEqual(d.name, "review-bchv-rfc6890bis-04-genart-lc-kyzivat-2017-02-28")
|
|
843
869
|
self.assertEqual(d.notify, "")
|
|
@@ -879,7 +905,6 @@ class TestDatatracker(unittest.TestCase):
|
|
|
879
905
|
def test_document_slides(self) -> None:
|
|
880
906
|
d = self.dt.document(DocumentURI(uri="/api/v1/doc/document/slides-65-l2vpn-4/"))
|
|
881
907
|
if d is not None:
|
|
882
|
-
self.assertEqual(d.internal_comments, "")
|
|
883
908
|
self.assertEqual(d.id, 736)
|
|
884
909
|
self.assertEqual(d.name, "slides-65-l2vpn-4")
|
|
885
910
|
self.assertEqual(d.notify, "")
|
|
@@ -916,7 +941,6 @@ class TestDatatracker(unittest.TestCase):
|
|
|
916
941
|
def test_document_statchg(self) -> None:
|
|
917
942
|
d = self.dt.document(DocumentURI(uri="/api/v1/doc/document/status-change-rfc3044-rfc3187-orig-urn-regs-to-historic/"))
|
|
918
943
|
if d is not None:
|
|
919
|
-
self.assertEqual(d.internal_comments, "")
|
|
920
944
|
self.assertEqual(d.id, 78306)
|
|
921
945
|
self.assertEqual(d.name, "status-change-rfc3044-rfc3187-orig-urn-regs-to-historic")
|
|
922
946
|
self.assertEqual(d.notify, "")
|
|
@@ -944,7 +968,7 @@ class TestDatatracker(unittest.TestCase):
|
|
|
944
968
|
self.assertEqual(d.states, [DocumentStateURI(uri="/api/v1/doc/state/127/")])
|
|
945
969
|
|
|
946
970
|
url = d.url()
|
|
947
|
-
self.assertEqual(url, "https://www.ietf.org/
|
|
971
|
+
self.assertEqual(url, "https://www.ietf.org/ietf-ftp/status-changes/status-change-rfc3044-rfc3187-orig-urn-regs-to-historic-00.txt")
|
|
948
972
|
self.assertEqual(self.dt.session.get(url).status_code, 200)
|
|
949
973
|
else:
|
|
950
974
|
self.fail("Cannot find document")
|
|
@@ -1731,19 +1755,20 @@ class TestDatatracker(unittest.TestCase):
|
|
|
1731
1755
|
|
|
1732
1756
|
def test_group_roles_email(self) -> None:
|
|
1733
1757
|
group_roles = list(self.dt.group_roles(email="csp@csperkins.org"))
|
|
1734
|
-
self.assertEqual(len(group_roles),
|
|
1758
|
+
self.assertEqual(len(group_roles), 13)
|
|
1735
1759
|
self.assertEqual(group_roles[0].id, 1076) # SAFE BoF chair
|
|
1736
1760
|
self.assertEqual(group_roles[1].id, 3998) # TSV DIR reviewer
|
|
1737
1761
|
self.assertEqual(group_roles[2].id, 8464) # IRSG chair
|
|
1738
1762
|
self.assertEqual(group_roles[3].id, 8465) # IRTF Open Meeting chair
|
|
1739
1763
|
self.assertEqual(group_roles[4].id, 8466) # IRTF chair
|
|
1740
1764
|
self.assertEqual(group_roles[5].id, 9355) # RMCAT chair
|
|
1741
|
-
self.assertEqual(group_roles[6].id,
|
|
1742
|
-
self.assertEqual(group_roles[7].id,
|
|
1743
|
-
self.assertEqual(group_roles[8].id,
|
|
1744
|
-
self.assertEqual(group_roles[9].id,
|
|
1745
|
-
self.assertEqual(group_roles[10].id,
|
|
1746
|
-
self.assertEqual(group_roles[11].id,
|
|
1765
|
+
self.assertEqual(group_roles[6].id, 11103) # TSV ART reviewer
|
|
1766
|
+
self.assertEqual(group_roles[7].id, 11680) # IRTF ANRW chair
|
|
1767
|
+
self.assertEqual(group_roles[8].id, 12875) # RSAB member
|
|
1768
|
+
self.assertEqual(group_roles[9].id, 12915) # IAB-ISOC Policy Coordination
|
|
1769
|
+
self.assertEqual(group_roles[10].id, 13098) # IAB E-Impact workshop
|
|
1770
|
+
self.assertEqual(group_roles[11].id, 13698) # IAB
|
|
1771
|
+
self.assertEqual(group_roles[12].id, 13726) #
|
|
1747
1772
|
|
|
1748
1773
|
|
|
1749
1774
|
def test_group_roles_group(self) -> None:
|
|
@@ -1770,19 +1795,20 @@ class TestDatatracker(unittest.TestCase):
|
|
|
1770
1795
|
|
|
1771
1796
|
def test_group_roles_person(self) -> None:
|
|
1772
1797
|
group_roles = list(self.dt.group_roles(person=self.dt.person(PersonURI(uri="/api/v1/person/person/20209/"))))
|
|
1773
|
-
self.assertEqual(len(group_roles),
|
|
1798
|
+
self.assertEqual(len(group_roles), 13)
|
|
1774
1799
|
self.assertEqual(group_roles[0].id, 1076) # SAFE BoF chair
|
|
1775
1800
|
self.assertEqual(group_roles[1].id, 3998) # TSV DIR reviewer
|
|
1776
1801
|
self.assertEqual(group_roles[2].id, 8464) # IRSG chair
|
|
1777
1802
|
self.assertEqual(group_roles[3].id, 8465) # IRTF Open Meeting chair
|
|
1778
1803
|
self.assertEqual(group_roles[4].id, 8466) # IRTF chair
|
|
1779
1804
|
self.assertEqual(group_roles[5].id, 9355) # RMCAT chair
|
|
1780
|
-
self.assertEqual(group_roles[6].id,
|
|
1781
|
-
self.assertEqual(group_roles[7].id,
|
|
1782
|
-
self.assertEqual(group_roles[8].id,
|
|
1783
|
-
self.assertEqual(group_roles[9].id,
|
|
1784
|
-
self.assertEqual(group_roles[10].id,
|
|
1785
|
-
self.assertEqual(group_roles[11].id,
|
|
1805
|
+
self.assertEqual(group_roles[6].id, 11103) # TSV ART reviewer
|
|
1806
|
+
self.assertEqual(group_roles[7].id, 11680) # IRTF ANRW chair
|
|
1807
|
+
self.assertEqual(group_roles[8].id, 12875) # RSAB member
|
|
1808
|
+
self.assertEqual(group_roles[9].id, 12915) # IAB-ISOC Policy Coordination
|
|
1809
|
+
self.assertEqual(group_roles[10].id, 13098) # IAB E-Impact workshop
|
|
1810
|
+
self.assertEqual(group_roles[11].id, 13698) # IAB
|
|
1811
|
+
self.assertEqual(group_roles[12].id, 13726) #
|
|
1786
1812
|
|
|
1787
1813
|
|
|
1788
1814
|
def test_group_milestone_history(self) -> None:
|
|
@@ -1888,7 +1914,7 @@ class TestDatatracker(unittest.TestCase):
|
|
|
1888
1914
|
|
|
1889
1915
|
def test_group_role_histories_email(self) -> None:
|
|
1890
1916
|
group_role_histories = list(self.dt.group_role_histories(email="csp@csperkins.org"))
|
|
1891
|
-
self.assertEqual(len(group_role_histories),
|
|
1917
|
+
self.assertEqual(len(group_role_histories), 91)
|
|
1892
1918
|
|
|
1893
1919
|
|
|
1894
1920
|
def test_group_role_histories_group(self) -> None:
|
|
@@ -1904,7 +1930,7 @@ class TestDatatracker(unittest.TestCase):
|
|
|
1904
1930
|
|
|
1905
1931
|
def test_group_role_histories_person(self) -> None:
|
|
1906
1932
|
group_role_histories = list(self.dt.group_role_histories(person=self.dt.person(PersonURI(uri="/api/v1/person/person/20209/"))))
|
|
1907
|
-
self.assertEqual(len(group_role_histories),
|
|
1933
|
+
self.assertEqual(len(group_role_histories), 91)
|
|
1908
1934
|
|
|
1909
1935
|
|
|
1910
1936
|
def test_group_state_change_event(self) -> None:
|
|
@@ -2080,7 +2106,6 @@ class TestDatatracker(unittest.TestCase):
|
|
|
2080
2106
|
self.assertEqual(assignment.pinned, False)
|
|
2081
2107
|
self.assertEqual(assignment.resource_uri, SessionAssignmentURI(uri="/api/v1/meeting/schedtimesessassignment/61212/"))
|
|
2082
2108
|
self.assertEqual(assignment.badness, 0)
|
|
2083
|
-
self.assertEqual(assignment.notes, "")
|
|
2084
2109
|
else:
|
|
2085
2110
|
self.fail("cannot find meeting session assignment")
|
|
2086
2111
|
|
|
@@ -2283,7 +2308,6 @@ class TestDatatracker(unittest.TestCase):
|
|
|
2283
2308
|
self.assertEqual(schedule.visible, True)
|
|
2284
2309
|
self.assertEqual(schedule.public, True)
|
|
2285
2310
|
self.assertEqual(schedule.badness, None)
|
|
2286
|
-
self.assertEqual(schedule.notes, "")
|
|
2287
2311
|
else:
|
|
2288
2312
|
self.fail("cannot find meeting schedule")
|
|
2289
2313
|
|
|
@@ -3746,7 +3770,7 @@ class TestDatatracker(unittest.TestCase):
|
|
|
3746
3770
|
|
|
3747
3771
|
def test_announcements_from_name(self) -> None:
|
|
3748
3772
|
announcements_from = list(self.dt.announcements_from(name=self.dt.role_name(RoleNameURI(uri="/api/v1/name/rolename/chair/"))))
|
|
3749
|
-
self.assertEqual(len(announcements_from),
|
|
3773
|
+
self.assertEqual(len(announcements_from), 11)
|
|
3750
3774
|
self.assertEqual(announcements_from[0].id, 1)
|
|
3751
3775
|
self.assertEqual(announcements_from[1].id, 2)
|
|
3752
3776
|
self.assertEqual(announcements_from[2].id, 3)
|
|
@@ -3757,6 +3781,7 @@ class TestDatatracker(unittest.TestCase):
|
|
|
3757
3781
|
self.assertEqual(announcements_from[7].id, 16)
|
|
3758
3782
|
self.assertEqual(announcements_from[8].id, 24)
|
|
3759
3783
|
self.assertEqual(announcements_from[9].id, 26)
|
|
3784
|
+
self.assertEqual(announcements_from[10].id, 30)
|
|
3760
3785
|
|
|
3761
3786
|
|
|
3762
3787
|
#def test_message(self) -> None:
|
|
@@ -65,7 +65,7 @@ class TestRFCIndex(unittest.TestCase):
|
|
|
65
65
|
self.assertEqual(rfc.obsoleted_by, [])
|
|
66
66
|
self.assertEqual(rfc.is_also, ["STD0064"])
|
|
67
67
|
self.assertEqual(rfc.see_also, [])
|
|
68
|
-
self.assertEqual(rfc.errata_url, "
|
|
68
|
+
self.assertEqual(rfc.errata_url, "https://www.rfc-editor.org/errata/rfc3550")
|
|
69
69
|
self.assertEqual(rfc.charset(), "utf-8")
|
|
70
70
|
self.assertEqual(rfc.content_url("ASCII"), "https://www.rfc-editor.org/rfc/rfc3550.txt")
|
|
71
71
|
self.assertEqual(rfc.content_url("PS"), "https://www.rfc-editor.org/rfc/rfc3550.ps")
|
|
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
|
|
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
|