github-heatmap 1.4.1__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/PKG-INFO +1 -1
  2. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/README.md +8 -8
  3. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/cli.py +28 -12
  4. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/config.py +2 -2
  5. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/drawer.py +10 -10
  6. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/base_loader.py +18 -0
  7. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/config.py +2 -2
  8. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/github_loader.py +2 -1
  9. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/gitlab_loader.py +1 -1
  10. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/notion_loader.py +58 -20
  11. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/weread_loader.py +23 -15
  12. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/poster.py +16 -10
  13. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/skyline/skyline.py +1 -1
  14. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/utils.py +4 -1
  15. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap.egg-info/PKG-INFO +1 -1
  16. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/setup.py +1 -1
  17. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/tests/test_notion_loader.py +44 -5
  18. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/tests/test_poster_utils.py +1 -4
  19. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/LICENSE +0 -0
  20. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/__init__.py +0 -0
  21. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/__main__.py +0 -0
  22. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/circluar_drawer.py +0 -0
  23. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/err.py +0 -0
  24. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/html_parser/__init__.py +0 -0
  25. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/html_parser/github_parser.py +0 -0
  26. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/html_parser/gitlab_parser.py +0 -0
  27. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/html_parser/jike_parse.py +0 -0
  28. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/html_parser/kindle_parser.py +0 -0
  29. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/__init__.py +0 -0
  30. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/apple_health_loader.py +0 -0
  31. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/bbdc_loader.py +0 -0
  32. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/bilibili_loader.py +0 -0
  33. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/chatgpt_loader.py +0 -0
  34. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/cichang_loader.py +0 -0
  35. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/covid_loader.py +0 -0
  36. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/dota2_loader.py +0 -0
  37. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/duolingo_loader.py +0 -0
  38. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/forest_loader.py +0 -0
  39. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/from_github_issue_loader.py +0 -0
  40. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/garmin_loader.py +0 -0
  41. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/gpx_loader.py +0 -0
  42. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/jike_loader.py +0 -0
  43. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/json_loader.py +0 -0
  44. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/kindle_loader.py +0 -0
  45. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/leetcode_loader.py +0 -0
  46. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/multiple_loader.py +0 -0
  47. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/neodb_loader.py +0 -0
  48. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/nrc_loader.py +0 -0
  49. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/ns_loader.py +0 -0
  50. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/openlanguage_loader.py +0 -0
  51. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/shanbay_loader.py +0 -0
  52. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/strava_loader.py +0 -0
  53. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/summary_loader.py +0 -0
  54. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/todoist_loader.py +0 -0
  55. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/wakatime_loader.py +0 -0
  56. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/youtube_loader.py +0 -0
  57. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/skyline/__init__.py +0 -0
  58. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/skyline/config.py +0 -0
  59. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/skyline/font/__init__.py +0 -0
  60. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/structures.py +0 -0
  61. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap.egg-info/SOURCES.txt +0 -0
  62. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap.egg-info/dependency_links.txt +0 -0
  63. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap.egg-info/entry_points.txt +0 -0
  64. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap.egg-info/requires.txt +0 -0
  65. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap.egg-info/top_level.txt +0 -0
  66. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/pyproject.toml +0 -0
  67. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/setup.cfg +0 -0
  68. {github_heatmap-1.4.1 → github_heatmap-2.2.0}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github_heatmap
3
- Version: 1.4.1
3
+ Version: 2.2.0
4
4
  Summary: Make everything a GitHub svg poster and Skyline!
5
5
  Home-page: https://github.com/malinkang/GitHubPoster
6
6
  Author: malinkang
@@ -435,18 +435,18 @@ github_heatmap nike --nike_refresh_token="your nike_refresh_token" --year 2012-2
435
435
  2. 点击「New integration」添加基础信息后,创建新的 Token
436
436
  3. 提交后可以看到 `Secrets` 下的 `Internal Integration Token`
437
437
 
