papi-projects 0.1.9__tar.gz → 0.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: papi-projects
3
- Version: 0.1.9
3
+ Version: 0.2.0
4
4
  Summary: PAPI is an API for managing projects
5
5
  License: MIT
6
6
  Author: sandyjmacdonald
@@ -12,6 +12,70 @@ logger = logging.getLogger(__name__)
12
12
 
13
13
  THIS_YEAR = pendulum.now().year
14
14
 
15
+
16
+ def get_project_ids(project_names) -> list:
17
+ """This function takes a list of project names and finds and
18
+ returns a list of project IDs.
19
+
20
+ :param project_names: A list of project names.
21
+ :type project_names: list
22
+ :return: A list of project IDs.
23
+ :rtype: list
24
+ """
25
+ logger.debug("Calling get_project_ids function")
26
+ project_ids = []
27
+ project_id_pattern = r"P[0-9]{4}-[A-Z]{2}[A-Z0-9]{1}-[A-Z]{4}"
28
+ for project_name in project_names:
29
+ match = re.search(project_id_pattern, project_name)
30
+ if match:
31
+ project_id = match.group()
32
+ if project_id not in project_ids:
33
+ project_ids.append(project_id)
34
+ project_ids = sorted(project_ids)
35
+ if len(project_ids):
36
+ logger.info(f"{len(project_ids)} project IDs found")
37
+ else:
38
+ logger.info(f"No project IDs found")
39
+ return project_ids
40
+
41
+ def decompose_project_name(project_name) -> dict:
42
+ """This function takes a project name string and attempts
43
+ to split out a project ID, name, and grant code.
44
+
45
+ :param project_name: A project name string.
46
+ :type project_name: str
47
+ :return: A dictionary with the split-out parts.
48
+ :rtype: dict
49
+ """
50
+ logger.debug("Calling decompose_project_name function")
51
+ pattern = r"""
52
+ ^
53
+ (?P<project_id>P\d{4}-(?:[A-Z]{2}\d|[A-Z]{3})-[A-Z]{4})?
54
+ (?:\s*[-–—]\s*|\s+)?
55
+ (?P<project_name>[^()\[\]]+?)?
56
+ (?:\s*[\(\[]\s*
57
+ (?P<grant_code>[^)\]]+)
58
+ \s*[\)\]])?
59
+ $
60
+ """
61
+ regex = re.compile(pattern, re.VERBOSE)
62
+ match = regex.match(project_name)
63
+ if match:
64
+ project_id = match.group('project_id')
65
+ project_name = match.group('project_name')
66
+ grant_code = match.group('grant_code')
67
+ return {
68
+ "project_id": project_id,
69
+ "project_name": project_name,
70
+ "grant_code": grant_code
71
+ }
72
+ else:
73
+ return {
74
+ "project_id": None,
75
+ "project_name": None,
76
+ "grant_code": None
77
+ }
78
+
15
79
  def check_project_id(id: str) -> bool:
16
80
  """Checks whether a project ID is correctly formed.
17
81
 
@@ -115,6 +179,8 @@ class Project(Protocol):
115
179
  p_uuid: str = None,
116
180
  name: str = "",
117
181
  grant_code: str = None,
182
+ created_at = None,
183
+ modified_at = None,
118
184
  ) -> None:
119
185
  """Constructor method"""
120
186
  logger.debug("Creating Project instance")
@@ -122,6 +188,8 @@ class Project(Protocol):
122
188
  self.user_id = user_id
123
189
  self.grant_code = grant_code
124
190
  self.name = name
191
+ self.created_at = created_at
192
+ self.modified_at = modified_at
125
193
  if suffix is not None:
126
194
  self.suffix = suffix
127
195
  else:
@@ -184,7 +184,7 @@ class UserDB(Protocol):
184
184
  else:
185
185
  user.user_id = f"{first_last_initial}1"
186
186
  self.db.insert(user.to_json())
187
- logger.info(f"User ID '{useruser_id}' inserted into user database")
187
+ logger.info(f"User ID '{user.user_id}' inserted into user database")
188
188
  return user.user_id
189
189
 
190
190
  def search_by_user_name(self, user_name: str) -> list:
@@ -6,36 +6,11 @@ import pendulum
6
6
  import warnings
7
7
  import logging
8
8
  from typing import Protocol, runtime_checkable
9
- from papi.project import Project
9
+ from papi.project import Project, get_project_ids, decompose_project_name
10
10
  from papi.user import User
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
14
- def get_project_ids(project_names):
15
- """This function takes a list of project names and finds and
16
- returns a list of project IDs.
17
-
18
- :param project_names: A list of project names.
19
- :type project_names: list
20
- :return: A list of project IDs.
21
- :rtype: list
22
- """
23
- logger.debug("Calling get_project_ids function")
24
- project_ids = []
25
- project_id_pattern = r"P[0-9]{4}-[A-Z]{2}[A-Z0-9]{1}-[A-Z]{4}"
26
- for project_name in project_names:
27
- match = re.search(project_id_pattern, project_name)
28
- if match:
29
- project_id = match.group()
30
- if project_id not in project_ids:
31
- project_ids.append(project_id)
32
- project_ids = sorted(project_ids)
33
- if len(project_ids):
34
- logger.info(f"{len(project_ids)} project IDs found")
35
- else:
36
- logger.info(f"No project IDs found")
37
- return project_ids
38
-
39
14
 
40
15
  @runtime_checkable
41
16
  class AsanaWrapper(Protocol):
@@ -433,6 +408,29 @@ class TogglTrackWrapper(Protocol):
433
408
  r = client.get("https://api.track.toggl.com/api/v9/me/projects")
434
409
  r_json = r.json()
435
410
  return r_json
411
+
412
+ def get_user_project_objects(self) -> list:
413
+ """Gets all of the Toggl Track user's projects as Project instances.
414
+
415
+ :return: A list of Project instances.
416
+ :rtype: dict
417
+ """
418
+ logger.debug("Calling TogglTrackWrapper.get_user_project_objects method")
419
+ user_projects_json = self.get_user_projects()
420
+ user_project_names = [p["name"] for p in user_projects_json]
421
+ user_project_created = [pendulum.parse(p["created_at"]) for p in user_projects_json]
422
+ user_project_modified = [pendulum.parse(p["at"]) for p in user_projects_json]
423
+ user_projects = []
424
+ for i, n in enumerate(user_project_names):
425
+ decomposed = decompose_project_name(n)
426
+ if decomposed["project_id"] is not None:
427
+ project_id = decomposed["project_id"]
428
+ project_name = decomposed["project_name"]
429
+ created_at = user_project_created[i]
430
+ modified_at = user_project_modified[i]
431
+ project = Project(id=project_id, name=project_name, created_at=created_at, modified_at=modified_at)
432
+ user_projects.append(project)
433
+ return user_projects
436
434
 
437
435
  def get_user_hours(
438
436
  self, start_time=None, end_time=pendulum.now().to_rfc3339_string()
@@ -509,6 +507,29 @@ class TogglTrackWrapper(Protocol):
509
507
  user_ids = sorted(list(set([pid.split("-")[1] for pid in project_ids])))
510
508
  return user_ids
511
509
 
510
+ def get_workspace_project_objects(self) -> list:
511
+ """Gets all of the Toggl Track workspace's projects as Project instances.
512
+
513
+ :return: A list of Project instances.
514
+ :rtype: dict
515
+ """
516
+ logger.debug("Calling TogglTrackWrapper.get_workspace_project_objects method")
517
+ workspace_projects_json = self.get_workspace_projects()
518
+ workspace_project_names = [p["name"] for p in workspace_projects_json]
519
+ workspace_projects = []
520
+ workspace_project_created = [pendulum.parse(p["created_at"]) for p in workspace_projects_json]
521
+ workspace_project_modified = [pendulum.parse(p["at"]) for p in workspace_projects_json]
522
+ for i, n in enumerate(workspace_project_names):
523
+ decomposed = decompose_project_name(n)
524
+ if decomposed["project_id"] is not None:
525
+ project_id = decomposed["project_id"]
526
+ project_name = decomposed["project_name"]
527
+ created_at = workspace_project_created[i]
528
+ modified_at = workspace_project_modified[i]
529
+ project = Project(id=project_id, name=project_name, created_at=created_at, modified_at=modified_at)
530
+ workspace_projects.append(project)
531
+ return workspace_projects
532
+
512
533
  def check_project_exists(self, id: str) -> str:
513
534
  """Checks whether a Toggl Track project containing the specified
514
535
  project ID already exists. If a name containing that ID is found,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "papi-projects"
3
- version = "0.1.9"
3
+ version = "0.2.0"
4
4
  description = "PAPI is an API for managing projects"
5
5
  authors = ["sandyjmacdonald <sandyjmacdonald@gmail.com>"]
6
6
  license = "MIT"
@@ -48,18 +48,19 @@ def main():
48
48
  # Get tracked hours and tracked project IDs/names
49
49
  tracked_hours = toggl.get_user_hours(start_time=start_time, end_time=end_time)
50
50
  projects = {p["id"]: p["name"] for p in toggl.get_user_projects() if p["id"] in tracked_hours}
51
+ hours_per_project = [(projects[t], tracked_hours[t]) for t in tracked_hours]
51
52
 
52
53
  output = args.output
53
54
 
54
55
  if output:
55
56
  # If output filename provided, write to file
56
57
  with open(output, "w") as out:
57
- for t in tracked_hours:
58
- out.write(f"{projects[t]}\t{tracked_hours[t]}\n")
58
+ for h in sorted(hours_per_project, key=lambda x:x[1], reverse=True):
59
+ out.write(f"{h[0]}\t{h[1]}\n")
59
60
  else:
60
61
  # Otherwise, print out project names and tracked hours to stdout
61
- for t in tracked_hours:
62
- print(f"{projects[t]}\t{tracked_hours[t]}")
62
+ for h in sorted(hours_per_project, key=lambda x:x[1], reverse=True):
63
+ print(f"{h[0]}\t{h[1]}")
63
64
 
64
65
  if __name__ == "__main__":
65
66
  main()
@@ -1,5 +1,4 @@
1
1
  import argparse
2
- import warnings
3
2
  from papi.wrappers import TogglTrackWrapper
4
3
  from papi import config, setup_logger
5
4
  from papi.project import Project
File without changes