sygmail 0.1.0__py3-none-any.whl → 0.1.1__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.
sygmail/cli.py CHANGED
@@ -36,6 +36,11 @@ def build_parser() -> argparse.ArgumentParser:
36
36
  config_set_parser.add_argument("--env", default=".env", help="Path to .env file")
37
37
  config_set_parser.add_argument("--from", dest="from_addr", help="From address")
38
38
  config_set_parser.add_argument("--app-password", help="Gmail app password")
39
+ config_set_parser.add_argument(
40
+ "--use-keyring",
41
+ action="store_true",
42
+ help="Store app password in keyring instead of .env",
43
+ )
39
44
  config_set_parser.add_argument("--to", help="To address")
40
45
  config_set_parser.add_argument("--subject", help="Email subject")
41
46
  config_set_parser.add_argument("--contents", help="Email contents")
@@ -110,6 +115,7 @@ def run_config_set(args: argparse.Namespace) -> int:
110
115
  subject=args.subject,
111
116
  contents=args.contents,
112
117
  attachments_path=args.attachments_path,
118
+ use_keyring=args.use_keyring,
113
119
  persist=True,
114
120
  )
115
121
  return 0
sygmail/client.py CHANGED
@@ -7,8 +7,14 @@ from typing import Iterable, List, Optional
7
7
 
8
8
  import yagmail
9
9
 
10
+ try:
11
+ import keyring
12
+ except ImportError: # pragma: no cover - optional dependency
13
+ keyring = None
14
+
10
15
  DEFAULT_SUBJECT = "Process Completed"
11
16
  DEFAULT_CONTENTS_TEMPLATE = "{script_name} has finished running."
17
+ KEYRING_SERVICE = "sygmail"
12
18
 
13
19
  ENV_KEYS = {
14
20
  "from_addr": "SYGMAIL_FROM",
@@ -80,13 +86,20 @@ class Sygmail:
80
86
  subject: Optional[str] = None,
81
87
  contents: Optional[str] = None,
82
88
  attachments_path: Optional[str] = None,
89
+ use_keyring: bool = False,
83
90
  persist: bool = True,
84
91
  ) -> None:
85
92
  if from_addr is None and from_ is not None:
86
93
  from_addr = from_
87
94
  if from_addr is not None:
88
95
  self.config.from_addr = from_addr
89
- if app_password is not None:
96
+ if app_password is not None and use_keyring:
97
+ resolved_from = from_addr or self.config.from_addr
98
+ if not resolved_from:
99
+ raise ValueError("from_addr is required to store keyring password")
100
+ _store_keyring_password(resolved_from, app_password)
101
+ self.config.app_password = None
102
+ elif app_password is not None:
90
103
  self.config.app_password = app_password
91
104
  if to is not None:
92
105
  self.config.to = to
@@ -117,7 +130,7 @@ class Sygmail:
117
130
  **kwargs,
118
131
  ) -> None:
119
132
  resolved_from = from_addr or from_ or self.config.from_addr
120
- app_password = self.config.app_password
133
+ app_password = self.config.app_password or _get_keyring_password(resolved_from)
121
134
  target = to or self.config.to or resolved_from
122
135
 
123
136
  if not resolved_from or not app_password or not target:
@@ -205,6 +218,21 @@ def _warn_missing_attachments(paths: Iterable[str]) -> None:
205
218
  warnings.warn(f"missing attachments ignored: {joined}", stacklevel=3)
206
219
 
207
220
 
221
+ def _get_keyring_password(username: Optional[str]) -> Optional[str]:
222
+ if not username or keyring is None:
223
+ return None
224
+ try:
225
+ return keyring.get_password(KEYRING_SERVICE, username)
226
+ except Exception:
227
+ return None
228
+
229
+
230
+ def _store_keyring_password(username: str, password: str) -> None:
231
+ if keyring is None:
232
+ raise RuntimeError("keyring is not installed")
233
+ keyring.set_password(KEYRING_SERVICE, username, password)
234
+
235
+
208
236
  def _read_env_file(env_path: str) -> dict:
209
237
  path = Path(env_path)
210
238
  if not path.exists():
@@ -1,12 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sygmail
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Lightweight Gmail notification wrapper
5
- Author-email: Satoshi Nakabayashi <3104nkb@gmail.com>
5
+ Author: Satoshi Nakabayashi
6
6
  Requires-Python: >=3.9
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
9
  Requires-Dist: yagmail
10
+ Provides-Extra: keyring
11
+ Requires-Dist: keyring; extra == "keyring"
10
12
  Dynamic: license-file
11
13
 
12
14
  # sygmail
@@ -30,6 +32,7 @@ pip install sygmail
30
32
 
31
33
  - Python 3.9+
32
34
  - Dependency: `yagmail`
35
+ - Optional: `keyring` (for OS credential store support)
33
36
 
34
37
  ## Quick start
35
38
 
@@ -53,9 +56,31 @@ SYGMAIL_APP_PASSWORD=app-password
53
56
  SYGMAIL_TO=to@example.com
54
57
  SYGMAIL_SUBJECT=Process Completed
55
58
  SYGMAIL_CONTENTS={script_name} has finished running.
56
- SYGMAIL_ATTACHMENTS_PATH=./a/
59
+ SYGMAIL_ATTACHMENTS_PATH=/path/to/folder
57
60
  ```
58
61
 
62
+ If you use keyring, you can leave `SYGMAIL_APP_PASSWORD` empty.
63
+
64
+ Install keyring support:
65
+
66
+ ```
67
+ pip install "sygmail[keyring]"
68
+ ```
69
+
70
+ Keyring usage:
71
+
72
+ ```bash
73
+ python -m sygmail config set --from you@gmail.com --app-password "app-password" --use-keyring
74
+ python -m sygmail send
75
+ ```
76
+
77
+ Notes:
78
+
79
+ - Passwords are stored under the service name `sygmail` with the `from` address as the username.
80
+ - If `SYGMAIL_APP_PASSWORD` is set, it is used first; otherwise keyring is used.
81
+ - Storage uses your OS credential manager (macOS Keychain, Windows Credential Manager, or a Linux keyring).
82
+ - To remove a stored password, delete it from your OS credential manager.
83
+
59
84
  ## Defaults
60
85
 
61
86
  - Subject: `Process Completed`
@@ -98,8 +123,8 @@ python -m sygmail send \
98
123
  --to to@example.com \
99
124
  --subject "Process Completed" \
100
125
  --contents "[sygmail notification]" \
101
- --attachments ./path/to/file \
102
- --attachments-path ./path/to/folder/
126
+ --attachments /path/to/file \
127
+ --attachments-path /path/to/folder/
103
128
  ```
104
129
 
105
130
  - If `--contents` is omitted, CLI uses `[sygmail notification]` without editing `.env`.
@@ -111,10 +136,14 @@ python -m sygmail send
111
136
 
112
137
  python -m sygmail send --subject "Job Done" --contents "[sygmail notification]"
113
138
 
114
- python -m sygmail send --attachments ./a/a.txt ./a/b.txt
139
+ python -m sygmail send --attachments /path/to/file-a.txt /path/to/file-b.txt
140
+
141
+ python -m sygmail send --attachments-path /path/to/folder/
115
142
 
116
143
  python -m sygmail config set --from you@gmail.com --app-password "app-password"
117
144
 
145
+ python -m sygmail config set --from you@gmail.com --app-password "app-password" --use-keyring
146
+
118
147
  python -m sygmail config show
119
148
  ```
120
149
 
@@ -128,7 +157,7 @@ python -m sygmail config set \
128
157
  --to to@example.com \
129
158
  --subject "Process Completed" \
130
159
  --contents "{script_name} has finished running." \
131
- --attachments-path ./a/
160
+ --attachments-path /path/to/folder/
132
161
 
133
162
  python -m sygmail config reset --env .env
134
163
 
@@ -0,0 +1,10 @@
1
+ sygmail/__init__.py,sha256=eGoC5SwKCqe1cYhH1LuNPogYeeOTd636Wlnt70nRRB8,83
2
+ sygmail/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
3
+ sygmail/cli.py,sha256=IQHQOIPWokRyv9ti34FEqKKXCgxiA6DyHr-MuSy5Ozg,5161
4
+ sygmail/client.py,sha256=O4x4CWln4Ju3DRXdm0WTgDyPykE4nm1Jm3y8OlA-N3s,9199
5
+ sygmail-0.1.1.dist-info/licenses/LICENSE,sha256=Oakmmh4GWfLsNpdR0BBBCzmhIf_3rEeIjrB_hvolczk,1076
6
+ sygmail-0.1.1.dist-info/METADATA,sha256=_3lOx0T_IT_PZxb-K22Knw91ZBQlxHkaA07D91NENDk,4694
7
+ sygmail-0.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
+ sygmail-0.1.1.dist-info/entry_points.txt,sha256=_6WdaniMLQk9VEx1MnYSNXMIyt7Csce2-PG6mopUGtI,45
9
+ sygmail-0.1.1.dist-info/top_level.txt,sha256=umbnP2FZMQ4N-tovcks9rAP1oN3XW7wu3TyDWq1rw-8,8
10
+ sygmail-0.1.1.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- sygmail/__init__.py,sha256=eGoC5SwKCqe1cYhH1LuNPogYeeOTd636Wlnt70nRRB8,83
2
- sygmail/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
3
- sygmail/cli.py,sha256=rafYsPIfx55WNnaoO6hgNJZpl0jsxNeZJLxZeIk3hbk,4965
4
- sygmail/client.py,sha256=lLO72zkAIp2MsO-SYh8_HwdOGP7GNEKaLvPBZ8wlqpE,8190
5
- sygmail-0.1.0.dist-info/licenses/LICENSE,sha256=Oakmmh4GWfLsNpdR0BBBCzmhIf_3rEeIjrB_hvolczk,1076
6
- sygmail-0.1.0.dist-info/METADATA,sha256=356x36EyQ6pp3vcC57EjlKNKvzd-FH5frZlIsu77xuk,3755
7
- sygmail-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
- sygmail-0.1.0.dist-info/entry_points.txt,sha256=_6WdaniMLQk9VEx1MnYSNXMIyt7Csce2-PG6mopUGtI,45
9
- sygmail-0.1.0.dist-info/top_level.txt,sha256=umbnP2FZMQ4N-tovcks9rAP1oN3XW7wu3TyDWq1rw-8,8
10
- sygmail-0.1.0.dist-info/RECORD,,