438
- 获取用于生成 Poster 的 Notion 数据库 ID(database_id),查看[官方文档](https://developers.notion.com/docs/working-with-databases#adding-pages-to-a-database)获取更多信息。
438
+ 获取用于生成 Poster 的 Notion 数据源 ID(`data_source_id`),查看[官方文档](https://developers.notion.com/reference/query-a-data-source)获取更多信息。
439
439
 
440
- 1. 以全屏页面打开数据库
441
- 2. 复制页面链接,链接组成应该是 `https://www.notion.so/{workspace_name}/{database_id}?v={view_id}` 这样的
442
- 3. 其中 `{database_id}` 部分即为数据库 ID
440
+ 1. 在 Notion 中打开对应的数据源页面
441
+ 2. 复制页面链接,链接一般类似 `https://www.notion.so/{workspace_name}/{data_source_id}?v={view_id}`
442
+ 3. 其中 `{data_source_id}` 部分即为数据源 ID
443
443
 
444
- 注:数据库需要添加一个属性类型为 `Date` 的日期属性,该属性的值将作为生成 Poster 的日期数据使用。在生成时需将该日期属性的名称作为选项 `prop_name` 的值,默认值为 `Datetime`
444
+ 注:数据源需要至少包含一个类型为 `Date` 的属性作为日期字段,以及一个数值/公式/rollup 属性作为热力图统计值。生成时通过 `--date_prop_name` `--value_prop_name` 指定字段名。
445
445
 
446
446
  ```
447
- python3 -m github_heatmap notion --notion_token="your notion_token" --database_id="your database_id" --prop_name="your prop_name"
447
+ python3 -m github_heatmap notion --notion_token="your notion_token" --data_source_id="your data_source_id" --date_prop_name="your date_prop_name" --value_prop_name="your value_prop_name"
448
448
  or
449
- github_heatmap notion --notion_token="your notion_token" --database_id="your database_id" --prop_name="your prop_name"
449
+ github_heatmap notion --notion_token="your notion_token" --data_source_id="your data_source_id" --date_prop_name="your date_prop_name" --value_prop_name="your value_prop_name"
450
450
  ```
451
451
 
452
452
  </details>
@@ -738,4 +738,4 @@ python3 -m github_heatmap neodb --neodb_token <token> --mark_type <complete, wis
738
738
 
739
739
  谢谢就够了
740
740
 
741
- Just enjoy it
741
+ Just enjoy it
@@ -7,22 +7,22 @@ import os
7
7
  import sys
8
8
 
9
9
  from github_heatmap.circluar_drawer import CircularDrawer
10
- from github_heatmap.config import TYPE_INFO_DICT
11
- from github_heatmap.drawer import Drawer
12
- from github_heatmap.err import DepNotInstalledError
13
- from github_heatmap.loader import LOADER_DICT
14
- from github_heatmap.poster import Poster
15
- from github_heatmap.utils import build_level_colors, parse_years, reduce_year_list
16
10
  from github_heatmap.config import (
17
- GITHUB_LEVEL_COLORS,
18
- HEAD_FONT_SIZE,
19
- YEAR_FONT_SIZE,
20
- MONTH_FONT_SIZE,
21
11
  DOM_BOX_PADING,
22
12
  DOM_BOX_TUPLE,
13
+ GITHUB_LEVEL_COLORS,
14
+ HEAD_FONT_SIZE,
15
+ MARGIN_LEFT,
23
16
  MARGIN_TOP,
24
- MARGIN_LEFT
17
+ MONTH_FONT_SIZE,
18
+ TYPE_INFO_DICT,
19
+ YEAR_FONT_SIZE,
25
20
  )
21
+ from github_heatmap.drawer import Drawer
22
+ from github_heatmap.err import DepNotInstalledError
23
+ from github_heatmap.loader import LOADER_DICT
24
+ from github_heatmap.poster import Poster
25
+ from github_heatmap.utils import build_level_colors, parse_years, reduce_year_list
26
26
 
27
27
  OUT_FOLDER = os.path.join(os.getcwd(), "OUT_FOLDER")
28
28
 
@@ -76,6 +76,7 @@ def run():
76
76
  }
77
77
  p.level_colors = level_colors
78
78
  p.use_github_level_mapping = not (args.special_number1 or args.special_number2)
79
+ p.use_raw_level = args.use_raw_level
79
80
 
80
81
  p.tooltip_template = args.tooltip_template or None
81
82
 
@@ -142,6 +143,16 @@ def run():
142
143
  for child_loader in loader.loader_list
143
144
  }
144
145
 
146
+ # explicit level thresholds override quartile calculation
147
+ if args.level_thresholds:
148
+ p.level_thresholds = tuple(float(v) for v in args.level_thresholds.split(","))
149
+ p.use_github_level_mapping = True
150
+
151
+ # explicit level colors override computed colors
152
+ if args.level_colors:
153
+ p.level_colors = [c.strip() for c in args.level_colors.split(",")]
154
+ p.use_github_level_mapping = True
155
+
145
156
  # set title
146
157
  # we don't know issue content so use name
147
158
  p.title = (
@@ -166,7 +177,12 @@ def run():
166
177
  MARGIN_TOP
167
178
  + HEAD_FONT_SIZE
168
179
  + poster_length
169
- * (YEAR_FONT_SIZE + MONTH_FONT_SIZE+DOM_BOX_PADING*3 + (DOM_BOX_PADING + DOM_BOX_TUPLE[0]) * 7)
180
+ * (
181
+ YEAR_FONT_SIZE
182
+ + MONTH_FONT_SIZE
183
+ + DOM_BOX_PADING * 3
184
+ + (DOM_BOX_PADING + DOM_BOX_TUPLE[0]) * 7
185
+ )
170
186
  )
171
187
  if not os.path.exists(OUT_FOLDER):
172
188
  os.mkdir(OUT_FOLDER)
@@ -1,4 +1,4 @@
1
- DOM_BOX_TUPLE = (4,4)
1
+ DOM_BOX_TUPLE = (4, 4)
2
2
  DOM_BOX_TUPLE_LIST_FOR_TWO = ((2.6, 1.3), (2.6, 1.3))
3
3
  DOM_BOX_TUPLE_LIST_FOR_THREE = ((2.7, 0.9), (2.7, 0.9), (2.7, 0.9))
4
4
 
@@ -11,7 +11,7 @@ DOM_BOX_DICT = {
11
11
  3: {"dom": DOM_BOX_TUPLE_LIST_FOR_THREE},
12
12
  }
13
13
  MARGIN_LEFT = 10
14
- MARGIN_TOP = 4
14
+ MARGIN_TOP = 4
15
15
  HEAD_FONT_SIZE = 6
16
16
  YEAR_FONT_SIZE = 3
17
17
  MONTH_FONT_SIZE = 2.5
@@ -7,19 +7,15 @@ from github_heatmap.config import (
7
7
  COLOR_TUPLE,
8
8
  DEFAULT_DOM_COLOR,
9
9
  DOM_BOX_DICT,
10
+ DOM_BOX_PADING,
11
+ DOM_BOX_RADIUS,
10
12
  DOM_BOX_TUPLE,
13
+ MONTH_FONT_SIZE,
11
14
  MONTH_NAMES,
12
15
  YEAR_FONT_SIZE,
13
- MONTH_FONT_SIZE,
14
- DOM_BOX_PADING,
15
- DOM_BOX_RADIUS,
16
16
  )
17
17
  from github_heatmap.err import BaseDrawError
18
- from github_heatmap.utils import (
19
- interpolate_color,
20
- make_key_times,
21
- resolve_github_level,
22
- )
18
+ from github_heatmap.utils import interpolate_color, make_key_times, resolve_github_level
23
19
 
24
20
 
25
21
  class Drawer:
@@ -81,7 +77,7 @@ class Drawer:
81
77
  return self.poster.level_thresholds
82
78
 
83
79
  def _make_github_level_color(self, length, type_name=None):
84
- level = resolve_github_level(length, self._resolve_level_thresholds(type_name))
80
+ level = resolve_github_level(length, self._resolve_level_thresholds(type_name), self.poster.use_raw_level)
85
81
  if level == 0:
86
82
  return self.poster.colors.get("dom")
87
83
  return self.poster.level_colors[level - 1]
@@ -269,7 +265,11 @@ class Drawer:
269
265
  style=self.month_names_style,
270
266
  )
271
267
  )
272
- if index > 0 and index < 53 and month!=MONTH_NAMES[github_rect_day.month - 1]:
268
+ if (
269
+ index > 0
270
+ and index < 53
271
+ and month != MONTH_NAMES[github_rect_day.month - 1]
272
+ ):
273
273
  month = MONTH_NAMES[github_rect_day.month - 1]
274
274
  dr.add(
275
275
  dr.text(
@@ -233,6 +233,24 @@ class BaseLoader(ABC):
233
233
  action="store_true",
234
234
  help="if account is CN",
235
235
  )
236
+ group.add_argument(
237
+ "--use-raw-level",
238
+ dest="use_raw_level",
239
+ action="store_true",
240
+ help="Use raw value (1-4) as level directly, skip quartile calculation",
241
+ )
242
+ group.add_argument(
243
+ "--level-thresholds",
244
+ dest="level_thresholds",
245
+ default="",
246
+ help="Explicit level thresholds (comma-separated, e.g. '360,450,99999'), overrides quartile calculation",
247
+ )
248
+ group.add_argument(
249
+ "--level-colors",
250
+ dest="level_colors",
251
+ default="",
252
+ help="Explicit level colors (comma-separated hex, e.g. '#E74C3C,#F1C40F,#2ECC71,#2ECC71'), overrides computed colors",
253
+ )
236
254
  # special here
237
255
  group.add_argument(
238
256
  "--stand-with-ukraine",
@@ -115,8 +115,8 @@ JIKE_PERSON_URL = "https://web.okjike.com/u/{user_id}"
115
115
  BBDC_API_URL = "https://learnywhere.cn/bb/dashboard/profile/search?userId={user_id}"
116
116
 
117
117
  # Notion
118
- NOTION_API_URL = "https://api.notion.com/v1/databases/{database_id}/query"
119
- NOTION_API_VERSION = "2022-06-28"
118
+ NOTION_API_URL = "https://api.notion.com/v1/data_sources/{data_source_id}/query"
119
+ NOTION_API_VERSION = "2026-03-11"
120
120
 
121
121
  # Weread
122
122
  WEREAD_BASE_URL = "https://weread.qq.com/"
@@ -1,6 +1,7 @@
1
- import requests
2
1
  from threading import Thread
3
2
 
3
+ import requests
4
+
4
5
  from github_heatmap.html_parser import GitHubParser
5
6
  from github_heatmap.loader.base_loader import BaseLoader, LoadError
6
7
  from github_heatmap.loader.config import GITHUB_CONTRIBUCTIONS_URL
@@ -1,7 +1,7 @@
1
1
  import time
2
2
 
3
3
  import requests
4
- from pendulum import parse, interval
4
+ from pendulum import interval, parse
5
5
 
6
6
  from github_heatmap.html_parser import GitLabParser
7
7
  from github_heatmap.loader.base_loader import BaseLoader, LoadError
@@ -1,10 +1,12 @@
1
- from datetime import datetime, timedelta
1
+ import argparse
2
+ import json
2
3
  import time
4
+ import warnings
3
5
  from collections import defaultdict
6
+ from datetime import datetime, timedelta
4
7
 
5
8
  import pendulum
6
9
  import requests
7
- import json
8
10
 
9
11
  from github_heatmap.loader.base_loader import BaseLoader
10
12
  from github_heatmap.loader.config import NOTION_API_URL, NOTION_API_VERSION
@@ -17,12 +19,28 @@ class NotionLoader(BaseLoader):
17
19
  def __init__(self, from_year, to_year, _type, **kwargs):
18
20
  super().__init__(from_year, to_year, _type)
19
21
  self.number_by_date_dict = self.generate_date_dict(from_year, to_year)
20
- self.notion_token = kwargs.get("notion_token", "").strip()
21
- self.database_id = kwargs.get("database_id", "").strip()
22
- self.date_prop_name = kwargs.get("date_prop_name", "")
23
- self.value_prop_name = kwargs.get("value_prop_name", "")
24
- self.database_filter = kwargs.get("database_filter", "")
25
- self.tooltip_prop_name = kwargs.get("tooltip_prop_name", "").strip()
22
+ self.notion_token = (kwargs.get("notion_token") or "").strip()
23
+ self.data_source_id = (kwargs.get("data_source_id") or "").strip()
24
+ deprecated_database_id = (kwargs.get("database_id") or "").strip()
25
+ if deprecated_database_id and not self.data_source_id:
26
+ warnings.warn(
27
+ "--database_id is deprecated; use --data_source_id instead.",
28
+ DeprecationWarning,
29
+ stacklevel=2,
30
+ )
31
+ self.data_source_id = deprecated_database_id
32
+ self.date_prop_name = kwargs.get("date_prop_name") or ""
33
+ self.value_prop_name = kwargs.get("value_prop_name") or ""
34
+ self.data_source_filter = kwargs.get("data_source_filter") or ""
35
+ deprecated_database_filter = kwargs.get("database_filter") or ""
36
+ if deprecated_database_filter and not self.data_source_filter:
37
+ warnings.warn(
38
+ "--database_filter is deprecated; use --data_source_filter instead.",
39
+ DeprecationWarning,
40
+ stacklevel=2,
41
+ )
42
+ self.data_source_filter = deprecated_database_filter
43
+ self.tooltip_prop_name = (kwargs.get("tooltip_prop_name") or "").strip()
26
44
  self.tooltip_by_date_dict = defaultdict(list)
27
45
 
28
46
  @classmethod
@@ -33,11 +51,17 @@ class NotionLoader(BaseLoader):
33
51
  type=str,
34
52
  help="The Notion internal integration token.",
35
53
  )
54
+ parser.add_argument(
55
+ "--data_source_id",
56
+ dest="data_source_id",
57
+ type=str,
58
+ help="The Notion data source id.",
59
+ )
36
60
  parser.add_argument(
37
61
  "--database_id",
38
62
  dest="database_id",
39
63
  type=str,
40
- help="The Notion database id.",
64
+ help=argparse.SUPPRESS,
41
65
  )
42
66
  parser.add_argument(
43
67
  "--date_prop_name",
@@ -45,7 +69,7 @@ class NotionLoader(BaseLoader):
45
69
  type=str,
46
70
  default="Datetime",
47
71
  required=optional,
48
- help="The database property name which stored the datetime.",
72
+ help="The Notion data source property name that stores the date.",
49
73
  )
50
74
  parser.add_argument(
51
75
  "--value_prop_name",
@@ -53,15 +77,23 @@ class NotionLoader(BaseLoader):
53
77
  type=str,
54
78
  default="Datetime",
55
79
  required=optional,
56
- help="The database property name which stored the datetime.",
57
- )
80
+ help="The Notion data source property name used as the heatmap value.",
81
+ )
82
+ parser.add_argument(
83
+ "--data_source_filter",
84
+ dest="data_source_filter",
85
+ type=str,
86
+ default="",
87
+ required=False,
88
+ help="Optional Notion data source query filter in JSON format.",
89
+ )
58
90
  parser.add_argument(
59
91
  "--database_filter",
60
92
  dest="database_filter",
61
93
  type=str,
62
94
  default="",
63
95
  required=False,
64
- help="The database property name which stored the datetime.",
96
+ help=argparse.SUPPRESS,
65
97
  )
66
98
  parser.add_argument(
67
99
  "--tooltip_prop_name",
@@ -90,8 +122,8 @@ class NotionLoader(BaseLoader):
90
122
  ]
91
123
  },
92
124
  }
93
- if self.database_filter:
94
- payload["filter"]["and"].append(json.loads(self.database_filter))
125
+ if self.data_source_filter:
126
+ payload["filter"]["and"].append(json.loads(self.data_source_filter))
95
127
  if start_cursor:
96
128
  payload.update({"start_cursor": start_cursor})
97
129
  print(payload)
@@ -104,14 +136,14 @@ class NotionLoader(BaseLoader):
104
136
  print(headers)
105
137
  try:
106
138
  resp = requests.post(
107
- NOTION_API_URL.format(database_id=self.database_id),
139
+ NOTION_API_URL.format(data_source_id=self.data_source_id),
108
140
  json=payload,
109
141
  headers=headers,
110
142
  )
111
143
  except requests.RequestException:
112
144
  print("Failed to connect to Notion API.")
113
145
  return data_list
114
- print(resp.text)
146
+ print(getattr(resp, "text", ""))
115
147
  if not resp.ok:
116
148
  # Treat non-OK responses as an empty result set so we can still draw
117
149
  # a heatmap even when the Notion API yields no rows for the period.
@@ -211,7 +243,9 @@ class NotionLoader(BaseLoader):
211
243
  [p.get("name") or p.get("id", "") for p in item.get("people", [])]
212
244
  ).strip()
213
245
  if item_type == "relation":
214
- return ", ".join([r.get("id", "") for r in item.get("relation", [])]).strip()
246
+ return ", ".join(
247
+ [r.get("id", "") for r in item.get("relation", [])]
248
+ ).strip()
215
249
  if item_type == "date":
216
250
  return cls._format_date(item.get("date", {}))
217
251
  if item_type in ("url", "email", "phone_number"):
@@ -274,7 +308,9 @@ class NotionLoader(BaseLoader):
274
308
  if prop_type == "date":
275
309
  return cls._format_date(prop.get("date", {}))
276
310
  if prop_type == "relation":
277
- return ", ".join([r.get("id", "") for r in prop.get("relation", [])]).strip()
311
+ return ", ".join(
312
+ [r.get("id", "") for r in prop.get("relation", [])]
313
+ ).strip()
278
314
  if prop_type == "files":
279
315
  texts = []
280
316
  for file_obj in prop.get("files", []):
@@ -310,7 +346,9 @@ class NotionLoader(BaseLoader):
310
346
  if rollup_type == "date":
311
347
  return cls._format_date(rollup.get("date", {}))
312
348
  if rollup_type == "array":
313
- texts = [cls._extract_rollup_item(item) for item in rollup.get("array", [])]
349
+ texts = [
350
+ cls._extract_rollup_item(item) for item in rollup.get("array", [])
351
+ ]
314
352
  texts = [text for text in texts if text]
315
353
  return ", ".join(texts)
316
354
  return ""
@@ -1,22 +1,25 @@
1
1
  import json
2
+ import os
3
+
2
4
  import pendulum
3
5
  import requests
4
- import os
5
6
 
6
7
  from github_heatmap.loader.base_loader import BaseLoader
7
8
  from github_heatmap.loader.config import WEREAD_BASE_URL, WEREAD_HISTORY_URL
8
9
 
9
10
  headers = {
10
- 'User-Agent': "WeRead/8.2.5 WRBrand/xiaomi Dalvik/2.1.0 (Linux; U; Android 12; Redmi Note 7 Pro Build/SQ3A.220705.004)",
11
- 'Connection': "Keep-Alive",
12
- 'Accept-Encoding': "gzip",
13
- 'baseapi': "32",
14
- 'appver': "8.2.5.10163885",
15
- 'osver': "12",
16
- 'channelId': "11",
17
- 'basever': "8.2.5.10163885",
18
- 'Content-Type': "application/json; charset=UTF-8"
11
+ "User-Agent": "WeRead/8.2.5 WRBrand/xiaomi Dalvik/2.1.0 (Linux; U; Android 12; Redmi Note 7 Pro Build/SQ3A.220705.004)",
12
+ "Connection": "Keep-Alive",
13
+ "Accept-Encoding": "gzip",
14
+ "baseapi": "32",
15
+ "appver": "8.2.5.10163885",
16
+ "osver": "12",
17
+ "channelId": "11",
18
+ "basever": "8.2.5.10163885",
19
+ "Content-Type": "application/json; charset=UTF-8",
19
20
  }
21
+
22
+
20
23
  class WereadLoader(BaseLoader):
21
24
  track_color = "#2EA8F7"
22
25
  unit = "mins"
@@ -45,7 +48,11 @@ class WereadLoader(BaseLoader):
45
48
  # )
46
49
 
47
50
  def refresh_token(self):
48
- body = {"deviceId":self.device_id ,"refreshToken":self.token,"activationCode":self.activation_code}
51
+ body = {
52
+ "deviceId": self.device_id,
53
+ "refreshToken": self.token,
54
+ "activationCode": self.activation_code,
55
+ }
49
56
  r = self.session.post(
50
57
  "https://api.notionhub.app/refresh-weread-token", json=body
51
58
  )
@@ -54,13 +61,14 @@ class WereadLoader(BaseLoader):
54
61
  vid = response_data.get("vid")
55
62
  accessToken = response_data.get("accessToken")
56
63
  if vid and accessToken:
57
- self.session.headers.update({"vid": str(vid), "accessToken": accessToken})
64
+ self.session.headers.update(
65
+ {"vid": str(vid), "accessToken": accessToken}
66
+ )
58
67
  else:
59
68
  print("Failed to refresh token")
60
69
  else:
61
70
  print("Failed to refresh token")
62
-
63
-
71
+
64
72
  def get_api_data(self):
65
73
  r = self.session.get(WEREAD_HISTORY_URL)
66
74
  if not r.ok:
@@ -75,7 +83,7 @@ class WereadLoader(BaseLoader):
75
83
 
76
84
  def make_track_dict(self):
77
85
  api_data = self.get_api_data()
78
- if("readTimes" in api_data):
86
+ if "readTimes" in api_data:
79
87
  readTimes = dict(sorted(api_data["readTimes"].items(), reverse=True))
80
88
  for k, v in readTimes.items():
81
89
  k = pendulum.from_timestamp(int(k), tz=self.time_zone)
@@ -1,14 +1,12 @@
1
1
  """Create a poster from track data."""
2
+
2
3
  from collections import defaultdict
3
4
 
4
5
  import svgwrite
5
6
 
7
+ from github_heatmap.config import HEAD_FONT_SIZE, MARGIN_LEFT, MARGIN_TOP
6
8
  from github_heatmap.structures import XY, ValueRange
7
- from github_heatmap.config import (
8
- HEAD_FONT_SIZE,
9
- MARGIN_LEFT,
10
- MARGIN_TOP
11
- )
9
+
12
10
 
13
11
  class Poster:
14
12
  def __init__(self):
@@ -28,7 +26,7 @@ class Poster:
28
26
  self.width = 400
29
27
  self.height = 300
30
28
  self.years = None
31
- self.offset = XY(MARGIN_LEFT,MARGIN_TOP)
29
+ self.offset = XY(MARGIN_LEFT, MARGIN_TOP)
32
30
  # maybe support more type
33
31
  self.tracks_drawer = None
34
32
  self.trans = None
@@ -40,6 +38,7 @@ class Poster:
40
38
  self.level_thresholds = ()
41
39
  self.level_thresholds_by_type = {}
42
40
  self.use_github_level_mapping = True
41
+ self.use_raw_level = False
43
42
 
44
43
  # for year summary
45
44
  self.is_summary = False
@@ -107,10 +106,17 @@ class Poster:
107
106
  def __draw_header(self, d):
108
107
  self.offset.y += HEAD_FONT_SIZE
109
108
  text_color = self.colors["text"]
110
- title_style = f"font-size:{HEAD_FONT_SIZE}px; font-family:Arial; font-weight:bold;"
111
- d.add(d.text(self.title, insert=(self.offset.x, self.offset.y), fill=text_color, style=title_style))
112
-
113
-
109
+ title_style = (
110
+ f"font-size:{HEAD_FONT_SIZE}px; font-family:Arial; font-weight:bold;"
111
+ )
112
+ d.add(
113
+ d.text(
114
+ self.title,
115
+ insert=(self.offset.x, self.offset.y),
116
+ fill=text_color,
117
+ style=title_style,
118
+ )
119
+ )
114
120
 
115
121
  def __draw_footer(self, d):
116
122
  self.tracks_drawer.draw_footer(d)
@@ -2,7 +2,7 @@ import math
2
2
  import os
3
3
 
4
4
  import numpy as np
5
- from pendulum import parse, interval
5
+ from pendulum import interval, parse
6
6
  from sdf import X, Y, box, ease, measure_text, rectangle, text, union
7
7
 
8
8
  import github_heatmap.skyline
@@ -93,9 +93,12 @@ def make_github_level_thresholds(number_list):
93
93
  return tuple(nearest_rank(p) for p in (0.25, 0.50, 0.75))
94
94
 
95
95
 
96
- def resolve_github_level(value, thresholds):
96
+ def resolve_github_level(value, thresholds, use_raw_level=False):
97
97
  if value <= 0:
98
98
  return 0
99
+ if use_raw_level:
100
+ # 直接使用传入的值作为级别(1-4),跳过四分位数计算
101
+ return max(1, min(4, int(value)))
99
102
  if not thresholds:
100
103
  return 1
101
104
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github_heatmap
3
- Version: 1.4.1
3
+ Version: 2.2.0
4
4
  Summary: Make everything a GitHub svg poster and Skyline!
5
5
  Home-page: https://github.com/malinkang/GitHubPoster
6
6
  Author: malinkang
@@ -6,7 +6,7 @@ setup(
6
6
  author_email="linkang.ma@gmail.com",
7
7
  url="https://github.com/malinkang/GitHubPoster",
8
8
  license="MIT",
9
- version="1.4.1",
9
+ version="2.2.0",
10
10
  description="Make everything a GitHub svg poster and Skyline!",
11
11
  packages=find_packages(),
12
12
  include_package_data=True,
@@ -1,3 +1,4 @@
1
+ import pytest
1
2
  import requests
2
3
 
3
4
  from github_heatmap.loader import notion_loader
@@ -10,7 +11,7 @@ def build_loader():
10
11
  2024,
11
12
  "notion",
12
13
  notion_token="token",
13
- database_id="db",
14
+ data_source_id="ds",
14
15
  date_prop_name="Date",
15
16
  value_prop_name="Value",
16
17
  tooltip_prop_name="Tooltip",
@@ -46,10 +47,7 @@ def test_notion_loader_make_track_dict_with_tooltips():
46
47
  loader.make_track_dict()
47
48
 
48
49
  assert loader.number_by_date_dict["2024-01-01"] == 7
49
- assert (
50
- loader.tooltip_by_date_dict["2024-01-01"]
51
- == "Study\nRead, Write"
52
- )
50
+ assert loader.tooltip_by_date_dict["2024-01-01"] == "Study\nRead, Write"
53
51
 
54
52
 
55
53
  def test_notion_loader_extract_property_text_variants():
@@ -133,3 +131,44 @@ def test_notion_loader_handles_request_exception(monkeypatch):
133
131
  assert tracks
134
132
  assert all(value == 0 for value in tracks.values())
135
133
  assert loader.number_list
134
+
135
+
136
+ def test_notion_loader_queries_data_source_endpoint(monkeypatch):
137
+ loader = build_loader()
138
+ captured = {}
139
+
140
+ class DummyResp:
141
+ ok = True
142
+
143
+ @staticmethod
144
+ def json():
145
+ return {"results": [], "next_cursor": None, "has_more": False}
146
+
147
+ def fake_post(url, json, headers):
148
+ captured["url"] = url
149
+ captured["json"] = json
150
+ captured["headers"] = headers
151
+ return DummyResp()
152
+
153
+ monkeypatch.setattr(notion_loader.requests, "post", fake_post)
154
+
155
+ loader.get_api_data()
156
+
157
+ assert captured["url"].endswith("/v1/data_sources/ds/query")
158
+ assert captured["headers"]["Notion-Version"] == "2026-03-11"
159
+ assert captured["json"]["filter"]["and"][0]["property"] == "Date"
160
+
161
+
162
+ def test_notion_loader_accepts_deprecated_database_id():
163
+ with pytest.warns(DeprecationWarning, match="--database_id is deprecated"):
164
+ loader = NotionLoader(
165
+ 2024,
166
+ 2024,
167
+ "notion",
168
+ notion_token="token",
169
+ database_id="legacy-db",
170
+ date_prop_name="Date",
171
+ value_prop_name="Value",
172
+ )
173
+
174
+ assert loader.data_source_id == "legacy-db"
@@ -67,10 +67,7 @@ def test_drawer_tooltip_formatting():
67
67
  assert drawer._format_tooltip("2024-01-01") == "2024-01-01"
68
68
 
69
69
  poster.tooltip_template = "{date} {value} for {type}"
70
- assert (
71
- drawer._format_tooltip("2024-01-01", 7, "run")
72
- == "2024-01-01 7 for run"
73
- )
70
+ assert drawer._format_tooltip("2024-01-01", 7, "run") == "2024-01-01 7 for run"
74
71
 
75
72
  poster.tooltip_by_date = {"2024-01-01": "Custom"}
76
73
  assert drawer._format_tooltip("2024-01-01", 5) == "Custom"
File without changes
File without changes