klnote 0.2.0__tar.gz → 0.3.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 (27) hide show
  1. {klnote-0.2.0 → klnote-0.3.0}/PKG-INFO +14 -5
  2. {klnote-0.2.0 → klnote-0.3.0}/README.md +13 -4
  3. klnote-0.3.0/klnote/__init__.py +3 -0
  4. {klnote-0.2.0 → klnote-0.3.0}/klnote/cli.py +111 -16
  5. {klnote-0.2.0 → klnote-0.3.0}/klnote/core/service.py +44 -0
  6. klnote-0.3.0/klnote/core/utils/time_1.py +21 -0
  7. {klnote-0.2.0 → klnote-0.3.0}/klnote.egg-info/PKG-INFO +14 -5
  8. {klnote-0.2.0 → klnote-0.3.0}/pyproject.toml +1 -1
  9. klnote-0.2.0/klnote/__init__.py +0 -3
  10. klnote-0.2.0/klnote/core/utils/time_1.py +0 -8
  11. {klnote-0.2.0 → klnote-0.3.0}/klnote/core/__init__.py +0 -0
  12. {klnote-0.2.0 → klnote-0.3.0}/klnote/core/constants.py +0 -0
  13. {klnote-0.2.0 → klnote-0.3.0}/klnote/core/database.py +0 -0
  14. {klnote-0.2.0 → klnote-0.3.0}/klnote/core/exceptions.py +0 -0
  15. {klnote-0.2.0 → klnote-0.3.0}/klnote/core/schema/__init__.py +0 -0
  16. {klnote-0.2.0 → klnote-0.3.0}/klnote/core/schema/schame.py +0 -0
  17. {klnote-0.2.0 → klnote-0.3.0}/klnote/core/utils/__init__.py +0 -0
  18. {klnote-0.2.0 → klnote-0.3.0}/klnote/core/utils/db.py +0 -0
  19. {klnote-0.2.0 → klnote-0.3.0}/klnote/core/utils/editor.py +0 -0
  20. {klnote-0.2.0 → klnote-0.3.0}/klnote.egg-info/SOURCES.txt +0 -0
  21. {klnote-0.2.0 → klnote-0.3.0}/klnote.egg-info/dependency_links.txt +0 -0
  22. {klnote-0.2.0 → klnote-0.3.0}/klnote.egg-info/entry_points.txt +0 -0
  23. {klnote-0.2.0 → klnote-0.3.0}/klnote.egg-info/requires.txt +0 -0
  24. {klnote-0.2.0 → klnote-0.3.0}/klnote.egg-info/top_level.txt +0 -0
  25. {klnote-0.2.0 → klnote-0.3.0}/setup.cfg +0 -0
  26. {klnote-0.2.0 → klnote-0.3.0}/tests/test_cli.py +0 -0
  27. {klnote-0.2.0 → klnote-0.3.0}/tests/test_service.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: klnote
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Stock note management tool
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -38,12 +38,17 @@ pip install klnote
38
38
  klnote stk ls # 列出所有股票
39
39
  klnote stk add 000001 -n 平安银行 # 添加股票
40
40
  klnote stk get 000001 # 查看股票(支持 id/code/name)
41
- klnote stk delete 000001 # 删除股票
41
+ klnote stk del 000001 # 删除股票
42
+ klnote stk get-meta 000001 # 查看 metadata
43
+ klnote stk set-meta 000001 --name 平安银行 --country CN # 更新 metadata
42
44
 
43
45
  # 笔记操作
44
46
  klnote note add 000001 -s "摘要" -c "内容" # 添加笔记
45
47
  klnote note ls 000001 # 列出笔记
46
- klnote note delete 000001 0 # 删除第0条笔记
48
+ klnote note read 000001 0 # 读取第0条笔记
49
+ klnote note edit 000001 0 -c "新内容" # 编辑笔记
50
+ klnote note edit 000001 0 -e # 弹出 GUI 编辑器
51
+ klnote note del 000001 0 # 删除第0条笔记
47
52
  ```
48
53
 
49
54
  ## 数据库
@@ -66,7 +71,9 @@ klnote --help
66
71
  klnote stk ls # 列出所有股票(表格对齐)
67
72
  klnote stk add <code> [-n name] [-d desc] # 添加股票
68
73
  klnote stk get <identifier> # 查看股票详情
69
- klnote stk delete <identifier> # 删除股票
74
+ klnote stk del <identifier> # 删除股票
75
+ klnote stk get-meta <identifier> # 查看 metadata
76
+ klnote stk set-meta <identifier> # 更新 metadata
70
77
  ```
