absfuyu 5.0.0__py3-none-any.whl → 6.1.2__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.

Potentially problematic release.


This version of absfuyu might be problematic. Click here for more details.

Files changed (103) hide show
  1. absfuyu/__init__.py +5 -3
  2. absfuyu/__main__.py +3 -3
  3. absfuyu/cli/__init__.py +13 -2
  4. absfuyu/cli/audio_group.py +98 -0
  5. absfuyu/cli/color.py +30 -14
  6. absfuyu/cli/config_group.py +9 -2
  7. absfuyu/cli/do_group.py +23 -6
  8. absfuyu/cli/game_group.py +27 -2
  9. absfuyu/cli/tool_group.py +81 -11
  10. absfuyu/config/__init__.py +3 -3
  11. absfuyu/core/__init__.py +12 -8
  12. absfuyu/core/baseclass.py +929 -96
  13. absfuyu/core/baseclass2.py +44 -3
  14. absfuyu/core/decorator.py +70 -4
  15. absfuyu/core/docstring.py +64 -41
  16. absfuyu/core/dummy_cli.py +3 -3
  17. absfuyu/core/dummy_func.py +19 -6
  18. absfuyu/dxt/__init__.py +2 -2
  19. absfuyu/dxt/base_type.py +93 -0
  20. absfuyu/dxt/dictext.py +204 -16
  21. absfuyu/dxt/dxt_support.py +2 -2
  22. absfuyu/dxt/intext.py +151 -34
  23. absfuyu/dxt/listext.py +969 -127
  24. absfuyu/dxt/strext.py +77 -17
  25. absfuyu/extra/__init__.py +2 -2
  26. absfuyu/extra/audio/__init__.py +8 -0
  27. absfuyu/extra/audio/_util.py +57 -0
  28. absfuyu/extra/audio/convert.py +192 -0
  29. absfuyu/extra/audio/lossless.py +281 -0
  30. absfuyu/extra/beautiful.py +3 -2
  31. absfuyu/extra/da/__init__.py +72 -0
  32. absfuyu/extra/da/dadf.py +1600 -0
  33. absfuyu/extra/da/dadf_base.py +186 -0
  34. absfuyu/extra/da/df_func.py +181 -0
  35. absfuyu/extra/da/mplt.py +219 -0
  36. absfuyu/extra/ggapi/__init__.py +8 -0
  37. absfuyu/extra/ggapi/gdrive.py +223 -0
  38. absfuyu/extra/ggapi/glicense.py +148 -0
  39. absfuyu/extra/ggapi/glicense_df.py +186 -0
  40. absfuyu/extra/ggapi/gsheet.py +88 -0
  41. absfuyu/extra/img/__init__.py +30 -0
  42. absfuyu/extra/img/converter.py +402 -0
  43. absfuyu/extra/img/dup_check.py +291 -0
  44. absfuyu/extra/pdf.py +87 -0
  45. absfuyu/extra/rclone.py +253 -0
  46. absfuyu/extra/xml.py +90 -0
  47. absfuyu/fun/__init__.py +7 -20
  48. absfuyu/fun/rubik.py +442 -0
  49. absfuyu/fun/tarot.py +2 -2
  50. absfuyu/game/__init__.py +2 -2
  51. absfuyu/game/game_stat.py +2 -2
  52. absfuyu/game/schulte.py +78 -0
  53. absfuyu/game/sudoku.py +2 -2
  54. absfuyu/game/tictactoe.py +2 -3
  55. absfuyu/game/wordle.py +6 -4
  56. absfuyu/general/__init__.py +4 -4
  57. absfuyu/general/content.py +4 -4
  58. absfuyu/general/human.py +2 -2
  59. absfuyu/general/resrel.py +213 -0
  60. absfuyu/general/shape.py +3 -8
  61. absfuyu/general/tax.py +344 -0
  62. absfuyu/logger.py +806 -59
  63. absfuyu/numbers/__init__.py +13 -0
  64. absfuyu/numbers/number_to_word.py +321 -0
  65. absfuyu/numbers/shorten_number.py +303 -0
  66. absfuyu/numbers/time_duration.py +217 -0
  67. absfuyu/pkg_data/__init__.py +2 -2
  68. absfuyu/pkg_data/deprecated.py +2 -2
  69. absfuyu/pkg_data/logo.py +1462 -0
  70. absfuyu/sort.py +4 -4
  71. absfuyu/tools/__init__.py +28 -2
  72. absfuyu/tools/checksum.py +144 -9
  73. absfuyu/tools/converter.py +120 -34
  74. absfuyu/tools/generator.py +461 -0
  75. absfuyu/tools/inspector.py +752 -0
  76. absfuyu/tools/keygen.py +2 -2
  77. absfuyu/tools/obfuscator.py +47 -9
  78. absfuyu/tools/passwordlib.py +89 -25
  79. absfuyu/tools/shutdownizer.py +3 -8
  80. absfuyu/tools/sw.py +718 -0
  81. absfuyu/tools/web.py +10 -13
  82. absfuyu/typings.py +138 -0
  83. absfuyu/util/__init__.py +114 -6
  84. absfuyu/util/api.py +41 -18
  85. absfuyu/util/cli.py +119 -0
  86. absfuyu/util/gui.py +91 -0
  87. absfuyu/util/json_method.py +43 -14
  88. absfuyu/util/lunar.py +2 -2
  89. absfuyu/util/package.py +124 -0
  90. absfuyu/util/path.py +702 -82
  91. absfuyu/util/performance.py +122 -7
  92. absfuyu/util/shorten_number.py +244 -21
  93. absfuyu/util/text_table.py +481 -0
  94. absfuyu/util/zipped.py +8 -7
  95. absfuyu/version.py +79 -59
  96. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/METADATA +52 -11
  97. absfuyu-6.1.2.dist-info/RECORD +105 -0
  98. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/WHEEL +1 -1
  99. absfuyu/extra/data_analysis.py +0 -1078
  100. absfuyu/general/generator.py +0 -303
  101. absfuyu-5.0.0.dist-info/RECORD +0 -68
  102. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/entry_points.txt +0 -0
  103. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,223 @@
