refactor(add project module folder; added more logs):
This commit is contained in:
		
							
								
								
									
										96
									
								
								twservices/apps/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								twservices/apps/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| import requests | ||||
|  | ||||
| import logging | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class TaskService(object): | ||||
|     def __init__(self, **entries): | ||||
|         self.__dict__.update(entries) | ||||
|  | ||||
|     def findAll(): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     def modify(): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|  | ||||
| class RestService(object): | ||||
|     def __init__(self, api_url, json=False): | ||||
|         self.api_url = api_url | ||||
|         self.headers = {} | ||||
|         self.json = json | ||||
|  | ||||
|     def get(self, path): | ||||
|  | ||||
|         if self.api_url.endswith('/'): | ||||
|             url = '{0}{1}'.format(self.api_url, path[1:]) | ||||
|         else: | ||||
|             url = '{0}{1}'.format(self.api_url, path) | ||||
|  | ||||
|         r = requests.get(url, headers=self.headers) | ||||
|  | ||||
|         if r.raise_for_status(): | ||||
|             logger.error(r.raise_for_status()) | ||||
|             raise r.raise_for_status() | ||||
|  | ||||
|         if self.json: | ||||
|             return r.json() | ||||
|  | ||||
|         return r.content | ||||
|  | ||||
|     def delete(self, path, id): | ||||
|         api_url = self.api_url | ||||
|  | ||||
|         if api_url.endswith('/'): | ||||
|             url = '{0}{1}'.format(api_url, path[1:]) | ||||
|         else: | ||||
|             url = '{0}/{1}'.format(api_url, path) | ||||
|  | ||||
|         r = requests.get(url, headers=self.headers) | ||||
|           | ||||
|         if r.raise_for_status(): | ||||
|             logger.error(r.raise_for_status()) | ||||
|             raise r.raise_for_status() | ||||
|  | ||||
|         if self.json: | ||||
|             return r.json() | ||||
|  | ||||
|         return r.content | ||||
|  | ||||
|     def post(self, path, data): | ||||
|         api_url = self.api_url | ||||
|         print(api_url) | ||||
|  | ||||
|         if self.api_url.endswith('/'): | ||||
|             api_url = api_url[1:] | ||||
|  | ||||
|         if path.startswith('/'): | ||||
|             url ='{0}{1}'.format(api_url, path) | ||||
|         else: | ||||
|             url = '{0}/{1}'.format(api_url, path) | ||||
|  | ||||
|         r = requests.post(url, data, headers=self.headers) | ||||
|  | ||||
|         if r.raise_for_status(): | ||||
|             logger.error(r.raise_for_status()) | ||||
|             raise r.raise_for_status() | ||||
|  | ||||
|         if self.json: | ||||
|             return r.json() | ||||
|  | ||||
|         return r.content | ||||
|  | ||||
|     def addHeader(self, type, value): | ||||
|         self.headers[type] = value | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										308
									
								
								twservices/apps/grocy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										308
									
								
								twservices/apps/grocy.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,308 @@ | ||||