71
78
 
72
79
  **输出示例:**
@@ -80,7 +87,9 @@ klnote stk delete <identifier> # 删除股票
80
87
  ```
81
88
  klnote note add <stock_identifier> [-s 摘要] [-c 内容] # 添加笔记
82
89
  klnote note ls <stock_identifier> # 列出某只股票的所有笔记
83
- klnote note delete <stock_identifier> <index> # 删除指定笔记
90
+ klnote note read <stock_identifier> <index> # 读取单条笔记
91
+ klnote note edit <stock_identifier> <index> [-c 内容] [-s 摘要] [-e] # 编辑笔记
92
+ klnote note del <stock_identifier> <index> # 删除指定笔记
84
93
  ```
85
94
 
86
95
  ### 高级操作 (admin)
@@ -27,12 +27,17 @@ pip install klnote
27
27
  klnote stk ls # 列出所有股票
28
28
  klnote stk add 000001 -n 平安银行 # 添加股票
29
29
  klnote stk get 000001 # 查看股票(支持 id/code/name)
30
- klnote stk delete 000001 # 删除股票
30
+ klnote stk del 000001 # 删除股票
31
+ klnote stk get-meta 000001 # 查看 metadata
32
+ klnote stk set-meta 000001 --name 平安银行 --country CN # 更新 metadata
31
33
 
32
34
  # 笔记操作
33
35
  klnote note add 000001 -s "摘要" -c "内容" # 添加笔记
34
36
  klnote note ls 000001 # 列出笔记
35
- klnote note delete 000001 0 # 删除第0条笔记
37
+ klnote note read 000001 0 # 读取第0条笔记
38
+ klnote note edit 000001 0 -c "新内容" # 编辑笔记
39
+ klnote note edit 000001 0 -e # 弹出 GUI 编辑器
40
+ klnote note del 000001 0 # 删除第0条笔记
36
41
  ```
37
42
 
38
43
  ## 数据库
@@ -55,7 +60,9 @@ klnote --help
55
60
  klnote stk ls # 列出所有股票(表格对齐)
56
61
  klnote stk add <code> [-n name] [-d desc] # 添加股票
57
62
  klnote stk get <identifier> # 查看股票详情
58
- klnote stk delete <identifier> # 删除股票
63
+ klnote stk del <identifier> # 删除股票
64
+ klnote stk get-meta <identifier> # 查看 metadata
65
+ klnote stk set-meta <identifier> # 更新 metadata
59
66
  ```
60
67
 
61
68
  **输出示例:**
@@ -69,7 +76,9 @@ klnote stk delete <identifier> # 删除股票
69
76
  ```
70
77
  klnote note add <stock_identifier> [-s 摘要] [-c 内容] # 添加笔记
71
78
  klnote note ls <stock_identifier> # 列出某只股票的所有笔记
72
- klnote note delete <stock_identifier> <index> # 删除指定笔记
79
+ klnote note read <stock_identifier> <index> # 读取单条笔记
80
+ klnote note edit <stock_identifier> <index> [-c 内容] [-s 摘要] [-e] # 编辑笔记
81
+ klnote note del <stock_identifier> <index> # 删除指定笔记
73
82
  ```
74
83
 
75
84
  ### 高级操作 (admin)
@@ -0,0 +1,3 @@
1
+ __version__ = "0.3.0"
2
+ __author__ = "zimvir"
3
+ __email__ = "zimvir@qq.com"
@@ -1,8 +1,10 @@
1
+
1
2
  import click
2
3
 
3
4
  from prettytable import PrettyTable
4
5
 
5
- from klnote.core import Service
6
+ from .core import Service
7
+ from .core.utils.time_1 import format_iso
6
8
 
7
9
 
8
10
  service = Service()
@@ -35,20 +37,17 @@ def ls():
35
37
  pt.align = "l"
36
38
  pt.horizontal_char = '-'
37
39
  pt.border = False
38
- for s in stocks:
39
-
40
- rows = [
41
- [
42
- s.get("id") or "None",
43
- s.get("code") or "None",
44
- s.get("name") or "None",
45
- s.get("description") or "None",
46
- str(len(s.get("notes") or [])),
47
- s.get("created_at") or "None",
48
- s.get("updated_at") or "None"
49
- ] for s in stocks
50
- ]
51
- pt.add_rows(rows)
40
+
41
+ rows =[[
42
+ s.get("id") or "None",
43
+ s.get("code") or "None",
44
+ s.get("name") or "None",
45
+ s.get("description") or "None",
46
+ str(len(s.get("notes") or [])),
47
+ s.get("created_at") or "None",
48
+ s.get("updated_at") or "None"
49
+ ]for s in stocks]
50
+ pt.add_rows(rows)
52
51
  click.echo(pt)