1
+ """
2
+ Absfuyu: Google related
3
+ -----------------------
4
+ Google Drive downloader
5
+
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
+ """
9
+
10
+ # Module level
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["GoogleDriveFile", "GoogleDriveClient"]
13
+
14
+
15
+ # Library
16
+ # ---------------------------------------------------------------------------
17
+ import io
18
+ import mimetypes
19
+ from dataclasses import dataclass
20
+ from pathlib import Path
21
+ from typing import ClassVar, cast
22
+
23
+ from absfuyu.core.baseclass import BaseClass
24
+
25
+ try:
26
+ import requests
27
+ from google.oauth2 import service_account
28
+ from googleapiclient.discovery import Resource, build
29
+ from googleapiclient.http import MediaIoBaseDownload
30
+ except ImportError:
31
+ from subprocess import run
32
+
33
+ from absfuyu.config import ABSFUYU_CONFIG
34
+
35
+ if ABSFUYU_CONFIG._get_setting("auto-install-extra").value: # type: ignore
36
+ cmd = "python -m pip install -U absfuyu[ggapi]".split()
37
+ run(cmd)
38
+ else:
39
+ raise SystemExit("This feature is in absfuyu[ggapi] package") # noqa: B904
40
+
41
+
42
+ # Class
43
+ # ---------------------------------------------------------------------------
44
+ @dataclass(slots=True)
45
+ class DriveFileMeta:
46
+ id: str
47
+ name: str
48
+ mime_type: str
49
+
50
+
51
+ class GoogleDriveClient(BaseClass):
52
+ SCOPES: ClassVar = [
53
+ "https://www.googleapis.com/auth/drive",
54
+ "https://www.googleapis.com/auth/spreadsheets",
55
+ ]
56
+
57
+ EXPORT_MIME_MAP: ClassVar = {
58
+ "application/vnd.google-apps.spreadsheet": (
59
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
60
+ ".xlsx",
61
+ ),
62
+ "application/vnd.google-apps.document": (
63
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
64
+ ".docx",
65
+ ),
66
+ "application/vnd.google-apps.presentation": (
67
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
68
+ ".pptx",
69
+ ),
70
+ }
71
+
72
+ def __init__(self, service_account_data: str | Path | dict[str, str]) -> None:
73
+ """
74
+ Google drive instance
75
+
76
+ Parameters
77
+ ----------
78
+ service_account_data : str | Path | dict[str, str]
79
+ Path to ``service_account.json`` file or .json loaded data
80
+ """
81
+ if isinstance(service_account_data, str):
82
+ self.credentials = service_account.Credentials.from_service_account_file(
83
+ service_account_data,
84
+ scopes=self.SCOPES,
85
+ )
86
+ elif isinstance(service_account_data, Path):
87
+ self.credentials = service_account.Credentials.from_service_account_file(
88
+ str(service_account_data.resolve()),
89
+ scopes=self.SCOPES,
90
+ )
91
+ else:
92
+ self.credentials = service_account.Credentials.from_service_account_info(
93
+ service_account_data,
94
+ scopes=self.SCOPES,
95
+ )
96
+
97
+ self.drive = cast(Resource, build("drive", "v3", credentials=self.credentials))
98
+ self.sheets = cast(Resource, build("sheets", "v4", credentials=self.credentials))
99
+
100
+ # Metadata
101
+ # -----------------------------
102
+ def get_metadata(self, file_id: str) -> DriveFileMeta:
103
+ data = self.drive.files().get(fileId=file_id, fields="id,name,mimeType").execute()
104
+ return DriveFileMeta(
105
+ id=data["id"],
106
+ name=data["name"],
107
+ mime_type=data["mimeType"],
108
+ )
109
+
110
+ # Download / Export
111
+ # -----------------------------
112
+ def download_bytes(self, file_id: str) -> bytes:
113
+ request = self.drive.files().get_media(fileId=file_id)
114
+ return self._download(request)
115
+
116
+ def export_bytes(self, file_id: str, mime_type: str) -> bytes:
117
+ request = self.drive.files().export_media(
118
+ fileId=file_id,
119
+ mimeType=mime_type,
120
+ )
121
+ return self._download(request)
122
+
123
+ def _download(self, request) -> bytes:
124
+ buf = io.BytesIO()
125
+ downloader = MediaIoBaseDownload(buf, request)
126
+ done = False
127
+ while not done:
128
+ _, done = downloader.next_chunk()
129
+ buf.seek(0)
130
+ return buf.read()
131
+
132
+
133
+ class GoogleDriveFile:
134
+ """
135
+ Google drive file to download
136
+
137
+
138
+ Example:
139
+ --------
140
+ >>> client = GoogleDriveClient("service_account.json")
141
+ >>> gfile = GoogleDriveFile(file_id=<id>, client=client)
142
+ >>> gfile.download(<dir>)
143
+ """
144
+
145
+ PUBLIC_URL = "https://drive.google.com/uc?export=download"
146
+
147
+ def __init__(
148
+ self,
149
+ file_id: str,
150
+ *,
151
+ client: GoogleDriveClient | None = None,
152
+ timeout: float = 30.0,
153
+ ) -> None:
154
+ self.file_id = file_id
155
+ self.client = client
156
+ self.timeout = timeout
157
+ self.session = requests.Session()
158
+
159
+ # Public API
160
+ # -----------------------------
161
+ def download(self, directory: str | Path = ".") -> Path:
162
+ """
163
+ Download file with auto-detected name and extension.
164
+ Returns saved file path.
165
+ """
166
+ directory = Path(directory)
167
+ directory.mkdir(parents=True, exist_ok=True)
168
+
169
+ if self.client:
170
+ return self._download_private(directory)
171
+
172
+ return self._download_public(directory)
173
+
174
+ # Private (Service Account)
175
+ # -----------------------------
176
+ def _download_private(self, directory: Path) -> Path:
177
+ meta = self.client.get_metadata(self.file_id)
178
+
179
+ # Google Docs -> export
180
+ if meta.mime_type in self.client.EXPORT_MIME_MAP:
181
+ export_mime, ext = self.client.EXPORT_MIME_MAP[meta.mime_type]
182
+ data = self.client.export_bytes(self.file_id, export_mime)
183
+ filename = meta.name + ext
184
+ else:
185
+ data = self.client.download_bytes(self.file_id)
186
+ ext = mimetypes.guess_extension(meta.mime_type) or ""
187
+ filename = meta.name if Path(meta.name).suffix else meta.name + ext
188
+
189
+ path = directory / filename
190
+ path.write_bytes(data)
191
+ return path
192
+
193
+ # Public (no auth)
194
+ # -----------------------------
195
+ def _download_public(self, directory: Path) -> Path:
196
+ response = self.session.get(
197
+ self.PUBLIC_URL,
198
+ params={"id": self.file_id},
199
+ timeout=self.timeout,
200
+ )
201
+
202
+ # confirmation token
203
+ for k, v in response.cookies.items():
204
+ if k.startswith("download_warning"):
205
+ response = self.session.get(
206
+ self.PUBLIC_URL,
207
+ params={"id": self.file_id, "confirm": v},
208
+ timeout=self.timeout,
209
+ )
210
+ break
211
+
212
+ response.raise_for_status()
213
+
214
+ # filename from header (if present)
215
+ cd = response.headers.get("content-disposition", "")
216
+ filename = "downloaded_file"
217
+
218
+ if "filename=" in cd:
219
+ filename = cd.split("filename=")[-1].strip('"')
220
+
221
+ path = directory / filename
222
+ path.write_bytes(response.content)
223
+ return path
@@ -0,0 +1,148 @@
1
+ """
2
+ Absfuyu: Google related
3
+ -----------------------
4
+ Google Online license from sheet
5
+
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
+ """
9
+
10
+ # Module level
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["GGSheetOnlineLicenseSystem"]
13
+
14
+
15
+ # Library
16
+ # ---------------------------------------------------------------------------
17
+ from dataclasses import dataclass
18
+ from datetime import datetime
19
+ from functools import cached_property
20
+ from pathlib import Path
21
+ from typing import Any, Self, cast
22
+
23
+ from absfuyu.core.baseclass import BaseClass
24
+ from absfuyu.extra.ggapi.gsheet import GoogleSheet
25
+ from absfuyu.tools.sw import HWIDgen, get_system_info
26
+
27
+ # Var
28
+ # ---------------------------------------------------------------------------
29
+ type GoogleSheetClient = GoogleSheet
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class LicenseEntry:
34
+ id: int
35
+ hwid: str
36
+ name: str
37
+ os: str
38
+ date: int | datetime
39
+ active: bool
40
+
41
+ def to_row(self) -> list:
42
+ return [self.id, self.hwid, self.name, self.os, self.date, self.active]
43
+
44
+
45
+ # Class
46
+ # ---------------------------------------------------------------------------
47
+ class GGSheetOnlineLicenseSystem(BaseClass):
48
+ def __init__(self, google_client: GoogleSheetClient, sheet_id: str, sheet_name: str = "Sheet1") -> None:
49
+ """
50
+ Google sheet online license system instance
51
+
52
+ Parameters
53
+ ----------
54
+ google_client : GoogleSheetClient
55
+ Google sheet client
56
+
57
+ sheet_id : str
58
+ Sheet ID
59
+
60
+ sheet_name : str, optional
61
+ Sheet name, by default ``"Sheet1"``
62
+ """
63
+ self.client = google_client
64
+ self._sheet_id = sheet_id
65
+ self._sheet_name = sheet_name
66
+
67
+ # Variable
68
+ self._df: list[LicenseEntry] | None = None
69
+ try:
70
+ self._load()
71
+ except Exception:
72
+ pass
73
+
74
+ # Property
75
+ @cached_property
76
+ def hwid(self) -> str:
77
+ return HWIDgen.generate()
78
+
79
+ # Support
80
+ def _load(self) -> None:
81
+ df = cast(list[list[Any]], self.client.read_sheet(spreadsheet_id=self._sheet_id, sheet_name=self._sheet_name))
82
+ data = [LicenseEntry(*x) for x in df]
83
+ self._df = data
84
+
85
+ def _gather_system_info(self) -> list[str]:
86
+ self._sysif = get_system_info()
87
+ return [self.hwid, self._sysif.system_name, self._sysif.os_type]
88
+
89
+ def _make_device_entry(self) -> list:
90
+ df = self._df
91
+ row = [int(df[-1].id) + 1]
92
+ row.extend(self._gather_system_info())
93
+ row.extend([datetime.now().strftime("%d/%m/%Y"), False])
94
+ return row
95
+
96
+ def _get_device_online(self):
97
+ """Get ID of device in sheet, create entry if None"""
98
+ df = self._df
99
+
100
+ for x in df:
101
+ if x.hwid == self.hwid:
102
+ return x.id
103
+
104
+ has_updated = False # safe guard
105
+ if not has_updated:
106
+ new_entry = self._make_device_entry()
107
+ self.client.append_rows(self._sheet_id, self._sheet_name, [new_entry])
108
+ has_updated = True
109
+ self._load()
110
+ return new_entry[0]
111
+
112
+ # Main
113
+ def license_check(self) -> None:
114
+ """Check if activated in sheet"""
115
+ df = self._df
116
+ id = self._get_device_online()
117
+
118
+ try:
119
+ for x in df:
120
+ if x.id == id:
121
+ active_status = x.active
122
+ if not active_status:
123
+ raise SystemExit("Not activated")
124
+ return None
125
+ except Exception:
126
+ raise SystemExit("Not activated")
127
+
128
+ # Classmethod
129
+ @classmethod
130
+ def from_service_account(
131
+ cls, service_account_data: str | Path | dict[str, str], sheet_id: str, sheet_name: str = "Sheet1"
132
+ ) -> Self:
133
+ """
134
+ Google sheet online license system instance
135
+
136
+ Parameters
137
+ ----------
138
+ service_account_data : str | Path | dict[str, str]
139
+ Path to ``service_account.json`` file or .json loaded data
140
+
141
+ sheet_id : str
142
+ Sheet ID
143
+
144
+ sheet_name : str, optional
145
+ Sheet name, by default ``"Sheet1"``
146
+ """
147
+ client = GoogleSheet(service_account_data)
148
+ return cls(client, sheet_id, sheet_name)
@@ -0,0 +1,186 @@
1
+ """
2
+ Absfuyu: Google related
3
+ -----------------------
4
+ Google Online license from sheet (DataFrame version)
5
+
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
+ """
9
+
10
+ # Module level
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["GGSheetOnlineLicenseSystemDF"]
13
+
14
+
15
+ # Library
16
+ # ---------------------------------------------------------------------------
17
+ from datetime import datetime
18
+ from enum import StrEnum
19
+ from pathlib import Path
20
+ from typing import Self, override
21
+
22
+ import pandas as pd
23
+
24
+ from absfuyu.extra.ggapi.glicense import GGSheetOnlineLicenseSystem
25
+ from absfuyu.extra.ggapi.gsheet import GoogleSheet
26
+
27
+ # Var
28
+ # ---------------------------------------------------------------------------
29
+ AVAILABLE_COLUMNS = ["stt", "hwid", "pc_name", "os", "date", "active"]
30
+
31
+
32
+ class DF_COL(StrEnum):
33
+ TT = "stt"
34
+ HWID = "hwid"
35
+ NAME = "pc_name"
36
+ OS = "OS"
37
+ DATE = "date"
38
+ ACTIVE = "active"
39
+
40
+
41
+ # Class
42
+ # ---------------------------------------------------------------------------
43
+ class GoogleSheetDF(GoogleSheet):
44
+ # Sheets — READ (NO DOWNLOAD)
45
+ # ------------------------------------------------------------------
46
+ def read_sheet_as_dataframe(
47
+ self,
48
+ spreadsheet_id: str,
49
+ sheet_name: str = "Sheet1",
50
+ header: bool = True,
51
+ ) -> pd.DataFrame:
52
+ """
53
+ Read Google Sheet directly into DataFrame.
54
+ """
55
+ result = (
56
+ self.sheets.spreadsheets()
57
+ .values()
58
+ .get(
59
+ spreadsheetId=spreadsheet_id,
60
+ range=sheet_name,
61
+ valueRenderOption="UNFORMATTED_VALUE",
62
+ )
63
+ .execute()
64
+ )
65
+
66
+ values = result.get("values", [])
67
+ if not values:
68
+ return pd.DataFrame()
69
+
70
+ if header:
71
+ return pd.DataFrame(values[1:], columns=values[0])
72
+
73
+ return pd.DataFrame(values)
74
+
75
+ # Sheets — APPEND
76
+ # ------------------------------------------------------------------
77
+ def append_dataframe(
78
+ self,
79
+ spreadsheet_id: str,
80
+ range_: str,
81
+ df: pd.DataFrame,
82
+ ) -> None:
83
+ self.append_rows(
84
+ spreadsheet_id,
85
+ range_,
86
+ df.astype(object).where(pd.notnull(df), "").values.tolist(),
87
+ )
88
+
89
+
90
+ type GoogleSheetClient = GoogleSheetDF
91
+
92
+
93
+ class GGSheetOnlineLicenseSystemDF(GGSheetOnlineLicenseSystem):
94
+ def __init__(self, google_client: GoogleSheetClient, sheet_id: str, sheet_name: str = "Sheet1") -> None:
95
+ """
96
+ Google sheet online license system instance
97
+
98
+ Parameters
99
+ ----------
100
+ google_client : GoogleSheetClient
101
+ Google sheet client
102
+
103
+ sheet_id : str
104
+ Sheet ID
105
+
106
+ sheet_name : str, optional
107
+ Sheet name, by default ``"Sheet1"``
108
+ """
109
+ self.client = google_client
110
+ self._sheet_id = sheet_id
111
+ self._sheet_name = sheet_name
112
+
113
+ # Variable
114
+ self._df: pd.DataFrame | None = None
115
+ try:
116
+ self._load()
117
+ except Exception:
118
+ pass
119
+
120
+ # Support
121
+ @override
122
+ def _load(self) -> None:
123
+ df = self.client.read_sheet_as_dataframe(spreadsheet_id=self._sheet_id, sheet_name=self._sheet_name)
124
+ self._df = df
125
+
126
+ @override
127
+ def _make_device_entry(self) -> list:
128
+ df = self._df
129
+ row = [int(df[DF_COL.TT].max()) + 1]
130
+ row.extend(self._gather_system_info())
131
+ row.extend([datetime.now().strftime("%d/%m/%Y"), False])
132
+ return row
133
+
134
+ @override
135
+ def _get_device_online(self):
136
+ """Get ID of device in sheet, create entry if None"""
137
+ df = self._df
138
+ id = df[df[DF_COL.HWID] == self.hwid][DF_COL.TT]
139
+
140
+ has_updated = False # safe guard
141
+ if id is None or id.empty:
142
+ if not has_updated:
143
+ new_entry = self._make_device_entry()
144
+ self.client.append_rows(self._sheet_id, self._sheet_name, [new_entry])
145
+ has_updated = True
146
+ self._load()
147
+ return new_entry[0]
148
+
149
+ return id.to_list()[0]
150
+
151
+ # Main
152
+ @override
153
+ def license_check(self) -> None:
154
+ """Check if activated in sheet"""
155
+ df = self._df
156
+ id = self._get_device_online()
157
+
158
+ try:
159
+ active_status = df[df[DF_COL.TT] == id][DF_COL.ACTIVE].to_list()[0]
160
+ if not active_status:
161
+ raise SystemExit("Not activated")
162
+ return None
163
+ except Exception:
164
+ raise SystemExit("Not activated")
165
+
166
+ # Classmethod
167
+ @classmethod
168
+ def from_service_account(
169
+ cls, service_account_data: str | Path | dict[str, str], sheet_id: str, sheet_name: str = "Sheet1"
170
+ ) -> Self:
171
+ """
172
+ Google sheet online license system instance
173
+
174
+ Parameters
175
+ ----------
176
+ service_account_data : str | Path | dict[str, str]
177
+ Path to ``service_account.json`` file or .json loaded data
178
+
179
+ sheet_id : str
180
+ Sheet ID
181
+
182
+ sheet_name : str, optional
183
+ Sheet name, by default ``"Sheet1"``
184
+ """
185
+ client = GoogleSheetDF(service_account_data)
186
+ return cls(client, sheet_id, sheet_name)
@@ -0,0 +1,88 @@
1
+ """
2
+ Absfuyu: Google related
3
+ -----------------------
4
+ Google Sheet
5
+
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
+ """
9
+
10
+ # Module level
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["GoogleSheet"]
13
+
14
+
15
+ # Library
16
+ # ---------------------------------------------------------------------------
17
+ from typing import Any
18
+
19
+ from absfuyu.extra.ggapi.gdrive import GoogleDriveClient
20
+
21
+
22
+ # Class
23
+ # ---------------------------------------------------------------------------
24
+ class GoogleSheet(GoogleDriveClient):
25
+ # Sheets — READ (NO DOWNLOAD)
26
+ # ------------------------------------------------------------------
27
+ def read_sheet(
28
+ self,
29
+ spreadsheet_id: str,
30
+ sheet_name: str = "Sheet1",
31
+ header: bool = True,
32
+ ) -> list[list[Any]]:
33
+ """
34
+ Read Google Sheet directly.
35
+ """
36
+ result = (
37
+ self.sheets.spreadsheets()
38
+ .values()
39
+ .get(
40
+ spreadsheetId=spreadsheet_id,
41
+ range=sheet_name,
42
+ valueRenderOption="UNFORMATTED_VALUE",
43
+ )
44
+ .execute()
45
+ )
46
+
47
+ values = result.get("values", [])
48
+ if values and header:
49
+ return values[1:]
50
+
51
+ return values
52
+
53
+ # Sheets — APPEND
54
+ # ------------------------------------------------------------------
55
+ def append_rows(
56
+ self,
57
+ spreadsheet_id: str,
58
+ range_: str,
59
+ rows: list[list],
60
+ ) -> None:
61
+ """
62
+ Append rows to Google Sheet.
63
+ """
64
+ self.sheets.spreadsheets().values().append(
65
+ spreadsheetId=spreadsheet_id,
66
+ range=range_,
67
+ valueInputOption="USER_ENTERED",
68
+ insertDataOption="INSERT_ROWS",
69
+ body={"values": rows},
70
+ ).execute()
71
+
72
+ # Sheets — UPDATE
73
+ # ------------------------------------------------------------------
74
+ def update_range(
75
+ self,
76
+ spreadsheet_id: str,
77
+ range_: str,
78
+ values: list[list],
79
+ ) -> None:
80
+ """
81
+ Update an existing cell range.
82
+ """
83
+ self.sheets.spreadsheets().values().update(
84
+ spreadsheetId=spreadsheet_id,
85
+ range=range_,
86
+ valueInputOption="USER_ENTERED",
87
+ body={"values": values},
88
+ ).execute()
@@ -0,0 +1,30 @@
1
+ """
2
+ Absfuyu: Image
3
+ --------------
4
+ Image related
5
+
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
+ """
9
+
10
+ # Module level
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["ImgConverter"]
13
+
14
+
15
+ # Library
16
+ # ---------------------------------------------------------------------------
17
+ try:
18
+ from PIL import Image
19
+ except ImportError:
20
+ from subprocess import run
21
+
22
+ from absfuyu.config import ABSFUYU_CONFIG
23
+
24
+ if ABSFUYU_CONFIG._get_setting("auto-install-extra").value: # type: ignore
25
+ cmd = "python -m pip install -U absfuyu[pic]".split()
26
+ run(cmd)
27
+ else:
28
+ raise SystemExit("This feature is in absfuyu[pic] package") # noqa: B904
29
+
30
+ from absfuyu.extra.img.converter import ImgConverter