| from taskw.exceptions import TaskwarriorError | ||||
| from twservices.apps import TaskService | ||||
| from datetime import datetime | ||||
| from twservices.apps import RestService | ||||
|  | ||||
| import logging | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class Grocy(TaskService): | ||||
|     API_KEY_HEADER = 'GROCY-API-KEY' | ||||
|  | ||||
|     # Endpoints | ||||
|     FIND_ALL_TASKS_ENDPOINT = '/get-objects/tasks' | ||||
|     DELETE_TASK_ENDPOINT = '/delete-object/tasks/{0}' | ||||
|     ADD_TASK_ENDPOINT = '/add-object/tasks' | ||||
|     MODIFY_TASK_ENDPOINT = '/edit-object/tasks' | ||||
|     # UDA | ||||
|     GROCY_ID = 'grocy_id' | ||||
|     UDAS = { | ||||
|         GROCY_ID: { | ||||
|             'type': 'string', | ||||
|             'label': 'Grocy Task ID' | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     def __init__(self, **entries): | ||||
|         super().__init__(**entries) | ||||
|         self.tw = None | ||||
|         self.rest_service = self.__initRestService() | ||||
|  | ||||
|     def get_udas(self): | ||||
|         return { | ||||
|             'uda': Grocy.UDAS | ||||
|         } | ||||
|  | ||||
|     def set_taskwarrior(self, tw): | ||||
|         self.tw = tw | ||||
|  | ||||
|     def get_grocy_tw_tasks(self): | ||||
|         if not self.tw: | ||||
|             raise TaskwarriorError('taskwarrior instance not defined') | ||||
|  | ||||
|         filters = { | ||||
|             'and':  [('%s.any' % key, None) for key in list(Grocy.UDAS.keys())], | ||||
|             'or': [('status', 'pending'), ('status', 'waiting')] | ||||
|         } | ||||
|  | ||||
|         tasks = self.tw.filter_tasks(filters) | ||||
|  | ||||
|         return tasks | ||||
|  | ||||
|     def to_grocy(self, task): | ||||
|         print('before the dawn') | ||||
|         print(task) | ||||
|         grocy_task = {} | ||||
|  | ||||
|         if Grocy.GROCY_ID in task: | ||||
|             grocy_task['id'] = task[Grocy.GROCY_ID] | ||||
|  | ||||
|         if 'description' in task: | ||||
|             grocy_task['name'] = task['description'] | ||||
|  | ||||
|         if 'annotations' in task: | ||||
|             grocy_task['description'] = '' | ||||
|             for note in task['annotations']: | ||||
|                 grocy_task['description'] += '{}\n'.format(note) | ||||
|  | ||||
|         if 'entry' in task and type(task['entry']) is datetime: | ||||
|             grocy_task['row_created_timestamp'] = task['entry'].strftime('%Y-%m-%d %H:%M:%S') | ||||
|  | ||||
|         if 'due' in task and type(task['due']) is datetime: | ||||
|             grocy_task['due_date'] = task['due'].strftime('%Y-%m-%d') | ||||
|  | ||||
|         if 'done' in task: | ||||
|             grocy_task['done'] = task['done'] | ||||
|  | ||||
|         return grocy_task | ||||
|  | ||||
|     def to_taskwarrior(self, grocy_task): | ||||
|         taskwarrior_task = {} | ||||
|  | ||||
|         if 'id' in grocy_task: | ||||
|             taskwarrior_task[Grocy.GROCY_ID] = grocy_task['id'] | ||||
|  | ||||
|         if 'name' in grocy_task: | ||||
|             taskwarrior_task['description'] = grocy_task['name'] | ||||
|  | ||||
|         if 'description' in grocy_task: | ||||
|             taskwarrior_task['annotations'] = grocy_task['description'].split('\n') | ||||
|  | ||||
|         if 'row_created_timestamp' in grocy_task: | ||||
|             taskwarrior_task['entry'] = grocy_task['row_created_timestamp']  | ||||
|  | ||||
|         if 'due_date' in grocy_task: | ||||
|             taskwarrior_task['due'] = grocy_task['due_date'] | ||||
|  | ||||
|         if 'done' in grocy_task and grocy_task['done'] == '1': | ||||
|             taskwarrior_task['status'] = 'completed'  | ||||
|  | ||||
|         return taskwarrior_task | ||||
|  | ||||
|  | ||||
|     def __initRestService(self): | ||||
|         if self.api.startswith == '/': | ||||
|             self.api = self.api[1:] | ||||
|         if self.api.endswith == '/': | ||||
|             self.api = self.api[0:-1] | ||||
|         rest_service = RestService(self.api, json=True) | ||||
|         rest_service.addHeader(Grocy.API_KEY_HEADER, self.token) | ||||
|         return rest_service | ||||
|  | ||||
|     def findAll(self): | ||||
|         if not self.rest_service: | ||||
|             raise Exception('rest service for grocy is not defined') | ||||
|         # Get all tasks as json | ||||
|         try: | ||||
|             response = self.rest_service.get(Grocy.FIND_ALL_TASKS_ENDPOINT) | ||||
|         except BaseException as e: | ||||
|             logger.error(e) | ||||
|             raise e | ||||
|         return response | ||||
|  | ||||
|     def delete(self, id): | ||||
|         if not self.rest_service: | ||||
|             raise Exception('rest service for grocy is not defined') | ||||
|         if not id: | ||||
|             raise Exception('id is not defined') | ||||
|         # Delete a task | ||||
|         try: | ||||
|             response = self.rest_service.get(Grocy.DELETE_TASK_ENDPOINT, id) | ||||
|         except Exception as e: | ||||
|             logger.error(e) | ||||
|             raise e | ||||
|         return response | ||||
|  | ||||
|     def add(self, task): | ||||
|         if not self.rest_service: | ||||
|             raise Exception('rest service for grocy is not defined') | ||||
|  | ||||
|         responses = [] | ||||
|         if not task: | ||||
|             raise Exception('task is not defined') | ||||
|         if not type(task) is list: | ||||
|             tasks = [task] | ||||
|         else: | ||||
|             tasks = task | ||||
|  | ||||
|         try:  | ||||
|             for next_task in tasks: | ||||
|                 self.to_grocy(next_task) | ||||
|                 response = self.rest_service.post(Grocy.ADD_TASK_ENDPOINT, task) | ||||
|                 responses.append(response) | ||||
|         except Exception as e: | ||||
|             logger.error(e) | ||||
|             raise e | ||||
|         return responses | ||||
|  | ||||
|     def modify(self, task): | ||||
|         if not self.rest_service: | ||||
|             raise Exception('rest service for grocy is not defined') | ||||
|         response = [] | ||||
|         if not task: | ||||
|             raise Exception('task is not defined') | ||||
|         if not type(task) is list: | ||||
|             tasks = [task] | ||||
|         else: | ||||
|             tasks = task | ||||
|         try: | ||||
|             for next_task in tasks: | ||||
|                 modify_endpoint  = '{0}/{1}'.format(Grocy.MODIFY_TASK_ENDPOINT, next_task['id']) | ||||
|                 response = self.rest_service.post(modify_endpoint, next_task) | ||||
|         except Exception as e: | ||||
|             logger.exception('Could not send post to modify grocy task %s', e.stderr) | ||||
|  | ||||
|     def _should_merge(self, converted_tw_to_grocy_task, grocy_task): | ||||
|         return True if converted_tw_to_grocy_task['id'] == grocy_task['id'] else False | ||||
|  | ||||
|     def merge_from_tw(self, taskwarrior_tasks, grocy_tasks): | ||||
|         # clone taskwarrior_tasks for processing | ||||
|         remaining_grocy_tasks_to_process = grocy_tasks[:] | ||||
|  | ||||
|         # Convert to taskwarrior tasks into grocy tasks | ||||
|         converted_tw_to_grocy_tasks = [] | ||||
|         modified_grocy_tasks = [] | ||||
|         tasks_to_add_to_grocy = [] | ||||
|         converted_tw_to_grocy_tasks = [self.to_grocy(task) for task in taskwarrior_tasks] | ||||
|  | ||||
|         # Sort grocy tasks by id and converted taskwarrior tasks by GROCY_ID | ||||
|         grocy_tasks.sort(key=lambda x: x['id']) | ||||
|         converted_tw_to_grocy_tasks.sort(key=lambda x: x['id']) | ||||
|  | ||||
|         # If no taskwarrior tasks add grocy tasks to taskwarrior  | ||||
|         if len(converted_tw_to_grocy_tasks) == 0:  | ||||
|             convert_grocy_tasks_to_tw = [self.to_taskwarrior(grocy_task) for grocy_task in grocy_tasks] | ||||
|             for task in convert_grocy_tasks_to_tw: | ||||
|                 try: | ||||
|                     added_task = self.tw.task_add(**task)  | ||||
|                     logger.debug('Added grocy task %s to taskwarrior', task) | ||||
|                 except TaskwarriorError as e: | ||||
|                     logger.exception('Unable to add task from grocy: %s', e.stderr) | ||||
|  | ||||
|         # Iterate through grocy tasks to find any conflicts | ||||
|         for index in range(len(converted_tw_to_grocy_tasks)): | ||||
|  | ||||
|             finished_merge_process = index > len(remaining_grocy_tasks_to_process) - 1 | ||||
|             # Add taskwarrior task to grocy if no more grocy tasks to process  | ||||
|             if finished_merge_process: | ||||
|                 tasks_to_add_to_grocy.append(converted_tw_to_grocy_task) | ||||
|                 logger.debug('Added grocy task %s to taskwarrior task', grocy_task['id']) | ||||
|                 continue | ||||
|             grocy_task = grocy_tasks[index] | ||||
|             converted_tw_to_grocy_task = converted_tw_to_grocy_tasks[index]  | ||||
|  | ||||
|             # Merge from grocy into taskwarrior if match found | ||||
|             # Delete taskwarrior task  | ||||
|             if self._should_merge(converted_tw_to_grocy_task, grocy_task): | ||||
|                 modified_grocy_tasks.append(converted_tw_to_grocy_task) | ||||
|                 logger.debug('Merged taskwarrior task %s to grocy task %s', converted_tw_to_grocy_task['id'], grocy_task['id']) | ||||
|                 del remaining_grocy_tasks_to_process[index] | ||||
|             # Add taskwarrior task into taskwarrior | ||||
|             else: | ||||
|                 tasks_to_add_to_grocy.append(converted_tw_to_grocy_task)  | ||||
|                 logger.debug('Added grocy task %s to taskwarrior task', grocy_task['id']) | ||||
|  | ||||
|         # Add any remaining taskwarrior tasks not found in grocy into grocy | ||||
|         for index in range(len(converted_tw_to_grocy_tasks)): | ||||
|             tw_task = self.to_taskwarrior(converted_tw_to_grocy_tasks[index]) | ||||
|             try: | ||||
|                 self.tw.task_add(**tw_task) | ||||
|             except TaskwarriorError as e: | ||||
|                 logger.exception('Could not add task %s', e.stderr) | ||||
|  | ||||
|        # Send requests to Grocy service | ||||
|         if len(modified_grocy_tasks) > 0: | ||||
|             self.modify(modified_grocy_tasks) | ||||
|         if len(tasks_to_add_to_grocy) > 0: | ||||
|             self.add(tasks_to_add_to_grocy) | ||||
|  | ||||
|     def merge_into_tw(self, taskwarrior_tasks, grocy_tasks): | ||||
|         print('here') | ||||
|         # Convert to taskwarrior tasks into grocy tasks  | ||||
|         converted_tw_to_grocy_tasks = [] | ||||
|         converted_tw_to_grocy_tasks = [self.to_grocy(task) for task in taskwarrior_tasks] | ||||
|  | ||||
|         # Sort grocy tasks by id and converted taskwarrior tasks by GROCY_ID | ||||
|         grocy_tasks.sort(key=lambda x: x['id']) | ||||
|         converted_tw_to_grocy_tasks.sort(key=lambda x: x[Grocy.GROCY_ID]) | ||||
|  | ||||
|         if len(grocy_tasks) == 0: | ||||
|             self.add(converted_tw_to_grocy_tasks) | ||||
|  | ||||
|         # Iterate through grocy tasks to find any conflicts | ||||
|         for index in range(len(grocy_tasks)): | ||||
|             grocy_task = grocy_tasks[index] | ||||
|             converted_tw_to_grocy_task = converted_tw_to_grocy_tasks[index]  | ||||
|             current_index_exceeded_tw_list = index > len(converted_tw_to_grocy_tasks) - 1 | ||||
|             # Add if no more taskwarrior tasks to process  | ||||
|             if current_index_exceeded_tw_list: | ||||
|                 try: | ||||
|                     self.tw.task_add(converted_tw_to_grocy_task) | ||||
|                     logger.debug('Added grocy task %s to taskwarrior task', grocy_task['id']) | ||||
|                 except TaskwarriorError as e: | ||||
|                     logger.exception('Could not add task: %s', e.stderr) | ||||
|  | ||||
|                 continue | ||||
|             # Merge from grocy into taskwarrior if match found | ||||
|             # Delete taskwarrior task  | ||||
|             if self.should_merge(converted_tw_to_grocy_task, grocy_task): | ||||
|                 try: | ||||
|                     converted_grocy_task_to_tw = self.to_taskwarrior(grocy_task) | ||||
|                     self.tw.update(converted_grocy_task_to_tw) | ||||
|                     logger.debug('Merged grocy task %s to taskwarrior task %s', grocy_task['id'], converted_grocy_task_to_tw['uuid']) | ||||
|                     del converted_tw_to_grocy_tasks[index] | ||||
|                 except TaskwarriorError as e: | ||||
|                     logger.exception('Could not update task: %s', e.stderr) | ||||
|             # Add grocy task into taskwarrior | ||||
|         else: | ||||
|             try: | ||||
|                 self.tw.task_add(converted_grocy_task_to_tw) | ||||
|                 logger.debug('Added grocy task %s to taskwarrior task', grocy_task['id']) | ||||
|             except TaskwarriorError as e: | ||||
|                 logger.exception('Could not add task: %s', e.stderr) | ||||
|  | ||||
|         # Add any remaining taskwarrior tasks not found in grocy into grocy | ||||
|         for index in range(len(converted_tw_to_grocy_tasks)): | ||||
|             tw_task = self.to_taskwarrior(converted_tw_to_grocy_tasks[index]) | ||||
|             self.tw.task_add(tw_task) | ||||
|  | ||||
|     def sync(self):  | ||||
|         # Get all tasks from taskwarrior | ||||
|         # Get all tasks from grocy | ||||
|         # Push to taskwarrior | ||||
|         taskwarrior_tasks = self.get_grocy_tw_tasks() | ||||
|  | ||||
|         try: | ||||
|             grocy_tasks = self.findAll() | ||||
|         except BaseException as e: | ||||
|             logger.error(e) | ||||
|             logger.error('Could not get all grocy tasks') | ||||
|  | ||||
|         try: | ||||
|             if self.resolution == 'tw': | ||||
|                 self.merge_from_tw(taskwarrior_tasks, grocy_tasks) | ||||
|             else: | ||||
|                 self.merge_into_tw(taskwarrior_tasks, grocy_tasks) | ||||
|         except TaskwarriorError as e: | ||||
|             logger.exception('Could not sync tasks %s', e.stderr) | ||||
		Reference in New Issue
	
	Block a user