53
52
 
54
53
 
@@ -83,6 +82,53 @@ def get(identifier):
83
82
 
84
83
  @stk.command()
85
84
  @click.argument('identifier')
85
+ def get_meta(identifier):
86
+ """查看股票 metadata(支持 id/code/name)"""
87
+ stock_ids = service.match_by_identifier(identifier)
88
+ if not stock_ids:
89
+ click.echo("未找到")
90
+ return
91
+ for stock_id in stock_ids:
92
+ s = service.stock_show(stock_id)
93
+ metadata = s.get("metadata", {})
94
+ click.echo(f"ID: {stock_id}")
95
+ for k, v in metadata.items():
96
+ click.echo(f" {k}: {v}")
97
+ click.echo("-"*50)
98
+
99
+
100
+ @stk.command()
101
+ @click.argument('identifier')
102
+ @click.option('--code', default=None, help='股票代码')
103
+ @click.option('--name', default=None, help='股票名称')
104
+ @click.option('--country', default=None, help='国家')
105
+ @click.option('--label', default=None, help='标签, 如中国A股:A')
106
+ def set_meta(identifier, code, name, country, label):
107
+ """更新股票 metadata(支持 id/code/name)"""
108
+ stock_ids = service.match_by_identifier(identifier)
109
+ if not stock_ids:
110
+ click.echo("未找到")
111
+ return
112
+ for stock_id in stock_ids:
113
+ kwargs = {}
114
+ if code is not None:
115
+ kwargs["code"] = code
116
+ if name is not None:
117
+ kwargs["name"] = name
118
+ if country is not None:
119
+ kwargs["country"] = country
120
+ if label is not None:
121
+ kwargs["label"] = label
122
+ if not kwargs:
123
+ click.echo("请至少指定一个要更新的字段")
124
+ continue
125
+ service.update_metadata(stock_id, **kwargs)
126
+ click.echo(f"已更新 metadata")
127
+ click.echo("-"*50)
128
+
129
+
130
+ @stk.command('del')
131
+ @click.argument('identifier')
86
132
  def delete(identifier):
87
133
  """删除股票(支持 id/code/name)"""
88
134
  stock_ids = service.match_by_identifier(identifier)
@@ -131,16 +177,44 @@ def ls(stock_identifier):
131
177
  continue
132
178
  for i, n in enumerate(notes):
133
179
  click.echo(f"索引: {i}")
134
- click.echo(f"创建时间: {n.get('created_at', '')}")
180
+ click.echo(f"创建时间: {format_iso(n.get('created_at', ''))}")
181
+ click.echo(f"更新时间: {format_iso(n.get('updated_at', ''))}")
135
182
  click.echo(f"摘要: {n.get('summary', '')}")
136
183
  if n.get('content'):
137
184
  click.echo(f"内容: \n {n.get('content', '')}")
185
+
138
186
  click.echo("-"*50)
139
187
 
140
188
 
141
189
  @note.command()
142
190
  @click.argument('stock_identifier')
143
191
  @click.argument('index', type=int)
192
+ def read(stock_identifier, index):
193
+ """读取单条笔记(支持 id/code/name)"""
194
+ stock_ids = service.match_by_identifier(stock_identifier)
195
+ if not stock_ids:
196
+ click.echo("未找到股票")
197
+ return
198
+ for stock_id in stock_ids:
199
+ notes = service.note_read(stock_id)
200
+ if index < 0 or index >= len(notes):
201
+ click.echo(f"索引 {index} 不存在")
202
+ continue
203
+ n = notes[index]
204
+ click.echo(f"索引: {index}")
205
+ click.echo(f"创建时间: {format_iso(n.get('created_at', ''))}")
206
+ click.echo(f"更新时间: {format_iso(n.get('updated_at', ''))}")
207
+
208
+ click.echo(f"摘要: {n.get('summary', '')}")
209
+ if n.get('content'):
210
+ click.echo(f"内容:\n {n.get('content', '')}")
211
+
212
+ click.echo("-"*50)
213
+
214
+
215
+ @note.command('del')
216
+ @click.argument('stock_identifier')
217
+ @click.argument('index', type=int)
144
218
  def delete(stock_identifier, index):
