gismap 0.2.2__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,119 @@
1
+ from string import Template
2
+
3
+
4
+ # language=css
5
+ default_style = Template("""
6
+
7
+ #box-$uid {
8
+ position: relative;
9
+ width: 100% !important;
10
+ height: 80vh !important;
11
+ max-width: 100vw !important;
12
+ max-height: 80vh !important;
13
+ min-height: 80vh;
14
+ color: #111;
15
+ }
16
+
17
+ #vis-$uid {
18
+ height: 100%; /* Make the inner div fill the parent */
19
+ width: 100%; /* Make the inner div fill the parent */
20
+ box-sizing: border-box;
21
+ border: 1px solid #444;
22
+ }
23
+
24
+ html[data-theme="dark"] #vis-$uid {
25
+ background-color: var(--pst-color-background, #14181e);
26
+ }
27
+
28
+ .modal {
29
+ display: none;
30
+ position: fixed;
31
+ z-index: 1000;
32
+ left: 0;
33
+ top: 0;
34
+ width: 100%;
35
+ height: 100%;
36
+ overflow: auto;
37
+ background-color: rgba(10, 10, 10, 0.85);
38
+ }
39
+
40
+ .modal-content {
41
+ background-color: #f4f4f7;
42
+ color: #222235;
43
+ margin: 10% auto;
44
+ padding: 24px;
45
+ border: 1px solid #888;
46
+ width: 50%;
47
+ border-radius: 8px;
48
+ box-shadow: 0 5px 15px rgba(0, 0, 0, .6);
49
+ }
50
+ .modal a {color: #2958d7;}
51
+ .modal a:visited {color: #8435a8;}
52
+
53
+ .close {
54
+ color: #777;
55
+ float: right;
56
+ font-size: 28px;
57
+ font-weight: bold;
58
+ cursor: pointer;
59
+ }
60
+
61
+ .close:hover, .close:focus {
62
+ color: #aaa;
63
+ text-decoration: none;
64
+ cursor: pointer;
65
+ }
66
+
67
+ .watermark {
68
+ position: absolute;
69
+ text-decoration: none;
70
+ color: #888;
71
+ font-size: min(2vw, 10px);
72
+ z-index: 10;
73
+ }
74
+
75
+ .gislink {
76
+ left: 10px;
77
+ bottom: 10px;
78
+ pointer-events: auto;
79
+ }
80
+
81
+ .button {
82
+ background: none;
83
+ border: none;
84
+ padding: 0;
85
+ margin: 0;
86
+ cursor: pointer;
87
+ }
88
+
89
+ .redraw {
90
+ left: 10px;
91
+ top: 10px;
92
+ }
93
+
94
+ .fullscreen {
95
+ bottom: 10px;
96
+ right: 10px;
97
+ }
98
+
99
+ .legend {
100
+ display: inline-block;
101
+ padding: 10px 16px;
102
+ border-radius: 8px;
103
+ box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.10);
104
+ border: 1px solid var(--legend-border, #bbb);
105
+ background: var(--jp-layout-color1, #f5f5fa);
106
+ background-color: var(--legend-bg, rgba(240, 240, 245, 0.95));
107
+ position: absolute;
108
+ top: 12px;
109
+ right: 12px;
110
+ z-index: 20;
111
+ }
112
+
113
+ .legend-entry {
114
+ display: flex;
115
+ margin-right: 10px;
116
+ align-items: center;
117
+ cursor: pointer;
118
+ }
119
+ """)
@@ -0,0 +1,145 @@
1
+ import re
2
+ import unicodedata
3
+ import base64
4
+ from IPython.display import display, HTML
5
+ import ipywidgets as widgets
6
+ from contextlib import contextmanager
7
+
8
+ from gismap.lab.egomap import EgoMap
9
+ from gismap.lab.labmap import ListMap
10
+
11
+
12
+ @contextmanager
13
+ def dummy_context():
14
+ yield
15
+
16
+
17
+ def safe_filename(name):
18
+ """
19
+ Parameters
20
+ ----------
21
+ name: :class:`str`
22
+ Pretty much anything.
23
+
24
+ Returns
25
+ -------
26
+ :class:`str`
27
+ GisMap filename.
28
+ """
29
+ normalized = unicodedata.normalize("NFKD", name)
30
+ ascii_only = normalized.encode("ascii", "ignore").decode("ascii")
31
+ ascii_only = ascii_only.replace(" ", "_")
32
+ safe_str = re.sub(r"[^a-zA-Z0-9_]", "", ascii_only)
33
+ return f"gismap-{safe_str[:60]}.html"
34
+
35
+
36
+ place_holder = "Diego Perino, The-Dang Huynh, François Durand (hal: fradurand, dblp: 38/11269), Rim Kaddah, Leonardo Linguaglossa, Céline Comte"
37
+
38
+
39
+ class GismapWidget:
40
+ """
41
+ A simple widget to test the production of LabMaps and EgoMaps.
42
+
43
+ Examples
44
+ --------
45
+
46
+ This is a doctest example. Use a notebook to play with the widget.
47
+
48
+ >>> gw = GismapWidget() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
49
+ VBox(children=(HTML(value=''), Output(), HBox(children=(Textarea(value='', ...
50
+ >>> gw.names.value = "Fabien Mathieu"
51
+ >>> gw.dbs.value = "HAL"
52
+ >>> gw.size.value = 3
53
+ >>> gw.compute_function(gw.compute, show=False)
54
+ >>> gw.save_link.value[:30]
55
+ "<a href='data:text/html;base64"
56
+ >>> gw.names.value = "Diego Perino, Laurent Viennot"
57
+ >>> gw.compute_function(gw.compute, show=False)
58
+ >>> gw.save_link.value[:30]
59
+ "<a href='data:text/html;base64"
60
+ """
61
+
62
+ def __init__(self):
63
+ self.names = widgets.Textarea(
64
+ placeholder=place_holder,
65
+ description="Name(s):",
66
+ layout=widgets.Layout(width="50%", height="100px"),
67
+ )
68
+ self.dbs = widgets.RadioButtons(
69
+ options=["HAL", "DBLP", "Both"],
70
+ description="DB(s):",
71
+ layout=widgets.Layout(width="80px", max_width="20%"),
72
+ )
73
+ self.size = widgets.IntSlider(
74
+ value=10,
75
+ min=1,
76
+ max=150,
77
+ step=1,
78
+ description="Size",
79
+ layout=widgets.Layout(width="250px"),
80
+ )
81
+ self.compute = widgets.Button(
82
+ description="Map!", layout=widgets.Layout(width="120px", max_width="140px")
83
+ )
84
+ self._col = widgets.VBox(
85
+ [self.size, self.compute],
86
+ layout=widgets.Layout(
87
+ align_items="center", max_width="27%", overflow="hidden"
88
+ ),
89
+ )
90
+ self.save_link = widgets.HTML(value="")
91
+ self.compute.on_click(self.compute_function)
92
+ self.out = widgets.Output()
93
+ self.widget = widgets.VBox(
94
+ [self.save_link, self.out, widgets.HBox([self.names, self._col, self.dbs])]
95
+ )
96
+ display(self.widget)
97
+ self.show = True
98
+
99
+ def html(self):
100
+ dbs = (
101
+ "hal"
102
+ if self.dbs.value == "HAL"
103
+ else "dblp"
104
+ if self.dbs.value == "DBLP"
105
+ else ["hal", "dblp"]
106
+ )
107
+ name = self.names.value
108
+ pattern = r",\s*(?![^()]*\))"
109
+ names = [n.strip() for n in re.split(pattern, name)]
110
+ self.save_link.value = ""
111
+ ctx = self.out if self.show else dummy_context()
112
+ if len(names) > 1:
113
+ lab = ListMap(names, dbs=dbs, name="planet")
114
+ if self.show:
115
+ self.out.clear_output()
116
+ with ctx:
117
+ lab.update_authors()
118
+ lab.update_publis()
119
+ extra = self.size.value - len(lab.authors)
120
+ if extra > 0:
121
+ lab.expand(target=extra)
122
+ else:
123
+ lab = EgoMap(names[0], dbs=dbs)
124
+ if self.show:
125
+ self.out.clear_output()
126
+ with ctx:
127
+ lab.build(target=self.size.value)
128
+ return lab.html()
129
+
130
+ def compute_function(self, b, show=True):
131
+ self.show = show
132
+ full = self.html()
133
+ b64 = base64.b64encode(
134
+ f"<html><body>{full}</body></html>".encode("utf8")
135
+ ).decode("utf8")
136
+ payload = f"data:text/html;base64,{b64}"
137
+ savename = safe_filename(self.names.value)
138
+ link_html = (
139
+ f"<a href='{payload}' download='{savename}'>Download the Map!</a>"
140
+ )
141
+ self.save_link.value = link_html
142
+ if show:
143
+ self.out.clear_output()
144
+ with self.out:
145
+ display(HTML(full))
gismap/lab/__init__.py CHANGED
@@ -2,9 +2,5 @@ from gismap.lab.labmap import (
2
2
  LabMap as Map,
3
3
  ListMap as ListMap,
4
4
  )
