rdxz2-utill 0.1.2__py3-none-any.whl → 0.1.4__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 rdxz2-utill might be problematic. Click here for more details.

utill/my_gdrive.py ADDED
@@ -0,0 +1,195 @@
1
+ from google.auth import default
2
+ from googleapiclient.discovery import build
3
+ from googleapiclient.http import MediaFileUpload
4
+ from googleapiclient.http import MediaIoBaseDownload
5
+ from humanize import naturalsize
6
+ import enum
7
+ import logging
8
+ import os
9
+
10
+
11
+ log = logging.getLogger(__name__)
12
+
13
+
14
+ class Role(enum.StrEnum):
15
+ READER = "reader"
16
+ WRITER = "writer"
17
+ COMMENTER = "commenter"
18
+ OWNER = "owner"
19
+
20
+
21
+ class GDrive:
22
+ """
23
+ Custom hook for Google Drive integration in Airflow.
24
+ This hook can be used to interact with Google Drive APIs.
25
+ """
26
+
27
+ def __init__(self):
28
+ credentials, project = default(
29
+ scopes=[
30
+ "https://www.googleapis.com/auth/drive",
31
+ "https://www.googleapis.com/auth/drive.file",
32
+ ]
33
+ )
34
+ drive_service = build("drive", "v3", credentials=credentials)
35
+ self.connection = drive_service
36
+
37
+ # region Folder operations
38
+
39
+ def get_folder_by_name(self, *, parent_folder_id: str, name: str) -> str | None:
40
+ """
41
+ Retrieves a folder by its name within a specified Google Drive folder.
42
+ :param folder_id: The ID of the parent folder to search in.
43
+ :param name: The name of the folder to find.
44
+ :return: The ID of the found folder or None if not found.
45
+ """
46
+ query = f"'{parent_folder_id}' in parents and name='{name}' and mimeType='application/vnd.google-apps.folder' and trashed=false"
47
+ results = (
48
+ self.connection.files()
49
+ .list(q=query, fields="files(id)", supportsAllDrives=True)
50
+ .execute()
51
+ )
52
+ items = results.get("files", [])
53
+
54
+ return items[0]["id"] if items else None
55
+
56
+ def create_folder(
57
+ self, folder_name: str, parent_folder_id: str | None = None
58
+ ) -> str:
59
+ """
60
+ Creates a folder in Google Drive.
61
+ :param folder_name: The name of the folder to create.
62
+ :param parent_folder_id: The ID of the parent folder (optional).
63
+ :return: The ID of the created folder.
64
+ """
65
+ file_metadata = {
66
+ "name": folder_name,
67
+ "mimeType": "application/vnd.google-apps.folder",
68
+ }
69
+ if parent_folder_id:
70
+ file_metadata["parents"] = [parent_folder_id]
71
+
72
+ file = (
73
+ self.connection.files()
74
+ .create(body=file_metadata, fields="id", supportsAllDrives=True)
75
+ .execute()
76
+ )
77
+ log.debug(
78
+ f"Folder {folder_name} created under {self.generate_gdrive_folder_url(parent_folder_id)}"
79
+ )
80
+ return file.get("id")
81
+
82
+ def grant_folder_access(
83
+ self,
84
+ folder_id: str,
85
+ email: str,
86
+ role: Role = Role.READER,
87
+ send_notification_email: bool = False,
88
+ ):
89
+ """
90
+ Grants access to a Google Drive folder to a user by email.
91
+ :param folder_id: The ID of the folder to grant access to.
92
+ :param email: The email address of the user to grant access to.
93
+ :param role: The role to assign (reader, writer, commenter, owner).
94
+ """
95
+ self.connection.permissions().create(
96
+ fileId=folder_id,
97
+ body={
98
+ "type": "user",
99
+ "role": role,
100
+ "emailAddress": email,
101
+ },
102
+ sendNotificationEmail=send_notification_email,
103
+ supportsAllDrives=True,
104
+ ).execute()
105
+ log.debug(
106
+ f"Granted {role} access to {email} for folder {self.generate_gdrive_folder_url(folder_id)}"
107
+ )
108
+
109
+ # endregion
110
+
111
+ # region File operations
112
+
113
+ def get_file(self, file_id: str):
114
+ raise NotImplementedError()
115
+
116
+ def list_files(self, folder_id: str, mime_type: str | None = None):
117
+ """
118
+ Lists files in a specified Google Drive folder.
119
+ :param folder_id: The ID of the folder to search in.
120
+ :param mime_type: Optional MIME type to filter files by.
121
+ :return: A list of files in the specified folder.
122
+ """
123
+ query = f"'{folder_id}' in parents and trashed=false"
124
+ if mime_type:
125
+ query += f" and mimeType='{mime_type}'"
126
+
127
+ results = (
128
+ self.connection.files()
129
+ .list(q=query, fields="files(id, name)", supportsAllDrives=True)
130
+ .execute()
131
+ )
132
+ return results.get("files", [])
133
+
134
+ def upload_file(
135
+ self, src_filepath: str, folder_id: str, mime_type: str | None = None
136
+ ):
137
+ media = MediaFileUpload(src_filepath, mimetype=mime_type, resumable=True)
138
+ request = self.connection.files().create(
139
+ body={"name": os.path.basename(src_filepath), "parents": [folder_id]},
140
+ media_body=media,
141
+ supportsAllDrives=True,
142
+ )
143
+ response = None
144
+ while response is None:
145
+ status, response = request.next_chunk()
146
+ if status:
147
+ log.debug(f"Upload progress: {int(status.progress() * 100)}%")
148
+
149
+ log.debug(
150
+ f"File {src_filepath} [{naturalsize(os.path.getsize(src_filepath))}] uploaded to {self.generate_gdrive_folder_url(folder_id)}"
151
+ )
152
+
153
+ def download_gdrive_file(self, file_id: str, dst_filepath: str):
154
+ request = self.connection.files().get_media(
155
+ fileId=file_id, supportsAllDrives=True
156
+ )
157
+
158
+ # Stream directly to disk
159
+ with open(dst_filepath, "wb") as f:
160
+ downloader = MediaIoBaseDownload(f, request)
161
+ done = False
162
+ while not done:
163
+ _, done = downloader.next_chunk()
164
+
165
+ log.debug(
166
+ f"GDrive file {file_id} downloaded to {dst_filepath} with size {naturalsize(os.path.getsize(dst_filepath))}"
167
+ )
168
+
169
+ def delete(self, file_id: str):
170
+ """
171
+ Deletes a file from Google Drive using its ID.
172
+ :param file_id: The ID of the file to delete.
173
+ """
174
+ self.connection.files().delete(fileId=file_id, supportsAllDrives=True).execute()
175
+ log.debug(f"GDrive file with ID {file_id} deleted")
176
+
177
+ # endregion
178
+
179
+ # region Other utilieis
180
+
181
+ @staticmethod
182
+ def generate_gdrive_folder_url(folder_id: str):
183
+ """
184
+ Generate a valid GDrive folder URL
185
+
186
+ Args:
187
+ folder_id (str): Folder ID
188
+
189
+ Returns:
190
+ str: A valid GDrive folder URL
191
+ """
192
+
193
+ return f"https://drive.google.com/drive/folders/{folder_id}"
194
+
195
+ # endregion
utill/my_input.py CHANGED
@@ -1,11 +1,15 @@
1
1
  from .my_style import italic