145
219
  """删除笔记(支持 id/code/name)"""
146
220
  stock_ids = service.match_by_identifier(stock_identifier)
@@ -152,6 +226,27 @@ def delete(stock_identifier, index):
152
226
  click.echo(f"已删除第 {index} 条笔记")
153
227
  click.echo("-" * 50)
154
228
 
229
+
230
+ @note.command()
231
+ @click.argument('stock_identifier')
232
+ @click.argument('index', type=int)
233
+ @click.option('-s', '--summary', default=None, help='笔记摘要')
234
+ @click.option('-c', '--content', default=None, help='笔记内容')
235
+ @click.option('-e', '--editer', is_flag=True, help='弹出 GUI 编辑器')
236
+ def edit(stock_identifier, index, summary, content, editer):
237
+ """编辑笔记(支持 id/code/name)"""
238
+ if not content and not summary and not editer:
239
+ click.echo("请指定 --content 或 --summary 或 --editer")
240
+ return
241
+ stock_ids = service.match_by_identifier(stock_identifier)
242
+ if not stock_ids:
243
+ click.echo("未找到股票")
244
+ return
245
+ for stock_id in stock_ids:
246
+ service.note_edit(stock_id, index, content=content, summary=summary, editer=editer)
247
+ click.echo(f"已编辑第 {index} 条笔记")
248
+ click.echo("-" * 50)
249
+
155
250
  # ============= admin 组 =============
156
251
  @cli.group()
157
252
  def admin():
@@ -55,6 +55,21 @@ class Service:
55
55
  def stock_add(self, code: str, id: str = None, name: str = None, description: str = None):
56
56
  """新建股票条目"""
57
57
  self.db.create_and_add_new_stock_row(code=code, id=id, name=name, description=description)
58
+ stock_ids = self.match_by_id_code_name(id=id, code=code, name=name)
59
+ if stock_ids:
60
+ self.update_metadata(stock_ids[0], code=code, name=name)
61
+ return self
62
+
63
+ def update_metadata(self, stock_id: str, **kwargs):
64
+ """更新股票的 metadata 字段
65
+
66
+ :param stock_id: 股票 ID
67
+ :param kwargs: 任意 key=value,如 code="000001", name="平安银行", country="CN"
68
+ """
69
+ stock = self.stock_show(stock_id)
70
+ metadata = stock.get("metadata", {})
71
+ metadata.update(kwargs)
72
+ self.db.update(stock_id, "metadata", json.dumps(metadata))
58
73
  return self
59
74
 
60
75
  def stock_delete(self, id: str):
@@ -82,6 +97,10 @@ class Service:
82
97
  "extra": extra or {}
83
98
  }
84
99
 
100
+ def touch_updated_at(self, stock_id: str):
101
+ """更新股票的 updated_at 时间戳"""
102
+ self.db.update(stock_id, "updated_at", get_iso_timestamp())
103
+
85
104
  def note_add(self, stock_id: str, content: str, summary: str = None, extra: dict = None):
86
105
  """给股票添加一条笔记"""
87
106
  stock = self.stock_show(stock_id)
@@ -89,6 +108,7 @@ class Service:
89
108
  note = self._build_note(content, summary, extra)
90
109
  notes.append(note)
91
110
  self.db.update(stock_id, "notes", json.dumps(notes))
111
+ self.touch_updated_at(stock_id)
92
112
  return self
93
113
 
94
114
  def note_delete(self, stock_id: str, index: int):
@@ -97,6 +117,30 @@ class Service:
97
117
  notes = stock.get("notes", [])
98
118
  notes.pop(index)
99
119
  self.db.update(stock_id, "notes", json.dumps(notes))
120
+ self.touch_updated_at(stock_id)
121
+ return self
122
+
123
+ def note_edit(self, stock_id: str, index: int, content: str = None, summary: str = None, editer: bool = False):
124
+ """编辑股票指定索引的笔记"""
125
+ stock = self.stock_show(stock_id)
126
+ notes = stock.get("notes", [])
127
+ if index < 0 or index >= len(notes):
128
+ raise IndexError(f"笔记索引 {index} 不存在")
129
+ if editer:
130
+ from .utils.editor import edit_in_editor
131
+ edited = edit_in_editor(initial_content=notes[index].get("content") or "")
132
+ if edited is None:
133
+ return self
134
+ content = edited
135
+ now = get_iso_timestamp()
136
+ if content is not None:
137
+ notes[index]["content"] = content
138
+ notes[index]["updated_at"] = now
139
+ if summary is not None:
140
+ notes[index]["summary"] = summary
141
+ notes[index]["updated_at"] = now
142
+ self.db.update(stock_id, "notes", json.dumps(notes))
143
+ self.touch_updated_at(stock_id)
100
144
  return self