5
- from gismap.lab.lip6 import Lip6Full as Lip6Full, Lip6Map as Lip6
6
- from gismap.lab.toulouse import SolaceMap as Solace, LaasMap as Laas
7
- from gismap.lab.graph import lab2graph as lab2graph
8
- from gismap.lab.vis import generate_html as generate_html
9
5
  from gismap.lab.egomap import EgoMap as EgoMap
10
6
  from gismap.lab.lab_author import LabAuthor as LabAuthor
gismap/lab/egomap.py CHANGED
@@ -15,15 +15,15 @@ class EgoMap(LabMap):
15
15
 
16
16
  >>> dang = EgoMap("The-Dang Huynh", dbs="hal")
17
17
  >>> dang.build(target=10)
18
- >>> sorted(a.name for a in dang.authors.values()) # doctest: +NORMALIZE_WHITESPACE
19
- ['Bruno Kauffmann', 'Chung Shue Chen', 'Fabien Mathieu', 'François Baccelli', 'Laurent Viennot', 'Ludovic Noirie',
20
- 'Siu-Wai Ho', 'Sébastien Tixeuil', 'The-Dang Huynh', 'Yannick Carlinet']
18
+ >>> sorted(a.name for a in dang.authors.values()) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
19
+ ['Bruno Kauffmann', 'Chung Shue Chen', 'Fabien Mathieu',...
21
20
  """
22
21
 
23
22
  def __init__(self, star, *args, **kwargs):
24
23
  if isinstance(star, str):
25
24
  star = LabAuthor(star)
26
25
  star.metadata.position = (0, 0)
26
+ star.metadata.group = "star"
27
27
  self.star = star
28
28
  super().__init__(*args, **kwargs)
29
29
 
@@ -32,11 +32,10 @@ class EgoMap(LabMap):
32
32
 
33
33
  def build(self, **kwargs):
34
34
  target = kwargs.pop("target", 50)
35
- group = kwargs.pop("group", "moon")
36
35
  self.update_authors(desc="Star metadata")
37
36
  self.update_publis(desc="Star publications")
38
37
  kwargs["target"] = target - len(self.authors)
39
- self.expand(group=None, desc="Planets", **kwargs)
40
- kwargs.update({"target": target - len(self.authors), "group": group})
38
+ self.expand(group="planet", desc="Planets", **kwargs)
39
+ kwargs["target"] = target - len(self.authors)
41
40
  if kwargs["target"] > 0:
42
- self.expand(desc="Moons", **kwargs)
41
+ self.expand(group="moon", desc="Moons", **kwargs)
gismap/lab/expansion.py CHANGED
@@ -73,8 +73,8 @@ def count_prospect_entries(lab):
73
73
  count_publications.append(a.key)
74
74
  else:
75
75
  lab_authors.add(a.key)
76
- for l in lab_authors:
77
- count_coauthors[l].update(new_authors)
76
+ for a in lab_authors:
77
+ count_coauthors[a].update(new_authors)
78
78
 
79
79
  count_coauthors = Counter(
80
80
  k for new_authors in count_coauthors.values() for k in new_authors
@@ -222,12 +222,13 @@ def proper_prospects(
222
222
  locs = [j for j in np.where(jc[i, :] > threshold)[0] if not done[j]]
223
223
  done[locs] = True
224
224
  sources = sort_author_sources([prospects[j].author for j in locs])
225
- strength = sum(prospects[j].score for j in locs)
226
- new_author = LabAuthor.from_sources(sources)
227
- new_lab.append((strength, new_author))
225
+ if sources:
226
+ strength = sum(prospects[j].score for j in locs)
227
+ new_author = LabAuthor.from_sources(sources)
228
+ new_lab.append((strength, new_author))
228
229
 
229
230
  # Extract top prospects
230
- new_lab = [l[1] for l in sorted(new_lab, key=lambda l: l[0], reverse=True)][
231
+ new_lab = [a[1] for a in sorted(new_lab, key=lambda a: a[0], reverse=True)][
231
232
  :max_new
232
233
  ]
233
234
  new_rosetta = {s.key: a for a in new_lab for s in a.sources}
gismap/lab/filters.py CHANGED
@@ -3,7 +3,7 @@ import re
3
3
  # editorials = re.compile(r"ditorial|foreword", re.IGNORECASE)
4
4
  # charlatans = re.compile(r"Raoult|Kofman|Buob")
5
5
 
6
- editorials = ["ditorial", "Foreword", "Brief Announcement"]
6
+ editorials = ["ditorial", "Foreword", "Brief Announcement", "Preface", "Préface"]
7
7
  charlatans = ["Raoult", "Kofman", "Buob"]
8
8
 
9
9
 
gismap/lab/lab_author.py CHANGED
@@ -1,7 +1,8 @@
1
1
  from dataclasses import dataclass, field
2
+ import re
2
3
 
3
4
  from gismap import get_classes, HAL, DBLP
4
- from gismap.sources.models import DB
5
+ from gismap.sources.models import DB, db_class_to_auth_class
5
6
  from gismap.sources.multi import SourcedAuthor, sort_author_sources
6
7
  from gismap.utils.common import LazyRepr, list_of_objects
7
8
  from gismap.utils.logger import logger
@@ -26,6 +27,8 @@ class AuthorMetadata(LazyRepr):
26
27
  Group of the author.
27
28
  position: :class:`tuple`
28
29
  Coordinates of the author.
30
+ keys: :class:`dict`
31
+ Some DB key values of the author.
29
32
  """
30
33
 
31
34
  url: str = None
@@ -36,6 +39,26 @@ class AuthorMetadata(LazyRepr):
36
39
 
37
40
  @dataclass(repr=False)
38
41
  class LabAuthor(SourcedAuthor):
42
+ """
43
+ Examples
44
+ ---------
45
+ The metadata and DB key(s) of an author can be entered in parentheses using key/values.
46
+
47
+ Improper key/values are ignored (with a warning).
48
+
49
+
50
+ >>> dummy= LabAuthor("My Name(img: https://my.url.img, group:me,url:https://mysite.org,hal:key1,dblp:toto,badkey:hello,no_colon_separator)")
51
+ >>> dummy.metadata
52
+ AuthorMetadata(url='https://mysite.org', img='https://my.url.img', group='me')
53
+ >>> dummy.sources
54
+ [HALAuthor(name='My Name', key='key1'), DBLPAuthor(name='My Name', key='toto')]
55
+
56
+ You can enter multiple keys for the same DB. HAL key types are automatically detected.
57
+
58
+ >>> dummy2= LabAuthor("My Name (hal:key1,hal:123456,hal: My Other Name )")
59
+ >>> dummy2.sources
60
+ [HALAuthor(name='My Name', key='key1'), HALAuthor(name='My Name', key='123456', key_type='pid'), HALAuthor(name='My Name', key='My Other Name', key_type='fullname')]
61
+ """
39
62
  metadata: AuthorMetadata = field(default_factory=AuthorMetadata)
40
63
 
41
64
  def auto_img(self):
@@ -45,6 +68,30 @@ class LabAuthor(SourcedAuthor):
45
68
  self.metadata.img = img
46
69
  break
47
70
 
71
+ def __post_init__(self):
72
+ pattern = r"\s*([^,(]+)\s*(?:\(([^)]*)\))?\s*$"
73
+ match = re.match(pattern, self.name)
74
+ if match:
75
+ self.name = match.group(1).strip()
76
+ content = match.group(2)
77
+ if content:
78
+ for kv in content.split(","):
79
+ if ":" not in kv:
80
+ logger.warning(f"I don't know what to do with {kv}.")
81
+ continue
82
+ k, v = kv.split(":", 1)
83
+ k = k.strip().lower()
84
+ v = v.strip()
85
+ if k in db_dict:
86
+ DBAuthor = db_class_to_auth_class(db_dict[k])
87
+ self.sources.append(DBAuthor(name=self.name, key=v))
88
+ elif k in ["url", "img", "group"]:
89
+ setattr(self.metadata, k, v)
90
+ else:
91
+ logger.warning(f"I don't know what to do with {kv}.")
92
+ else:
93
+ self.name = self.name.strip()
94
+
48
95
  def auto_sources(self, dbs=None):
49
96
  """
50
97
  Automatically populate the sources based on author's name.
@@ -63,9 +110,9 @@ class LabAuthor(SourcedAuthor):
63
110
  for db in dbs:
64
111
  source = db.search_author(self.name)
65
112
  if len(source) == 0:
66
- logger.warning(f"{self.name} not found in {db.db_name}")
113
+ logger.info(f"{self.name} not found in {db.db_name}")
67
114
  elif len(source) > 1:
68
- logger.warning(f"Multiple entries for {self.name} in {db.db_name}")
115
+ logger.info(f"Multiple entries for {self.name} in {db.db_name}")
69
116
  sources += source
70
117
  if len(sources) > 0:
71
118
  self.sources = sort_author_sources(sources)
@@ -74,7 +121,7 @@ class LabAuthor(SourcedAuthor):
74
121
  def labify_author(author, rosetta):
75
122
  if isinstance(author, LabAuthor):
76
123
  return author
77
- return rosetta.get(author.key, author)
124
+ return rosetta.get(author.key, rosetta.get(author.name, author))
78
125
 
79
126
 
80
127
  def labify_publications(pubs, rosetta):
gismap/lab/labmap.py CHANGED
@@ -23,7 +23,7 @@ from gismap.lab.filters import (
23
23
  publication_size_filter,
24
24
  publication_oneword_filter,
25
25
  )
26
- from gismap.lab.graph import lab2graph
26
+ from gismap.gisgraphs.builder import make_vis
27
27
 
28
28
 
29
29
  class LabMap(MixInIO):
@@ -97,6 +97,8 @@ class LabMap(MixInIO):
97
97
  self.authors[author.key] = author
98
98
  if author.metadata.img is None:
99
99
  author.auto_img()
100
+ if author.metadata.group is None:
101
+ author.metadata.group = self.name
100
102
 
101
103
  def update_publis(self, desc="Publications information"):
102
104
  """
@@ -151,7 +153,7 @@ class LabMap(MixInIO):
151
153
  return None
152
154
 
153
155
  def html(self, **kwargs):
154
- return lab2graph(self, **kwargs)
156
+ return make_vis(self, **kwargs)
155
157
 
156
158
  def save_html(self, name=None, **kwargs):
157
159
  if name is None:
File without changes
@@ -0,0 +1,46 @@
1
+ import requests
2
+ from bs4 import BeautifulSoup as Soup
3
+ from gismap.lab.labmap import LabMap
4
+ from gismap.lab.lab_author import AuthorMetadata, LabAuthor
5
+ from gismap.utils.requests import get
6
+
7
+
8
+ class CedricMap(LabMap):
9
+ """
10
+ Class for handling a CNAM Cedric team from its name.
11
+ Default to `roc` team.
12
+ """
13
+ name = "roc"
14
+ base_url = "https://cedric.cnam.fr"
15
+
16
+ def _author_iterator(self):
17
+ soup = Soup(get(f"{self.base_url}/equipes/{self.name}/"), features="lxml")
18
+ searchers = [li.a for ul in soup.find('div', {'id': 'annuaire'})('ul')[:3] for li in ul('li')]
19
+ done = set()
20
+ for searcher in searchers:
21
+ name = searcher.text.split('(')[0].strip()
22
+ if name in done:
23
+ continue
24
+ url = f"{self.base_url}{searcher['href']}"
25
+ sousoup = Soup(get(url), features="lxml")
26
+ img = sousoup.find('img', {'class': 'photo'})['src']
27
+ response = requests.head(img, allow_redirects=True)
28
+ if int(response.headers.get("Content-Length")) < 3000:
29
+ img = None
30
+ done.add(name)
31
+ yield LabAuthor(name=name, metadata=AuthorMetadata(url=url, img=img, group=self.name.upper()))
32
+
33
+
34
+ class CedricFull(LabMap):
35
+ """
36
+ Class for handling all CNAM Cedric teams using `https://cedric.cnam.fr/equipes` to get team names.
37
+ """
38
+ name = "Cedric"
39
+
40
+ def _author_iterator(self):
41
+ base = "https://cedric.cnam.fr/equipes/"
42
+ soup = Soup(get(base), features="lxml")
43
+ teams = {a['href'].split('/')[-2] for a in soup('a') if base in a.get('href', "") and len(a['href'])>len(base)}
44
+ for team in teams:
45
+ for author in CedricMap(name=team)._author_iterator():
46
+ yield author
@@ -22,7 +22,7 @@ class LINCS(Map):
22
22
  name = "LINCS"
23
23
 
24
24
  def _author_iterator(self):
25
- soup = Soup(get("https://www.lincs.fr/people/"))
25
+ soup = Soup(get("https://www.lincs.fr/people/"), features="lxml")
26
26
  for entry in soup.main("div", class_="trombinoscope-row"):
27
27
  cols = entry("div")
28
28
  name = cols[1].text
@@ -36,7 +36,7 @@ class LINCS(Map):
36
36
  if group:
37
37
  group = group[-1].text
38
38
  else:
39
- group = None
39
+ group = "External"
40
40
  author = LabAuthor(name)
41
41
  author.metadata.img = img
42
42
  author.metadata.group = group
@@ -29,7 +29,21 @@ class LaasMap(LabMap):
29
29
  if "public_avatar" in a.img["class"]
30
30
  else None
31
31
  )
32
- yield LabAuthor(name=name, metadata=AuthorMetadata(url=url, img=img))
32
+ yield LabAuthor(name=name, metadata=AuthorMetadata(url=url, img=img, group=self.name.upper()))
33
+
34
+
35
+ class LaasFull(LabMap):
36
+ """
37
+ Class for handling all LAAS teams using `https://www.laas.fr/fr/equipes/` to get team names.
38
+ """
39
+ name = "LAAS"
40
+
41
+ def _author_iterator(self):
42
+ soup = Soup(get("https://www.laas.fr/fr/equipes/"), features="lxml")
43
+ teams = [a['href'].split('/')[-2] for a in soup('a', {'class': "badge"})]
44
+ for team in teams:
45
+ for author in LaasMap(name=team)._author_iterator():
46
+ yield author
33
47
 
34
48
 
35
49
  class SolaceMap(LabMap):
@@ -41,8 +55,11 @@ class SolaceMap(LabMap):
41
55
  regex = re.compile(r"<li>(.*?)(,| \(|</li>)")
42
56
 
43
57
  def _author_iterator(self):
44
- html = get("https://solace.cnrs.fr/people.html")
58
+ html = get("https://solace.cnrs.fr/people.html", verify=False)
45
59
  for name, _ in self.regex.findall(html):
46
60
  soup = Soup(name, features="lxml")
47
61
  url = soup.a["href"] if soup.a else None
48
- yield LabAuthor(name=soup.text.strip(), metadata=AuthorMetadata(url=url))
62
+ yield LabAuthor(
63
+ name=soup.text.strip(),
64
+ metadata=AuthorMetadata(url=url, group=self.name.upper()),
65
+ )
gismap/sources/dblp.py CHANGED
@@ -72,23 +72,6 @@ class DBLP(DB):
72
72
  Papers available in DBLP.
73
73
  wait: :class:`bool`
74
74
  Wait a bit to avoid 429.
75
-
76
- Examples
77
- --------
78
-
79
- >>> fabien = DBLPAuthor('Fabien Mathieu', key='66/2077')
80
- >>> publications = sorted(DBLP.from_author(fabien),
81
- ... key=lambda p: p.title)
82
- >>> publications[0] # doctest: +NORMALIZE_WHITESPACE
83
- DBLPPublication(title='Achievable catalog size in peer-to-peer video-on-demand systems.',
84
- authors=[DBLPAuthor(name='Yacine Boufkhad', key='75/5742'), DBLPAuthor(name='Fabien Mathieu', key='66/2077'),
85
- DBLPAuthor(name='Fabien de Montgolfier', key='57/6313'), DBLPAuthor(name='Diego Perino', key='03/3645'),
86
- DBLPAuthor(name='Laurent Viennot', key='v/LaurentViennot')],
87
- venue='IPTPS', type='conference', year=2008, key='conf/iptps/BoufkhadMMPV08')
88
- >>> publications[-1] # doctest: +NORMALIZE_WHITESPACE
89
- DBLPPublication(title='Upper Bounds for Stabilization in Acyclic Preference-Based Systems.',
90
- authors=[DBLPAuthor(name='Fabien Mathieu', key='66/2077')], venue='SSS', type='conference', year=2007,
91
- key='conf/sss/Mathieu07')
92
75
  """
93
76
  r = get(f"https://dblp.org/pid/{a.key}.xml")
94
77
  soup = Soup(r, features="xml")
@@ -100,6 +83,21 @@ class DBLP(DB):
100
83
 
101
84
  @dataclass(repr=False)
102
85
  class DBLPAuthor(Author, DBLP):
86
+ """
87
+ Examples
88
+ --------
89
+
90
+ >>> fabien = DBLPAuthor('Fabien Mathieu', key='66/2077')
91
+ >>> publications = sorted(fabien.get_publications(),
92
+ ... key=lambda p: p.title)
93
+ >>> publications[0].url # doctest: +NORMALIZE_WHITESPACE
94
+ 'https://dblp.org/rec/conf/iptps/BoufkhadMMPV08.html'
95
+ >>> publications[-1] # doctest: +NORMALIZE_WHITESPACE
96
+ DBLPPublication(title='Upper Bounds for Stabilization in Acyclic Preference-Based Systems.',
97
+ authors=[DBLPAuthor(name='Fabien Mathieu', key='66/2077')], venue='SSS', type='conference', year=2007,
98
+ key='conf/sss/Mathieu07')
99
+
100
+ """
103
101
  key: str
104
102
  aliases: list = field(default_factory=list)
105
103
 
gismap/sources/hal.py CHANGED
@@ -44,8 +44,7 @@ class HAL(DB):
44
44
  >>> HAL.search_author("Ana Busic")
45
45
  [HALAuthor(name='Ana Busic', key='anabusic')]
46
46
  >>> HAL.search_author("Potop-Butucaru Maria") # doctest: +NORMALIZE_WHITESPACE
47
- [HALAuthor(name='Potop-Butucaru Maria', key='858256', key_type='pid'),
48
- HALAuthor(name='Potop-Butucaru Maria', key='841868', key_type='pid')]
47
+ [HALAuthor(name='Potop-Butucaru Maria', key='841868', key_type='pid')]
49
48
  >>> diego = HAL.search_author("Diego Perino")
50
49
  >>> diego # doctest: +NORMALIZE_WHITESPACE
51
50
  [HALAuthor(name='Diego Perino', key='847558', key_type='pid'),
@@ -134,12 +133,12 @@ class HAL(DB):
134
133
 
135
134
  >>> maria = HAL.search_author('Maria Potop-Butucaru')
136
135
  >>> maria # doctest: +NORMALIZE_WHITESPACE
137
- [HALAuthor(name='Maria Potop-Butucaru', key='858256', key_type='pid'),
138
- HALAuthor(name='Maria Potop-Butucaru', key='841868', key_type='pid')]
139
- >>> len(HAL.from_author(maria[0]))
140
- 26
141
- >>> len(maria[1].get_publications())
142
- 124
136
+ [HALAuthor(name='Maria Potop-Butucaru', key='841868', key_type='pid')]
137
+ >>> n_pubs = len(HAL.from_author(maria[0]))
138
+ >>> n_pubs > 200
139
+ True
140
+ >>> n_pubs == len(maria[0].get_publications())
141
+ True
143
142
 
144
143
  Note: an error is raised if not enough data is provided
145
144
 
@@ -173,6 +172,9 @@ class HAL(DB):
173
172
  r = get(api, params=params)
174
173
  response = json.loads(r)["response"]
175
174
  res = [HALPublication.from_json(r) for r in response.get("docs", [])]
175
+ if len(res) == 0 and a.key_type != "fullname":
176
+ name = a.name
177
+ return HAL.from_author(HALAuthor(name=name, key=name, key_type="fullname"))
176
178
  return res
177
179
 
178
180
 
@@ -185,6 +187,13 @@ class HALAuthor(Author, HAL):
185
187
  _img: str = None
186
188
  _cv: bool = None
187
189
 
190
+ def __post_init__(self):
191
+ if self.key and self.key_type is None:
192
+ if self.key.isdigit():
193
+ self.key_type = "pid"
194
+ if " " in self.key:
195
+ self.key_type = "fullname"
196
+
188
197
  def check_cv(self):
189
198
  if self.key_type is not None:
190
199
  self._cv = False