2
2
 
3
3
 
4
- def ask_yes_no(prompt: str = 'Continue?', yes_strings: tuple[str] = ('y', ), throw_if_no: bool = False) -> str:
5
- prompt = f'{prompt} ({yes_strings[0]}/no) : '
6
- yes = input(f'\n{italic(prompt)}') in yes_strings
4
+ def ask_yes_no(
5
+ prompt: str = "Continue?",
6
+ yes_strings: tuple[str] = ("y",),
7
+ throw_if_no: bool = False,
8
+ ) -> str:
9
+ prompt = f"{prompt} ({yes_strings[0]}/no) : "
10
+ yes = input(f"\n{italic(prompt)}") in yes_strings
7
11
  if not yes:
8
12
  if throw_if_no:
9
- raise Exception('Aborted by user')
13
+ raise Exception("Aborted by user")
10
14
 
11
15
  return yes
utill/my_json.py CHANGED
@@ -6,7 +6,7 @@ def _crawl_dictionary_keys(d: dict, path: tuple = ()) -> list[str]:
6
6
  paths: list[tuple] = []
7
7
 
8
8
  for key in d.keys():
9
- key_path = path + (key, )
9
+ key_path = path + (key,)
10
10
 
11
11
  # Recursively traverse nested dictionary
12
12
  if type(d[key]) is dict:
@@ -35,11 +35,11 @@ def flatten(data: str | dict) -> list:
35
35
 
36
36
  def get_path(data: dict, path: str) -> str:
37
37
  if type(data) != dict:
38
- raise ValueError('data is not a dictionary!')
38
+ raise ValueError("data is not a dictionary!")
39
39
 
40
- items = path.split('.')
40
+ items = path.split(".")
41
41
  item = items[0]
42
- path_remaining = '.'.join(items[1:]) if len(items) > 1 else None
42
+ path_remaining = ".".join(items[1:]) if len(items) > 1 else None
43
43
 
44
44
  if item not in data:
45
45
  return None
@@ -55,8 +55,8 @@ def load_jsonc_file(path) -> dict:
55
55
  Read a .jsonc (JSON with comment) files, as json.loads cannot read it
56
56
  """
57
57
 
58
- with open(path, 'r') as f:
58
+ with open(path, "r") as f:
59
59
  content = f.read()
60
60
  pattern = r'("(?:\\.|[^"\\])*")|\/\/.*|\/\*[\s\S]*?\*\/'
61
- content = re.sub(pattern, lambda m: m.group(1) if m.group(1) else '', content)
61
+ content = re.sub(pattern, lambda m: m.group(1) if m.group(1) else "", content)
62
62
  return json.loads(content)