101
145
 
102
146
  def _read_notes(self, stock_id: str) -> list:
@@ -0,0 +1,21 @@
1
+ import os
2
+ from datetime import datetime, timezone
3
+
4
+
5
+ def get_iso_timestamp(microseconds=True):
6
+ """自定义是否包含微秒"""
7
+ if microseconds:
8
+ return datetime.now(timezone.utc).isoformat(timespec='microseconds').replace('+00:00', 'Z')
9
+ else:
10
+ return datetime.now(timezone.utc).isoformat(timespec='seconds').replace('+00:00', 'Z')
11
+
12
+
13
+ def format_iso(iso_str: str | None) -> str:
14
+ """将 ISO 时间戳解码为本地可读格式,如 '2026-06-09 14:30:00'"""
15
+ if not iso_str:
16
+ return ""
17
+ try:
18
+ dt = datetime.fromisoformat(iso_str.replace('Z', '+00:00'))
19
+ return dt.strftime("%Y-%m-%d %H:%M:%S")
20
+ except (ValueError, TypeError):
21
+ return iso_str
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: klnote
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Stock note management tool
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -38,12 +38,17 @@ pip install klnote
38
38
  klnote stk ls # 列出所有股票
39
39
  klnote stk add 000001 -n 平安银行 # 添加股票
40
40
  klnote stk get 000001 # 查看股票(支持 id/code/name)
41
- klnote stk delete 000001 # 删除股票
41
+ klnote stk del 000001 # 删除股票
42
+ klnote stk get-meta 000001 # 查看 metadata
43
+ klnote stk set-meta 000001 --name 平安银行 --country CN # 更新 metadata
42
44
 
43
45
  # 笔记操作
44
46
  klnote note add 000001 -s "摘要" -c "内容" # 添加笔记
45
47
  klnote note ls 000001 # 列出笔记
46
- klnote note delete 000001 0 # 删除第0条笔记
48
+ klnote note read 000001 0 # 读取第0条笔记
49
+ klnote note edit 000001 0 -c "新内容" # 编辑笔记
50
+ klnote note edit 000001 0 -e # 弹出 GUI 编辑器
51
+ klnote note del 000001 0 # 删除第0条笔记
47
52
  ```
48
53
 
49
54
  ## 数据库
@@ -66,7 +71,9 @@ klnote --help
66
71
  klnote stk ls # 列出所有股票(表格对齐)
67
72
  klnote stk add <code> [-n name] [-d desc] # 添加股票
68
73
  klnote stk get <identifier> # 查看股票详情
69
- klnote stk delete <identifier> # 删除股票
74
+ klnote stk del <identifier> # 删除股票
75
+ klnote stk get-meta <identifier> # 查看 metadata
76
+ klnote stk set-meta <identifier> # 更新 metadata
70
77
  ```
71
78
 
72
79
  **输出示例:**
@@ -80,7 +87,9 @@ klnote stk delete <identifier> # 删除股票
80
87
  ```
81
88
  klnote note add <stock_identifier> [-s 摘要] [-c 内容] # 添加笔记
82
89
  klnote note ls <stock_identifier> # 列出某只股票的所有笔记
83
- klnote note delete <stock_identifier> <index> # 删除指定笔记
90
+ klnote note read <stock_identifier> <index> # 读取单条笔记
91
+ klnote note edit <stock_identifier> <index> [-c 内容] [-s 摘要] [-e] # 编辑笔记
92
+ klnote note del <stock_identifier> <index> # 删除指定笔记
84
93
  ```
85
94
 
86
95
  ### 高级操作 (admin)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "klnote"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Stock note management tool"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -1,3 +0,0 @@
1
- __version__ = "0.2.0"
2
- __author__ = "zimvir"
3
- __email__ = "zimvir@qq.com"
@@ -1,8 +0,0 @@
1
- import os
2
- from datetime import datetime, timezone
3
- def get_iso_timestamp(microseconds=True):
4
- """自定义是否包含微秒"""
5
- if microseconds:
6
- return datetime.now(timezone.utc).isoformat(timespec='microseconds').replace('+00:00', 'Z')
7
- else:
8
- return datetime.now(timezone.utc).isoformat(timespec='seconds').replace('+00:00', 'Z')
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes