From a2b543d42a94d92aabcb91c582e5e7ef20ef5f43 Mon Sep 17 00:00:00 2001 From: CaidevOficial Date: Tue, 15 Feb 2022 17:23:54 -0300 Subject: [PATCH 1/9] Applied Pandas in DataFrameHandler Class --- API_Info.json | 18 +++ DataFrameHandler_Mod/DFHandler.py | 208 ++++++++++++++++++++++++++++++ DataFrameHandler_Mod/__init__.py | 16 +++ GetData_Mod/DataManager.py | 189 ++++++++++++++++++--------- GithubCloner2022.py | 69 ++++++++-- Github_Repositories.csv | 4 +- apiInfo.json | 8 -- test.py | 5 + 8 files changed, 434 insertions(+), 83 deletions(-) create mode 100644 API_Info.json create mode 100644 DataFrameHandler_Mod/DFHandler.py create mode 100644 DataFrameHandler_Mod/__init__.py delete mode 100644 apiInfo.json create mode 100644 test.py diff --git a/API_Info.json b/API_Info.json new file mode 100644 index 0000000..8b92de3 --- /dev/null +++ b/API_Info.json @@ -0,0 +1,18 @@ +{ + "Github": { + "URL": "https://api.github.com/repos", + "USER": "CaidevOficial", + "REPO": "Python_Udemy_DataManipulation", + "BRANCH": "main" + }, + "DataFrame": { + "Fields": { + "Name": "Nombre/s", + "Surname": "Apellido/s", + "Course": "División", + "ID": "DNI / Legajo", + "Email": "E-Mail", + "GitLink": "Link al repositorio" + } + } +} \ No newline at end of file diff --git a/DataFrameHandler_Mod/DFHandler.py b/DataFrameHandler_Mod/DFHandler.py new file mode 100644 index 0000000..af9b79a --- /dev/null +++ b/DataFrameHandler_Mod/DFHandler.py @@ -0,0 +1,208 @@ +# GNU General Public License V3 +# +# Copyright (c) 2022 [FacuFalcone] +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from textwrap import indent +from numpy import ndarray +from pandas import DataFrame +import pandas as pd + +class DataFrameHandler: + + ##### Attributes ##### + __configsJsonValues:dict = {} + __commands = [] + __uniqueColumns:ndarray = [] + __mainDF:DataFrame = None + __studentsDF = [] + __orderedListOfDFStudents:list = [] + ##### End Attributes ##### + + def __init__(self) -> None: + pass + + + ##### Properties ##### + @property + def OrderListOfDFStudents(self)->list: + """[summary] + Get the list of ordered dataframes + Returns: + [list]: [The list of ordered dataframes] + """ + return self.__orderedListOfDFStudents + + @OrderListOfDFStudents.setter + def OrderListOfDFStudents(self, df:DataFrame)->None: + """[summary] + Set the list of ordered dataframes + Args: + df (DataFrame): [The ordered dataframes] + """ + self.__orderedListOfDFStudents.append(df) + + @property + def ConfigsJsonValues(self)->dict: + return self.__configsJsonValues + + @ConfigsJsonValues.setter + def ConfigsJsonValues(self, value:dict)->None: + self.__configsJsonValues = value + + @property + def MainDataFrame(self)->DataFrame: + """[summary] + Get the main dataframe + Returns: + [DataFrame]: [The main dataframe] + """ + return self.__mainDF + + @MainDataFrame.setter + def MainDataFrame(self, df:DataFrame): + """[summary] + Set the main dataframe + Args: + df (DataFrame): [The dataframe to set] + """ + self.__mainDF = df + + @property + def UniqueColumns(self) -> ndarray: + return self.__uniqueColumns + + @UniqueColumns.setter + def UniqueColumns(self, uniqueColumns:ndarray): + self.__uniqueColumns = uniqueColumns + + @property + def Commands(self)->list: + """[summary] + Get the list of the commands + Returns: + [list]: [The list of the commands] + """ + return self.__commands + + @Commands.setter + def Commands(self, command:str)->None: + """[summary] + Set the command to the list of the commands + Args: + command (str): [The command to add] + """ + self.__commands.append(command) + + @property + def StudentsDF(self)->list: + """[summary] + Get the dataframe with the students + Returns: + [DataFrame]: [The dataframe with the students] + """ + return self.__studentsDF + + @StudentsDF.setter + def StudentsDF(self, df:DataFrame)->None: + """[summary] + Set the dataframe with the students + Args: + df (DataFrame): [The dataframe with the students] + """ + self.__studentsDF.append(df) + ##### End Properties ##### + + ##### Methods ##### + def GetSpecificStudentsDF(self, df:DataFrame, column:str, value:str)->DataFrame: + """[summary] + Get the students that have the specified index value in the specified column. + The DataFrame MUST be indexed by the 'value' column. + Args: + df (DataFrame): [The dataframe to filter] + column (str): [The column to filter] + value (str): [The value to filter] + Returns: + [DataFrame]: [The dataframe with the filtered students ordered by Course, Surname & Name] + """ + specificDF: DataFrame = df[df[column] == value] + orderedDF: DataFrame = self.OrderIndexedDFBy( + specificDF, self.ConfigsJsonValues['Course'], + self.ConfigsJsonValues['Surname'], + self.ConfigsJsonValues['Name'] + ) + return orderedDF + + def GetListDFStudentsBy(self, df:DataFrame, column:str, columnValue:str): + """[summary] + Get the students that have the specified index value in the specified column. + The DataFrame MUST be indexed by the 'value' column. + Args: + df (DataFrame): [The dataframe to filter] + column (str): [The column to filter] + value (list): [The values to filter] + """ + #listCourses = [] + #listCourses.append(self.GetSpecificStudentsDF(df, column, columnValue)) + self.OrderListOfDFStudents = self.GetSpecificStudentsDF(df, column, columnValue) + + def OrderIndexedDFBy(self, df:DataFrame, firstField:str, secondField:str, thirdField:str)->DataFrame: + """[summary] + Order the dataframe by the specified fields + Args: + df (DataFrame): [The dataframe to order] + firstField (str): [The first field to order] + secondField (str): [The second field to order] + thirdField (str): [The third field to order] + Returns: + [DataFrame]: [The dataframe ordered by the three fields in the specified order] + """ + sortedDF = df.sort_values(by=[firstField, secondField, thirdField], ascending=[True, True, True]) + return sortedDF + + #* USED + def ConfigUniqueValuesInColumn(self, column:str): + """[summary] + Get the unique values in the specified column and sort them in alphabetical order ASC. + Args: + column (str): [The column to filter] + Returns: + [list]: [The unique values in the specified column] + """ + self.UniqueColumns = self.MainDataFrame[column].unique() + self.UniqueColumns.sort() + + #* USED + def CreateJsonOfEveryDF(self): + for i in range(len(self.OrderListOfDFStudents)): + frame:DataFrame = self.OrderListOfDFStudents[i] + name = frame.at[frame.index.values[0], self.ConfigsJsonValues['Course']] + filename:str = f'{name}.json' + frame.to_json(f'{filename}', orient='table', indent=4, force_ascii=True) + + ##### End Methods ##### + + #####* Main Method ##### + def ConfigurateDataFrame(self, columnValue:str)->None: + #* Gets the unique values of the column 'columnValue' [Division] + self.ConfigUniqueValuesInColumn(columnValue) + #* For each unique value of the column 'columnValue' [Division] + #* Creates a list of dataframes with the students that have the specified value in the column 'columnValue' [Division] + for unique in self.UniqueColumns: + self.GetListDFStudentsBy(self.MainDataFrame, columnValue, unique) + self.CreateJsonOfEveryDF() + + #####* End Main Method ##### + diff --git a/DataFrameHandler_Mod/__init__.py b/DataFrameHandler_Mod/__init__.py new file mode 100644 index 0000000..89927cb --- /dev/null +++ b/DataFrameHandler_Mod/__init__.py @@ -0,0 +1,16 @@ +# GNU General Public License V3 +# +# Copyright (c) 2022 [FacuFalcone] +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . diff --git a/GetData_Mod/DataManager.py b/GetData_Mod/DataManager.py index 55f17fc..b3e4469 100644 --- a/GetData_Mod/DataManager.py +++ b/GetData_Mod/DataManager.py @@ -18,9 +18,10 @@ import os import requests from PrintMessage_Mod.CloneMessenger import CloneMessenger as CM +from DataFrameHandler_Mod.DFHandler import DataFrameHandler as DFH -#import pandas as pd -#from pandas import DataFrame as df +import pandas as pd +from pandas import DataFrame class DataManager: @@ -36,14 +37,19 @@ class DataManager: __version = '' __commands: list = [] __Messenger: CM = None - #__studentsInfo: pd.DataFrame = pd.DataFrame() + __studentsInfo: DataFrame = DataFrame() - def __init__(self, fileName:str, name:str, version:str, APIURL:str): - self.SetFilename(fileName) + def __init__(self): + pass + + def InitialConfig(self, name:str, version:str, APIURL:dict): + #self.SetFilename(fileName) self.SetAppName(name) self.SetAppVersion(version) self.SetAPIURL(APIURL) + #* SETTERS + def SetFilename(self, fileName:str)->None: """[summary] Set the name of the file to read @@ -68,13 +74,17 @@ def SetAppVersion(self, version:str)->None: """ self.__version = version - def SetAPIURL(self, api:str)->None: + def SetAPIURL(self, api:dict)->None: """[summary] Set the URL of the API Args: api (str): [The URL of the API] + "URL": "https://api.github.com/repos", + "USER": "CaidevOficial", + "REPO": "Python_Udemy_DataManipulation", + "BRANCH": "main" """ - self.__configAPIURL = api + self.__configAPIURL = f'{api["URL"]}/{api["USER"]}/{api["REPO"]}/{api["BRANCH"]}' def AddComand(self, command:str)->None: """[summary] @@ -84,6 +94,16 @@ def AddComand(self, command:str)->None: """ self.__commands.append(command) + def SetStudentsDF(self, students: DataFrame)->None: + """[summary] + Sets the dataframe of students to work with. + Args: + students (DataFrame): The dataframe of students to work + """ + self.__studentsInfo = students + + #* GETTERS + def GetCommands(self)->list: """[summary] Get the commands to clone the repositories of the students @@ -91,6 +111,24 @@ def GetCommands(self)->list: list: [The list of the commands to execute] """ return self.__commands + + @property + def Commands(self): + """[summary] + Get the commands to clone the repositories of the students + Returns: + list: [The list of the commands to execute] + """ + return self.__commands + + @Commands.setter + def Commands(self, commands:list): + """[summary] + Set the commands to clone the repositories of the students + Args: + commands (list): [The list of the commands to execute] + """ + self.__commands = commands def GetAppName(self)->str: """[summary] @@ -137,44 +175,14 @@ def GetDate(self, api:str)->str: date = date[:10] return date.replace("-","") - #!TODO: Implement Pandas - def OpenFile(self)->None: + def GetStudentsDF(self)->DataFrame: """[summary] - Open the file and get the data + Gets the Students DataFrame of the class to work with. + Returns: + DataFrame: The Actual DataFrame of the students """ - #file = pd.read_csv(self.fileName) - appInfo = f'{self.GetAppName()} - {self.GetAppVersion()}' - self.__Messenger = CM(appInfo) - self.__Messenger.PrintMessage() + return self.__studentsInfo - try: - with open(self.GetFilename(), 'r') as f: - lines:list = f.readlines()[1::] - for line in lines: - fieldList = line.split(',') - fieldList = [x.strip().replace('"', '') for x in fieldList] - - if len(fieldList)>=6: - message = f'Cloning repository of {fieldList[2]}, {fieldList[1]}' - self.__Messenger = CM(message) - studentGitUrl = fieldList[6] - api = self.GetAPIURL() - date = self.GetDate(api) - - self.__Messenger.PrintMessage() - - self.MakeCloneCommands( - self.FormatSurname(fieldList, date), - self.FormatCourse(fieldList), - self.NormalizeURL(studentGitUrl) - ) - self.ExecuteCommands(self.GetCommands()) - self.__Messenger.SetMessage('All Repositories have been cloned!') - self.__Messenger.PrintMessage() - except Exception as e: - self.__Messenger = CM(e) - self.__Messenger.PrintMessage() - def NormalizeURL(self, url:str)->str: """[summary] Normalize the URLs of the git's repositorys of the students, @@ -199,19 +207,20 @@ def NormalizeCourse(self, course:str)->str: """ return course.replace(' - ', '-').replace(" ","_") - def FormatSurname(self, fieldList:list, date)->str: + def FormatFullnameDate(self, surname:str, name:str)->str: """[summary] Format the surname of the student, removing the spaces and replacing them with _ Args: - fieldList (list): [The list of the fields of the student] + surname (str): [The surname of the student] + name (str): [The name of the student] date (str): [The date of the student] Returns: - str: [The formatted surname] + str: [The formatted Fullname like this: surname_name_date] """ - surname = fieldList[2].replace(",","_").replace(" ","").replace("\n","") - name = fieldList[1].replace(",","_").replace(" ","").replace("\n","") + surname = surname.replace(",","_").replace(" ","").replace("\n","") + name = name.replace(",","_").replace(" ","").replace("\n","") - return f'{surname}_{name}_{date}' + return f'{surname}_{name}_{self.GetDate(self.GetAPIURL())}' def FormatCourse(self, fieldList:str)->str: """[summary] @@ -223,23 +232,83 @@ def FormatCourse(self, fieldList:str)->str: """ return self.NormalizeCourse(fieldList[3].replace("\n","")) - def MakeCloneCommands(self, surname:str, course:str, git:str)->None: - """[summary] - Make the commands to clone the repositories of the students - Args: - surname (str): [The surname of the student] - course (str): [The course of the student] - git (str): [The url of the git's repository] - """ - command = f'git clone {git} {course}//{surname}' - self.AddComand(command) + # def MakeCloneCommands(self, surname:str, course:str, git:str)->None: + # """[summary] + # Make the commands to clone the repositories of the students + # Args: + # surname (str): [The surname of the student] + # course (str): [The course of the student] + # git (str): [The url of the git's repository] + # """ + # command = f'git clone {git} {course}//{surname}' + # self.AddComand(command) + + #def MakeCloneCommands(self, dfHandler:DataFrame, surname:str, name:str, course:str, git:str)->None: + def MakeCloneCommands(self, dfHandler: DFH, df: DataFrame, configJsonFields: dict)->None: + """[summary] + Make the commands to clone the repositories of the students + Args: + surname (str): [The surname of the student] + course (str): [The course of the student] + git (str): [The url of the git's repository] + """ + for index, row in dfHandler.iterrows(): + courseStr = self.NormalizeCourse(self.FormatCourse(row[configJsonFields['Course']])) + surnameStr = row[configJsonFields['Surname']] + nameStr = row[configJsonFields['Name']] + command = f"git clone {self.NormalizeURL(row[configJsonFields['GitLink']])} {courseStr}//{self.FormatFullnameDate(surnameStr, nameStr)}" + self.AddComand(command) + + - def ExecuteCommands(self, commandList:list)->None: + def ExecuteCommands(self)->None: """[summary] Execute the commands to clone the repositories of the students Args: commandList (list): [The list of the commands to execute] """ - commandList = [x.strip() for x in commandList] + commandList = [x.strip() for x in self.Commands] for command in commandList: os.system(command) + + #!TODO: Implement Pandas + def OpenFile(self)->None: + """[summary] + Open the file and get the data + """ + #file = pd.read_csv(self.fileName) + appInfo = f'{self.GetAppName()} - {self.GetAppVersion()}' + self.__Messenger = CM(appInfo) + self.__Messenger.PrintMessage() + + try: + with open(self.GetFilename(), 'r') as f: + lines:list = f.readlines()[1::] + for line in lines: + fieldList = line.split(',') + fieldList = [x.strip().replace('"', '') for x in fieldList] + + if len(fieldList)>=6: + message = f'Cloning repository of {fieldList[2]}, {fieldList[1]}' + self.__Messenger = CM(message) + studentGitUrl = fieldList[6] + api = self.GetAPIURL() + date = self.GetDate(api) + + self.__Messenger.PrintMessage() + + self.MakeCloneCommands( + self.FormatSurname(fieldList, date), + self.FormatCourse(fieldList), + self.NormalizeURL(studentGitUrl) + ) + self.ExecuteCommands(self.GetCommands()) + self.__Messenger.SetMessage('All Repositories have been cloned!') + self.__Messenger.PrintMessage() + except Exception as e: + self.__Messenger = CM(e) + self.__Messenger.PrintMessage() + + #def DoMagic(self, dfHandler: DFH): + + diff --git a/GithubCloner2022.py b/GithubCloner2022.py index 5130557..d040dae 100644 --- a/GithubCloner2022.py +++ b/GithubCloner2022.py @@ -18,21 +18,62 @@ import json from GetData_Mod.DataManager import DataManager as DM -########## Start Basic Configuration ########## +import pandas as pd +from DataFrameHandler_Mod.DFHandler import DataFrameHandler as DfH + +##########? Start Basic Configuration ########## filename = 'Github_Repositories.csv' name = 'Github Repository Cloner' -version = '[V1.1.05]' -fileConfigName = 'apiInfo.json' -########## End Basic Configuration ########## +version = '[V1.1.04]' +fileConfigName = 'API_Info.json' +##########? End Basic Configuration ########## + + try: - with open(fileConfigName, 'r') as APIFILE: - JsonFile = json.load(APIFILE)[0] - print(JsonFile) - APIURL = f'{JsonFile["URL"]}/{JsonFile["USER"]}/{JsonFile["REPO"]}/commits/{JsonFile["BRANCH"]}' - print(APIURL) - - manager = DM(filename, name, version, APIURL) - manager.OpenFile() -except FileNotFoundError: - print(f'File not found: {fileConfigName}') + JsonFile = pd.read_json(f"./{fileConfigName}", orient='records') + JsonAPI = JsonFile['Github'] + JsonDFConfigs = JsonFile['DataFrame']['Fields'] + + Handler = DfH() + Manager = DM() + Manager.InitialConfig(name, version, JsonAPI) + + + ########## Start DataFrame Configuration ########## + #* Reads the 'csv' File to get the dataframe + df = pd.read_csv(filename) + + #? SETTINGS OF DATAFRAMEHANDLER + #* Sets the Main DF to the class to handle it + Handler.MainDataFrame = df + Handler.ConfigsJsonValues = JsonDFConfigs + Handler.ConfigurateDataFrame(Handler.ConfigsJsonValues['Course']) + + ########## TEST UNIQUE VALUES ########## #* TEST PASSED + print('UNIQUE VALUES\n') + for unique in Handler.UniqueColumns: + print(unique) + ########## End TEST UNIQUE VALUES ########## + + ########## TEST LIST OF STUDENTS ########## #* TEST PASSED + print('\nLIST OF STUDENTS\n') + studList = Handler.OrderListOfDFStudents + print(studList) + ########## END TEST LIST OF STUDENTS ########## + +except Exception as e: + #print(f'File not found: {fileConfigName}') + print(f'Error: {e}') + +# try: +# with open(fileConfigName, 'r') as APIFILE: +# JsonFile = pd.read_json(APIFILE)["Github"] +# print(JsonFile) +# APIURL = f'{JsonFile["URL"]}/{JsonFile["USER"]}/{JsonFile["REPO"]}/commits/{JsonFile["BRANCH"]}' +# print(APIURL) + +# manager = DM(filename, name, version, APIURL) +# manager.OpenFile() +# except FileNotFoundError: +# print(f'File not found: {fileConfigName}') diff --git a/Github_Repositories.csv b/Github_Repositories.csv index 5ed0f49..ce50003 100644 --- a/Github_Repositories.csv +++ b/Github_Repositories.csv @@ -1,5 +1,7 @@ "Marca temporal","Nombre/s","Apellido/s","División","DNI / Legajo","E-Mail","Link al repositorio" -"2022/02/13 10:26:52 p. m. GMT-3","Hades","Grecian God","1F - Professor 2 - Helper 2","111111","Hades@underworld.com","https://github.com/caidevOficial/CaidevOficial.git" "2022/02/13 10:26:52 p. m. GMT-3","Neptune","Romane God","1G - Professor 1 - Helper 1","222222","neptune@notplanet.com","https://github.com/caidevOficial/SPD2022_TPS.git" "2022/02/13 10:26:52 p. m. GMT-3","Poseidon","Grecian God","1F - Professor 2 - Helper 2","333333","poseidon@sea.com","https://github.com/caidevOficial/Python_ITBA_IEEE.git" +"2022/02/13 10:26:52 p. m. GMT-3","Hades","Grecian God","1F - Professor 2 - Helper 2","111111","Hades@underworld.com","https://github.com/caidevOficial/CaidevOficial.git" "2022/02/13 10:26:52 p. m. GMT-3","Zeus","Grecian God","1G - Professor 1 - Helper 1","444444","zeus@ray.com","https://github.com/caidevOficial/Python_IEEE_Team14293.git" +"2022/02/13 10:26:52 p. m. GMT-3","Mercury","Romane God","1G - Professor 1 - Helper 1","222222","neptune@notplanet.com","https://github.com/caidevOficial/SPD2022_TPS.git" +"2022/02/13 10:26:52 p. m. GMT-3","Artemisa","Grecian God","1G - Professor 1 - Helper 1","444444","zeus@ray.com","https://github.com/caidevOficial/Python_IEEE_Team14293.git" diff --git a/apiInfo.json b/apiInfo.json deleted file mode 100644 index fc1e810..0000000 --- a/apiInfo.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "URL": "https://api.github.com/repos", - "USER": "CaidevOficial", - "REPO": "Python_Udemy_DataManipulation", - "BRANCH": "main" - } -] \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..a25686e --- /dev/null +++ b/test.py @@ -0,0 +1,5 @@ + +for i in range(0,10): + if i%2==0: + continue + print(i) \ No newline at end of file From bf95d7c5fa989753081bdce184e5349122af5d14 Mon Sep 17 00:00:00 2001 From: CaidevOficial Date: Wed, 16 Feb 2022 01:01:19 -0300 Subject: [PATCH 2/9] Upgrade to V1.2.01 - Pandas & DF implemented --- DataFrameHandler_Mod/DFHandler.py | 160 ++++++++----- GetData_Mod/DataManager.py | 373 ++++++++++++++++------------- GithubCloner2022.py | 48 ++-- PrintMessage_Mod/CloneMessenger.py | 49 ++-- 4 files changed, 349 insertions(+), 281 deletions(-) diff --git a/DataFrameHandler_Mod/DFHandler.py b/DataFrameHandler_Mod/DFHandler.py index af9b79a..3e1cd89 100644 --- a/DataFrameHandler_Mod/DFHandler.py +++ b/DataFrameHandler_Mod/DFHandler.py @@ -15,127 +15,151 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from textwrap import indent from numpy import ndarray from pandas import DataFrame -import pandas as pd class DataFrameHandler: - - ##### Attributes ##### + """[summary]\n + Class in charge of configurate and handle all the dataframe operations.\n + Returns: + class: [DataFrameHandler].\n + """ + #####? Attributes ##### __configsJsonValues:dict = {} __commands = [] __uniqueColumns:ndarray = [] __mainDF:DataFrame = None __studentsDF = [] __orderedListOfDFStudents:list = [] - ##### End Attributes ##### + #####? End Attributes ##### def __init__(self) -> None: pass - - ##### Properties ##### + #####? Properties ##### + @property def OrderListOfDFStudents(self)->list: - """[summary] - Get the list of ordered dataframes + """[summary]\n + Get the list of ordered dataframes.\n Returns: - [list]: [The list of ordered dataframes] + [list]: [The list of ordered dataframes].\n """ return self.__orderedListOfDFStudents @OrderListOfDFStudents.setter def OrderListOfDFStudents(self, df:DataFrame)->None: - """[summary] - Set the list of ordered dataframes + """[summary]\n + Set the list of ordered dataframes.\n Args: - df (DataFrame): [The ordered dataframes] + df (DataFrame): [The ordered dataframes].\n """ self.__orderedListOfDFStudents.append(df) @property def ConfigsJsonValues(self)->dict: + """[summary]\n + Get the configs of the json.\n + Returns: + [dict]: [The configs of the json].\n + """ return self.__configsJsonValues @ConfigsJsonValues.setter def ConfigsJsonValues(self, value:dict)->None: + """[summary]\n + Set the configs of the json.\n + Args: + value (dict): [The configs of the json].\n + """ self.__configsJsonValues = value @property def MainDataFrame(self)->DataFrame: - """[summary] - Get the main dataframe + """[summary]\n + Get the main dataframe.\n Returns: - [DataFrame]: [The main dataframe] + [DataFrame]: [The main dataframe].\n """ return self.__mainDF @MainDataFrame.setter def MainDataFrame(self, df:DataFrame): - """[summary] - Set the main dataframe + """[summary]\n + Set the main dataframe.\n Args: - df (DataFrame): [The dataframe to set] + df (DataFrame): [The dataframe to set].\n """ self.__mainDF = df @property def UniqueColumns(self) -> ndarray: + """[summary]\n + Get the unique values of the column 'columnValue' [Division].\n + Returns: + [ndarray]: [The unique values of the column 'columnValue' [Division]].\n + """ return self.__uniqueColumns @UniqueColumns.setter def UniqueColumns(self, uniqueColumns:ndarray): + """[summary]\n + Set the unique values of the column 'columnValue' [Division].\n + Args: + uniqueColumns (ndarray): [The unique values of the column 'columnValue' [Division]].\n + """ self.__uniqueColumns = uniqueColumns @property def Commands(self)->list: - """[summary] - Get the list of the commands + """[summary]\n + Get the list of the commands.\n Returns: - [list]: [The list of the commands] + [list]: [The list of the commands].\n """ return self.__commands @Commands.setter def Commands(self, command:str)->None: - """[summary] - Set the command to the list of the commands + """[summary]\n + Sets a command inside the list of the commands.\n Args: - command (str): [The command to add] + command (str): [The command to add].\n """ self.__commands.append(command) @property def StudentsDF(self)->list: - """[summary] - Get the dataframe with the students + """[summary]\n + Get the dataframe with the students.\n Returns: - [DataFrame]: [The dataframe with the students] + [DataFrame]: [The dataframe with the students].\n """ return self.__studentsDF @StudentsDF.setter def StudentsDF(self, df:DataFrame)->None: - """[summary] - Set the dataframe with the students + """[summary]\n + Set the dataframe with the students.\n Args: - df (DataFrame): [The dataframe with the students] + df (DataFrame): [The dataframe with the students].\n """ self.__studentsDF.append(df) - ##### End Properties ##### - ##### Methods ##### + #####? End Properties ##### + + #####? Methods ##### + def GetSpecificStudentsDF(self, df:DataFrame, column:str, value:str)->DataFrame: - """[summary] - Get the students that have the specified index value in the specified column. - The DataFrame MUST be indexed by the 'value' column. + """[summary]\n + Get the students that have the specified index value in the specified column.\n + The DataFrame MUST be indexed by the 'value' column.\n Args: - df (DataFrame): [The dataframe to filter] - column (str): [The column to filter] - value (str): [The value to filter] + df (DataFrame): [The dataframe to filter].\n + column (str): [The column to filter].\n + value (str): [The value to filter].\n Returns: - [DataFrame]: [The dataframe with the filtered students ordered by Course, Surname & Name] + [DataFrame]: [The dataframe with the filtered students ordered by Course, Surname & Name].\n """ specificDF: DataFrame = df[df[column] == value] orderedDF: DataFrame = self.OrderIndexedDFBy( @@ -145,64 +169,70 @@ def GetSpecificStudentsDF(self, df:DataFrame, column:str, value:str)->DataFrame: ) return orderedDF - def GetListDFStudentsBy(self, df:DataFrame, column:str, columnValue:str): - """[summary] - Get the students that have the specified index value in the specified column. - The DataFrame MUST be indexed by the 'value' column. + def CreateListDFStudentsBy(self, df:DataFrame, column:str, columnValue:str): + """[summary]\n + Creates a list of the students that have the specified index value in the specified column.\n + The DataFrame MUST be indexed by the 'value' column.\n Args: - df (DataFrame): [The dataframe to filter] - column (str): [The column to filter] - value (list): [The values to filter] + df (DataFrame): [The dataframe to filter].\n + column (str): [The column to filter].\n + value (list): [The values to filter].\n """ - #listCourses = [] - #listCourses.append(self.GetSpecificStudentsDF(df, column, columnValue)) self.OrderListOfDFStudents = self.GetSpecificStudentsDF(df, column, columnValue) def OrderIndexedDFBy(self, df:DataFrame, firstField:str, secondField:str, thirdField:str)->DataFrame: - """[summary] - Order the dataframe by the specified fields + """[summary]\n + Order the dataframe by the specified fields.\n Args: - df (DataFrame): [The dataframe to order] - firstField (str): [The first field to order] - secondField (str): [The second field to order] - thirdField (str): [The third field to order] + df (DataFrame): [The dataframe to order].\n + firstField (str): [The first field to order].\n + secondField (str): [The second field to order].\n + thirdField (str): [The third field to order].\n Returns: - [DataFrame]: [The dataframe ordered by the three fields in the specified order] + [DataFrame]: [The dataframe ordered by the three fields in the specified order].\n """ sortedDF = df.sort_values(by=[firstField, secondField, thirdField], ascending=[True, True, True]) return sortedDF - #* USED def ConfigUniqueValuesInColumn(self, column:str): - """[summary] - Get the unique values in the specified column and sort them in alphabetical order ASC. + """[summary]\n + Get the unique values in the specified column and sort them in alphabetical order ASC.\n Args: - column (str): [The column to filter] + column (str): [The column to filter].\n Returns: - [list]: [The unique values in the specified column] + [list]: [The unique values in the specified column].\n """ self.UniqueColumns = self.MainDataFrame[column].unique() self.UniqueColumns.sort() - #* USED def CreateJsonOfEveryDF(self): + """[summary]\n + Create a json file for every dataframe.\n + """ + for i in range(len(self.OrderListOfDFStudents)): frame:DataFrame = self.OrderListOfDFStudents[i] name = frame.at[frame.index.values[0], self.ConfigsJsonValues['Course']] filename:str = f'{name}.json' frame.to_json(f'{filename}', orient='table', indent=4, force_ascii=True) - ##### End Methods ##### + #####? End Methods ##### #####* Main Method ##### + def ConfigurateDataFrame(self, columnValue:str)->None: + """[summary]\n + Configurate the dataframe with the specified column value.\n + Args: + columnValue (str): [The column value to configurate].\n + """ + #* Gets the unique values of the column 'columnValue' [Division] self.ConfigUniqueValuesInColumn(columnValue) #* For each unique value of the column 'columnValue' [Division] #* Creates a list of dataframes with the students that have the specified value in the column 'columnValue' [Division] for unique in self.UniqueColumns: - self.GetListDFStudentsBy(self.MainDataFrame, columnValue, unique) + self.CreateListDFStudentsBy(self.MainDataFrame, columnValue, unique) self.CreateJsonOfEveryDF() #####* End Main Method ##### - diff --git a/GetData_Mod/DataManager.py b/GetData_Mod/DataManager.py index b3e4469..14efb93 100644 --- a/GetData_Mod/DataManager.py +++ b/GetData_Mod/DataManager.py @@ -16,181 +16,242 @@ # along with this program. If not, see . import os + import requests -from PrintMessage_Mod.CloneMessenger import CloneMessenger as CM from DataFrameHandler_Mod.DFHandler import DataFrameHandler as DFH - -import pandas as pd from pandas import DataFrame +from PrintMessage_Mod.CloneMessenger import CloneMessenger as CM class DataManager: #!TODO: Implement Pandas - """[summary] - Class in charge of the file management to read and process the - data of the students in order to clone their repositorys. + """[summary]\n + Class in charge of the file management to read and process the \n + data of the students in order to clone their repositorys.\n Returns: - [class]: [DataManager] + [class]: [DataManager].\n """ + #########? START ATTRIBUTES ######### __configAPIURL = '' __name = '' __version = '' __commands: list = [] - __Messenger: CM = None + __Messenger: CM = CM() __studentsInfo: DataFrame = DataFrame() + __APIResponse = None + __cloningMessages:list = [] + ########? END ATTRIBUTES ######### def __init__(self): pass def InitialConfig(self, name:str, version:str, APIURL:dict): - #self.SetFilename(fileName) + """[summary]\n + Initialize the config of the class.\n + Args: + name (str): [The name of the program].\n + version (str): [The version of the program].\n + APIURL (dict): [The API URL of the program].\n + """ self.SetAppName(name) self.SetAppVersion(version) self.SetAPIURL(APIURL) + self.APIResponse = self.GetAPIURL() - #* SETTERS + #########? SETTERS ######### def SetFilename(self, fileName:str)->None: - """[summary] - Set the name of the file to read + """[summary]\n + Set the name of the file to read.\n Args: - fileName (str): [The name of the file to read] + fileName (str): [The name of the file to read].\n """ self.fileName = fileName def SetAppName(self, name:str)->None: - """[summary] - Set the name of the file + """[summary]\n + Set the name of the file.\n Args: - name (str): [The name of the file] + name (str): [The name of the file].\n """ self.__name = name def SetAppVersion(self, version:str)->None: - """[summary] - Set the version of the file + """[summary]\n + Set the version of the file.\n Args: - version (str): [The version of the file] + version (str): [The version of the file].\n """ self.__version = version def SetAPIURL(self, api:dict)->None: - """[summary] - Set the URL of the API - Args: - api (str): [The URL of the API] - "URL": "https://api.github.com/repos", - "USER": "CaidevOficial", - "REPO": "Python_Udemy_DataManipulation", - "BRANCH": "main" + """[summary]\n + Set the URL of the API.\n + Args:\n + api (str): [The URL of the API]\n + "URL": "https://api.github.com/repos",\n + "USER": "CaidevOficial",\n + "REPO": "Python_Udemy_DataManipulation",\n + "BRANCH": "main" \n + Example: "https://api.github.com/repos/CaidevOficial/Python_Udemy_DataManipulation/commits/main".\n """ - self.__configAPIURL = f'{api["URL"]}/{api["USER"]}/{api["REPO"]}/{api["BRANCH"]}' + self.__configAPIURL = f'{api["URL"]}/{api["USER"]}/{api["REPO"]}/commits/{api["BRANCH"]}' def AddComand(self, command:str)->None: - """[summary] - Add a command to the list of the commands + """[summary]\n + Add a command to the list of the commands.\n Args: - command (str): [The command to add] + command (str): [The command to add].\n """ self.__commands.append(command) def SetStudentsDF(self, students: DataFrame)->None: - """[summary] - Sets the dataframe of students to work with. + """[summary]\n + Sets the dataframe of students to work with.\n Args: - students (DataFrame): The dataframe of students to work + students (DataFrame): The dataframe of students to work.\n """ self.__studentsInfo = students - #* GETTERS + ########? GETTERS ######### def GetCommands(self)->list: - """[summary] - Get the commands to clone the repositories of the students + """[summary]\n + Get the commands to clone the repositories of the students.\n Returns: - list: [The list of the commands to execute] + list: [The list of the commands to execute].\n """ return self.__commands - @property - def Commands(self): - """[summary] - Get the commands to clone the repositories of the students - Returns: - list: [The list of the commands to execute] - """ - return self.__commands - - @Commands.setter - def Commands(self, commands:list): - """[summary] - Set the commands to clone the repositories of the students - Args: - commands (list): [The list of the commands to execute] - """ - self.__commands = commands - def GetAppName(self)->str: - """[summary] - Get the name of the application + """[summary]\n + Get the name of the application.\n Returns: - str: [The name of the application] + str: [The name of the application].\n """ return self.__name def GetAppVersion(self)->str: - """[summary] - Get the version of the application + """[summary]\n + Get the version of the application.\n Returns: - str: [The version of the application] + str: [The version of the application].\n """ return self.__version def GetFilename(self)->str: - """[summary] - Get the name of the file + """[summary]\n + Get the name of the file.\n Returns: - str: [The name of the file] + str: [The name of the file].\n """ return self.fileName def GetAPIURL(self)->str: - """[summary] - Get the URL of the API + """[summary]\n + Get the URL of the API.\n Returns: - str: [The URL of the API] + str: [The URL of the API].\n """ return self.__configAPIURL - def GetDate(self, api:str)->str: - """[summary] - Get the date from the API - Args: - api (str): [The link of the API to consume] + def GetDate(self)->str: + """[summary]\n + Get the date from the API.\n Returns: - str: [The date formatted without the dashes] + str: [The date formatted without the dashes].\n """ - date = requests.get(api) - date = date.json()["commit"]["author"]["date"] + date = self.APIResponse.json()["commit"]["author"]["date"] date = date[:10] return date.replace("-","") def GetStudentsDF(self)->DataFrame: - """[summary] - Gets the Students DataFrame of the class to work with. + """[summary]\n + Gets the Students DataFrame of the class to work with.\n Returns: - DataFrame: The Actual DataFrame of the students + DataFrame: The Actual DataFrame of the students.\n """ return self.__studentsInfo + #########? END GETTERS ######### + + #########? PROPERTIES ######### + + @property + def Messenger(self)->CM: + """[summary]\n + Get the Messenger of the class.\n + Returns: + CM: [The Messenger of the class].\n + """ + return self.__Messenger + + @property + def Commands(self): + """[summary]\n + Get the commands to clone the repositories of the students.\n + Returns: + list: [The list of the commands to execute].\n + """ + return self.__commands + + @Commands.setter + def Commands(self, commands:list): + """[summary]\n + Set the commands to clone the repositories of the students.\n + Args: + commands (list): [The list of the commands to execute].\n + """ + self.__commands = commands + + @property + def APIResponse(self)->str: + """[summary]\n + Get the API Response.\n + Returns: + str: [The API Response].\n + """ + return self.__APIResponse + + @APIResponse.setter + def APIResponse(self, APILink:str): + """[summary]\n + Set the API Response by sending a request trough the API link.\n + Args: + APILink (str): [The Link of the API].\n + """ + self.__APIResponse = requests.get(APILink) + + @property + def CloningMessages(self)->list: + """[summary]\n + Get the list of the cloning messages.\n + Returns: + list: [The list of the cloning messages].\n + """ + return self.__cloningMessages + + @CloningMessages.setter + def CloningMessages(self, cloningMessage:str): + """[summary]\n + Adds a message to the cloning messages.\n + Args: + cloningMessage (str): [The message to add].\n + """ + self.__cloningMessages.append(cloningMessage) + + ########? END PROPERTIES ######### + + #########? METHODS ######### + def NormalizeURL(self, url:str)->str: - """[summary] - Normalize the URLs of the git's repositorys of the students, - adding the .git at the end if it is not already there + """[summary]\n + Normalize the URLs of the git's repositorys of the students,\n + adding the .git at the end if it is not already there.\n Args: - url (str): [The crudal url of the git's repository] + url (str): [The crudal url of the git's repository].\n Returns: - str: [The normalized url] + str: [The normalized url].\n """ if not ".git" in url: url = url.replace('\n','') @@ -198,117 +259,101 @@ def NormalizeURL(self, url:str)->str: return url.replace("\\n","") def NormalizeCourse(self, course:str)->str: - """[summary] - Normalize the course name, removing the spaces. + """[summary]\n + Normalize the course name, removing the spaces.\n Args: - course (str): [The course name] + course (str): [The course name].\n Returns: - str: [The normalized course name] + str: [The normalized course name].\n """ return course.replace(' - ', '-').replace(" ","_") def FormatFullnameDate(self, surname:str, name:str)->str: - """[summary] - Format the surname of the student, removing the spaces and replacing them with _ + """[summary]\n + Format the surname of the student, removing the spaces and replacing them with '_'\n Args: - surname (str): [The surname of the student] - name (str): [The name of the student] - date (str): [The date of the student] + surname (str): [The surname of the student].\n + name (str): [The name of the student].\n + date (str): [The date of the student].\n Returns: - str: [The formatted Fullname like this: surname_name_date] + str: [The formatted Fullname like this: surname_name_date].\n """ surname = surname.replace(",","_").replace(" ","").replace("\n","") name = name.replace(",","_").replace(" ","").replace("\n","") - return f'{surname}_{name}_{self.GetDate(self.GetAPIURL())}' + return f'{surname}_{name}_{self.GetDate()}' def FormatCourse(self, fieldList:str)->str: - """[summary] - Format the course of the student, removing the line jumps and replacing them with _ + """[summary]\n + Format the course of the student, removing the line jumps and replacing them with '_'\n Args: - fieldList (str): [The field of the course. It is the second field of the csv file] + fieldList (str): [The field of the course. It is the second field of the csv file].\n Returns: - str: [The formatted course] + str: [The formatted course].\n """ - return self.NormalizeCourse(fieldList[3].replace("\n","")) - - # def MakeCloneCommands(self, surname:str, course:str, git:str)->None: - # """[summary] - # Make the commands to clone the repositories of the students - # Args: - # surname (str): [The surname of the student] - # course (str): [The course of the student] - # git (str): [The url of the git's repository] - # """ - # command = f'git clone {git} {course}//{surname}' - # self.AddComand(command) - - #def MakeCloneCommands(self, dfHandler:DataFrame, surname:str, name:str, course:str, git:str)->None: - def MakeCloneCommands(self, dfHandler: DFH, df: DataFrame, configJsonFields: dict)->None: - """[summary] - Make the commands to clone the repositories of the students + return self.NormalizeCourse(fieldList.replace("\n","")) + + def MakeCloneCommands(self, dfHandler: DFH)->None: + """[summary]\n + Make the commands to clone the repositories of the students.\n Args: - surname (str): [The surname of the student] - course (str): [The course of the student] - git (str): [The url of the git's repository] + surname (str): [The surname of the student].\n + course (str): [The course of the student].\n + git (str): [The url of the git's repository].\n """ - for index, row in dfHandler.iterrows(): - courseStr = self.NormalizeCourse(self.FormatCourse(row[configJsonFields['Course']])) - surnameStr = row[configJsonFields['Surname']] - nameStr = row[configJsonFields['Name']] - command = f"git clone {self.NormalizeURL(row[configJsonFields['GitLink']])} {courseStr}//{self.FormatFullnameDate(surnameStr, nameStr)}" - self.AddComand(command) - + for frame in dfHandler.OrderListOfDFStudents: + self.MakeCloneCommandsForDF(frame, dfHandler) + def MakeCloneCommandsForDF(self, df: DataFrame, dfHandler: DFH)->None: + """[summary]\n + Make the commands to clone the repositories of the students.\n + Args: + df (DataFrame): [The DataFrame with the students information].\n + """ + for i in df.index: + crudeCourse = df[dfHandler.ConfigsJsonValues['Course']][i] + courseStr = self.NormalizeCourse(self.FormatCourse(crudeCourse)) + surnameStr = df[dfHandler.ConfigsJsonValues['Surname']][i] + nameStr = df[dfHandler.ConfigsJsonValues['Name']][i] + message = f"Cloning {surnameStr}, {nameStr}'s repository from {crudeCourse}" + command = f"git clone {self.NormalizeURL(df[dfHandler.ConfigsJsonValues['GitLink']][i])} {courseStr}//{self.FormatFullnameDate(surnameStr, nameStr)}" + self.AddComand(command) + self.CloningMessages = message - def ExecuteCommands(self)->None: - """[summary] - Execute the commands to clone the repositories of the students + def ExecuteCommands(self, cloneMessenger: CM)->None: + """[summary]\n + Execute the commands to clone the repositories of the students.\n Args: - commandList (list): [The list of the commands to execute] + commandList (list): [The list of the commands to execute].\n """ + commandList = [x.strip() for x in self.Commands] + messages = [x.strip() for x in self.CloningMessages] + for command in commandList: + cloneMessenger.SetMessage(messages[commandList.index(command)]) + cloneMessenger.PrintMessage() os.system(command) - #!TODO: Implement Pandas - def OpenFile(self)->None: - """[summary] - Open the file and get the data + def CloneRepositories(self, DfH: DFH, )->None: + """[summary]\n + Open the file and get the data.\n """ - #file = pd.read_csv(self.fileName) appInfo = f'{self.GetAppName()} - {self.GetAppVersion()}' - self.__Messenger = CM(appInfo) - self.__Messenger.PrintMessage() + self.Messenger.SetMessage(appInfo) + self.Messenger.PrintMessage() try: - with open(self.GetFilename(), 'r') as f: - lines:list = f.readlines()[1::] - for line in lines: - fieldList = line.split(',') - fieldList = [x.strip().replace('"', '') for x in fieldList] - - if len(fieldList)>=6: - message = f'Cloning repository of {fieldList[2]}, {fieldList[1]}' - self.__Messenger = CM(message) - studentGitUrl = fieldList[6] - api = self.GetAPIURL() - date = self.GetDate(api) - - self.__Messenger.PrintMessage() - - self.MakeCloneCommands( - self.FormatSurname(fieldList, date), - self.FormatCourse(fieldList), - self.NormalizeURL(studentGitUrl) - ) - self.ExecuteCommands(self.GetCommands()) - self.__Messenger.SetMessage('All Repositories have been cloned!') - self.__Messenger.PrintMessage() + #? Create git Clone commands + self.MakeCloneCommands(DfH) + + #? Execute the commands + self.ExecuteCommands(self.Messenger) + + # self.__Messenger.SetMessage('All Repositories have been cloned!') + # self.__Messenger.PrintMessage() except Exception as e: - self.__Messenger = CM(e) + self.Messenger.SetMessage(f'Exception: {e.args}') self.__Messenger.PrintMessage() - #def DoMagic(self, dfHandler: DFH): - - + #########? END METHODS ######### diff --git a/GithubCloner2022.py b/GithubCloner2022.py index d040dae..1b0ebaf 100644 --- a/GithubCloner2022.py +++ b/GithubCloner2022.py @@ -15,7 +15,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import json from GetData_Mod.DataManager import DataManager as DM import pandas as pd @@ -24,56 +23,39 @@ ##########? Start Basic Configuration ########## filename = 'Github_Repositories.csv' name = 'Github Repository Cloner' -version = '[V1.1.04]' +version = '[V1.2.01]' fileConfigName = 'API_Info.json' ##########? End Basic Configuration ########## - - try: + ##########? Start Initialization ########## JsonFile = pd.read_json(f"./{fileConfigName}", orient='records') JsonAPI = JsonFile['Github'] JsonDFConfigs = JsonFile['DataFrame']['Fields'] - + ##########? End Initialization ############ + + ##########? Start Objects Instances ########## Handler = DfH() Manager = DM() - Manager.InitialConfig(name, version, JsonAPI) + ##########? End Objects Instances ########## + ##########? Start DataManager Configuration ########## + Manager.InitialConfig(name, version, JsonAPI) + ##########? End DataManager Configuration ########## - ########## Start DataFrame Configuration ########## + ##########? Start DataFrame Configuration ########## #* Reads the 'csv' File to get the dataframe df = pd.read_csv(filename) - #? SETTINGS OF DATAFRAMEHANDLER #* Sets the Main DF to the class to handle it Handler.MainDataFrame = df Handler.ConfigsJsonValues = JsonDFConfigs Handler.ConfigurateDataFrame(Handler.ConfigsJsonValues['Course']) + ##########? End DataFrame Configuration ########## - ########## TEST UNIQUE VALUES ########## #* TEST PASSED - print('UNIQUE VALUES\n') - for unique in Handler.UniqueColumns: - print(unique) - ########## End TEST UNIQUE VALUES ########## - - ########## TEST LIST OF STUDENTS ########## #* TEST PASSED - print('\nLIST OF STUDENTS\n') - studList = Handler.OrderListOfDFStudents - print(studList) - ########## END TEST LIST OF STUDENTS ########## + ##########? Start Initialize DataManager ########## + Manager.CloneRepositories(Handler) + ###########? End Initialize DataManager ########### except Exception as e: - #print(f'File not found: {fileConfigName}') - print(f'Error: {e}') - -# try: -# with open(fileConfigName, 'r') as APIFILE: -# JsonFile = pd.read_json(APIFILE)["Github"] -# print(JsonFile) -# APIURL = f'{JsonFile["URL"]}/{JsonFile["USER"]}/{JsonFile["REPO"]}/commits/{JsonFile["BRANCH"]}' -# print(APIURL) - -# manager = DM(filename, name, version, APIURL) -# manager.OpenFile() -# except FileNotFoundError: -# print(f'File not found: {fileConfigName}') + print(f'Exception: {e.args}') \ No newline at end of file diff --git a/PrintMessage_Mod/CloneMessenger.py b/PrintMessage_Mod/CloneMessenger.py index cd98fc4..b1e38ba 100644 --- a/PrintMessage_Mod/CloneMessenger.py +++ b/PrintMessage_Mod/CloneMessenger.py @@ -15,39 +15,49 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import logging - class CloneMessenger: - """_summary_ - Class to print messages in the console + """[summary] + Class to print messages in the console.\n Returns: - class: CloneMessenger + class: [CloneMessenger].\n """ + #######? START ATTRIBUTES ####### __message:str = '' + #######? END ATTRIBUTES ####### - def __init__(self, message:str) -> None: - self.SetMessage(message) + def __init__(self) -> None: + pass def SetMessage(self, message:str) -> None: - """[_summary] - Sets the message of the class + """[summary]\n + Sets the message of the class.\n Args: - message (str): The message to be printed in the console + message (str): The message to be printed in the console.\n """ self.__message = message def GetMessage(self) -> str: - """_summary_ - Gets the message of the class + """[summary]\n + Gets the message of the class.\n Returns: - str: Message of the class to be printed in the console. + str: Message of the class to be printed in the console.\n """ return self.__message + ############? START METHODS ############ + + def InitializeMessenger(self, message:str) -> None: + """[summary]\n + Initializes the class with a message.\n + Args: + message (str): The message to be printed in the console.\n + """ + self.SetMessage(message) + def PrintMessage(self) -> None: - """[summary] - Creates a string of symbols of the same length of the message and - prints them in the console. + """[summary]\n + Creates a string of symbols of the same length of the message and\n + prints them in the console.\n """ symbols = self.GenerateSymbols() print( @@ -58,10 +68,11 @@ def PrintMessage(self) -> None: ) def GenerateSymbols(self) -> str: - """[summary] - Generates a string of symbols of the same length of the message of the class + """[summary]\n + Generates a string of symbols of the same length of the message of the class.\n Returns: - str: String of symbols of the same length of the message of the class + str: String of symbols of the same length of the message of the class.\n """ return ''.join(['#' for i in range(len(self.GetMessage()))]) + ############? END METHODS ############ From 4b7a773197929835260b60e5226ee0d8a28a6a49 Mon Sep 17 00:00:00 2001 From: CaidevOficial Date: Wed, 16 Feb 2022 01:55:13 -0300 Subject: [PATCH 3/9] Delete test.py --- test.py | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 test.py diff --git a/test.py b/test.py deleted file mode 100644 index a25686e..0000000 --- a/test.py +++ /dev/null @@ -1,5 +0,0 @@ - -for i in range(0,10): - if i%2==0: - continue - print(i) \ No newline at end of file From 187148458613521ba458cfc88f1253be56fa4817 Mon Sep 17 00:00:00 2001 From: CaidevOficial Date: Wed, 16 Feb 2022 01:55:34 -0300 Subject: [PATCH 4/9] Version Finished --- GetData_Mod/DataManager.py | 32 +++- GithubCloner2022.py | 3 +- Media/Directories_tree.png | Bin 0 -> 6449 bytes Media/Messages.png | Bin 0 -> 21359 bytes README.md | 301 ++++++++++++++++++++++++++++++++++++- 5 files changed, 329 insertions(+), 7 deletions(-) create mode 100644 Media/Directories_tree.png create mode 100644 Media/Messages.png diff --git a/GetData_Mod/DataManager.py b/GetData_Mod/DataManager.py index 14efb93..0609105 100644 --- a/GetData_Mod/DataManager.py +++ b/GetData_Mod/DataManager.py @@ -35,6 +35,7 @@ class DataManager: __configAPIURL = '' __name = '' __version = '' + __author = '' __commands: list = [] __Messenger: CM = CM() __studentsInfo: DataFrame = DataFrame() @@ -45,16 +46,18 @@ class DataManager: def __init__(self): pass - def InitialConfig(self, name:str, version:str, APIURL:dict): + def InitialConfig(self, name:str, version:str, author:str, APIURL:dict): """[summary]\n Initialize the config of the class.\n Args: name (str): [The name of the program].\n version (str): [The version of the program].\n + author (str): [The author of the program].\n APIURL (dict): [The API URL of the program].\n """ self.SetAppName(name) self.SetAppVersion(version) + self.AppAuthor = author self.SetAPIURL(APIURL) self.APIResponse = self.GetAPIURL() @@ -177,6 +180,24 @@ def GetStudentsDF(self)->DataFrame: #########? PROPERTIES ######### + @property + def AppAuthor(self)->str: + """[summary]\n + Get the author of the application.\n + Returns: + str: [The author of the application].\n + """ + return self.__author + + @AppAuthor.setter + def AppAuthor(self, author:str)->None: + """[summary]\n + Set the author of the application.\n + Args: + author (str): [The author of the application].\n + """ + self.__author = author + @property def Messenger(self)->CM: """[summary]\n @@ -339,7 +360,7 @@ def CloneRepositories(self, DfH: DFH, )->None: """[summary]\n Open the file and get the data.\n """ - appInfo = f'{self.GetAppName()} - {self.GetAppVersion()}' + appInfo = f'{self.GetAppName()} - {self.GetAppVersion()} by {self.AppAuthor}' self.Messenger.SetMessage(appInfo) self.Messenger.PrintMessage() @@ -350,10 +371,11 @@ def CloneRepositories(self, DfH: DFH, )->None: #? Execute the commands self.ExecuteCommands(self.Messenger) - # self.__Messenger.SetMessage('All Repositories have been cloned!') - # self.__Messenger.PrintMessage() + self.Messenger.SetMessage('All Repositories have been cloned!') + self.Messenger.PrintMessage() + except Exception as e: self.Messenger.SetMessage(f'Exception: {e.args}') - self.__Messenger.PrintMessage() + self.Messenger.PrintMessage() #########? END METHODS ######### diff --git a/GithubCloner2022.py b/GithubCloner2022.py index 1b0ebaf..54a7737 100644 --- a/GithubCloner2022.py +++ b/GithubCloner2022.py @@ -24,6 +24,7 @@ filename = 'Github_Repositories.csv' name = 'Github Repository Cloner' version = '[V1.2.01]' +author = '[FacuFalcone - CaidevOficial]' fileConfigName = 'API_Info.json' ##########? End Basic Configuration ########## @@ -40,7 +41,7 @@ ##########? End Objects Instances ########## ##########? Start DataManager Configuration ########## - Manager.InitialConfig(name, version, JsonAPI) + Manager.InitialConfig(name, version, author, JsonAPI) ##########? End DataManager Configuration ########## ##########? Start DataFrame Configuration ########## diff --git a/Media/Directories_tree.png b/Media/Directories_tree.png new file mode 100644 index 0000000000000000000000000000000000000000..b137e3008b4f7f4d4c51e92ee09e14f0f9f78dfa GIT binary patch literal 6449 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 baK<92F0WUS(fR58nu7wQWvGc$e;oN=x!Hpb literal 0 HcmV?d00001 diff --git a/Media/Messages.png b/Media/Messages.png new file mode 100644 index 0000000000000000000000000000000000000000..219d29a18781a0073231da29fd18f2c8358840ac GIT binary patch literal 21359 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 S?g2_NB@mXj7M14bBL4uE;I)td literal 0 HcmV?d00001 diff --git a/README.md b/README.md index da02e7e..bf0eb4c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,154 @@ + + + + + +
+ Universidad Tecnológica Nacional + + Python Logo +
+
+ + # Repository Cloner -It allows you to clone repositories from github in bulk and store them in specific directories from a csv file. +The program it allows you to clone repositories from github in bulk and store them in specific directories from a csv file. +Aditionally it saves the data of every student & course into a json with the name of the course. + +All this is possible by the use of [Pandas library](https://pandas.pydata.org/docs/index.html) and dataframes to manipulate the data, sorting and filtering the courses, students and repositories to get a list of dataframes, one for each course with all the students data sorted by course, surname and name. + +At the end of the execution, the program will download the files of every student and save them in the directory of the course that they belong to. + +like this: + + + + + + + + + + +
Courses & students directories
+ Directories Image +
+ +And the JSON generates with the data of the students and courses will be like this: + +```json +{ + "schema":{ + "fields":[ + { + "name":"index", + "type":"integer" + }, + { + "name":"Marca temporal", + "type":"string" + }, + { + "name":"Nombre\/s", + "type":"string" + }, + { + "name":"Apellido\/s", + "type":"string" + }, + { + "name":"Divisi\u00f3n", + "type":"string" + }, + { + "name":"DNI \/ Legajo", + "type":"integer" + }, + { + "name":"E-Mail", + "type":"string" + }, + { + "name":"Link al repositorio", + "type":"string" + } + ], + "primaryKey":[ + "index" + ], + "pandas_version":"1.4.0" + }, + "data":[ + { + "index":5, + "Marca temporal":"2022\/02\/13 10:26:52 p.\u00a0m. GMT-3", + "Nombre\/s":"Artemisa", + "Apellido\/s":"Grecian God", + "Divisi\u00f3n":"1G - Professor 1 - Helper 1", + "DNI \/ Legajo":444444, + "E-Mail":"zeus@ray.com", + "Link al repositorio":"https:\/\/github.com\/caidevOficial\/Python_IEEE_Team14293.git" + }, + { + "index":3, + "Marca temporal":"2022\/02\/13 10:26:52 p.\u00a0m. GMT-3", + "Nombre\/s":"Zeus", + "Apellido\/s":"Grecian God", + "Divisi\u00f3n":"1G - Professor 1 - Helper 1", + "DNI \/ Legajo":444444, + "E-Mail":"zeus@ray.com", + "Link al repositorio":"https:\/\/github.com\/caidevOficial\/Python_IEEE_Team14293.git" + }, + { + "index":4, + "Marca temporal":"2022\/02\/13 10:26:52 p.\u00a0m. GMT-3", + "Nombre\/s":"Mercury", + "Apellido\/s":"Romane God", + "Divisi\u00f3n":"1G - Professor 1 - Helper 1", + "DNI \/ Legajo":222222, + "E-Mail":"neptune@notplanet.com", + "Link al repositorio":"https:\/\/github.com\/caidevOficial\/SPD2022_TPS.git" + }, + { + "index":0, + "Marca temporal":"2022\/02\/13 10:26:52 p.\u00a0m. GMT-3", + "Nombre\/s":"Neptune", + "Apellido\/s":"Romane God", + "Divisi\u00f3n":"1G - Professor 1 - Helper 1", + "DNI \/ Legajo":222222, + "E-Mail":"neptune@notplanet.com", + "Link al repositorio":"https:\/\/github.com\/caidevOficial\/SPD2022_TPS.git" + } + ] +} +``` +


+ +--- + +


+# Console Messages +Meanwhile the program is cloning the repositories, the console will show messages like showns below: + + + + + + + + + + +
Console Messages
+ Console Messages Image +
+ + +


+ +--- +


### File format * 1st Column: Date time. [it isn't used yet.] * 2nd Column: Student Name @@ -10,7 +158,19 @@ It allows you to clone repositories from github in bulk and store them in specif * 6th Column: Student E-mail. [it isn't used yet.] * 7th Column: Repository Name to download (It could skip the '.git' part) +Like this: + +``` +"Marca temporal","Nombre/s","Apellido/s","División","DNI / Legajo","E-Mail","Link al repositorio" +"2022/02/13 10:26:52 p. m. GMT-3","Neptune","Romane God","1G - Professor 1 - Helper 1","222222","neptune@notplanet.com","https://github.com/caidevOficial/SPD2022_TPS.git" +"2022/02/13 10:26:52 p. m. GMT-3","Poseidon","Grecian God","1F - Professor 2 - Helper 2","333333","poseidon@sea.com","https://github.com/caidevOficial/Python_ITBA_IEEE.git" +"2022/02/13 10:26:52 p. m. GMT-3","Hades","Grecian God","1F - Professor 2 - Helper 2","111111","Hades@underworld.com","https://github.com/caidevOficial/CaidevOficial.git" +``` +


+ +--- +


# Configuration In order to use this Cloner, you should configure the file [apiInfo.json](apiInfo.json) with your Github API's information as shown below. @@ -21,6 +181,16 @@ In order to use this Cloner, you should configure the file [apiInfo.json](apiInf "USER": "Your_Github_User", "REPO": "Your_Repository_To_Get_The_Date_Of_Last_Commit", "BRANCH": "The_Branch_Of_The_Last_Commit_lowercase" + }, + "DataFrame": { + "Fields": { + "Name": "Name_For_Column_Of_Names", + "Surname": "Name_For_Column_Of_Surnames", + "Course": "Name_For_Column_Of_Courses", + "ID": "Name_For_Column_Of_Students_ID", + "Email": "Name_For_Column_Of_Emails", + "GitLink": "Name_For_Column_Of_Links_To_Repositories" + } } ] ``` @@ -34,6 +204,16 @@ for example: "USER": "CaidevOficial", "REPO": "Python_Udemy_DataManipulation", "BRANCH": "main" + }, + "DataFrame": { + "Fields": { + "Name": "Nombre/s", + "Surname": "Apellido/s", + "Course": "División", + "ID": "DNI / Legajo", + "Email": "E-Mail", + "GitLink": "Link al repositorio" + } } ] ``` @@ -46,7 +226,80 @@ https://api.github.com/repos/CaidevOficial/Python_Udemy_DataManipulation/commits This way the program will take the 'Date' of the last commit of the branch 'main' and will use it to create the folder with the name of the repository. Obviously, the repository MUST BE PUBLIC, otherwise the program won't be able to access its API. +Regarding the 'DataFrame' Key, al the keys inside are configured to use them with a 'csv' file with at least theses columns. [Could have more columns, but it's not necessary for us.] + +For our example, the columns of the csv file are: + + + + + + + + + + + + + + + +
Nombre/sApellido/sDivisiónDNI / LegajoE-MailLink al repositorio
+

Poseidon

+
+

Grecian God

+
+

1F

+
+

123456789

+
+

poseidon@grecianGod.olympus

+
+

https://github.com/caidevOficial/CaidevOficial.git

+
+


+ +--- +


+ + +

Technologies used. 📌

+ + + + + + + + + + + + + + + + + + + +
+ Pyhton Logo +
Python
+ Pandas Logo +
Pandas
+ NumPy Logo +
Numpy
+ MatPlotLib Logo +
MatPlotLib
+ VSCode Logo +
VSCode
+


+ +--- + +


@@ -96,3 +349,49 @@ This way the program will take the 'Date' of the last commit of the branch 'main
+


+ +--- + +


+ + + + + + + + + + + + + + + + + + + + + + +

Where to find me: 🌎

+ Facu +
🤴 Facu Falcone - Junior Developer
+ + GitHub + +
+ + LinkedIn + +
+ + Invitame un café en cafecito.app + +
+ + Buy Me a Coffee at ko-fi.com + +
\ No newline at end of file From 23b776f522556c654c4c17cb68c2a3f26879f1ae Mon Sep 17 00:00:00 2001 From: CaidevOficial Date: Wed, 16 Feb 2022 02:03:44 -0300 Subject: [PATCH 5/9] Update Readme --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index bf0eb4c..bc25c4a 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,17 @@
+
+

Pisces♓ | Developer👨‍💻 | Pythonpython | GCP GCP | Java java | C# csharp | Dreamer 💖 | Teacher👨‍🏫| A bit nerd🤓

+
+

Programming Student & Assistant Professor at the National Technological University [UTN] 👨‍💻

+

Backend programmer at Accenture 👨‍💻

+
+


+--- +


# Repository Cloner The program it allows you to clone repositories from github in bulk and store them in specific directories from a csv file. Aditionally it saves the data of every student & course into a json with the name of the course. From 3364e8985eddb5c9da09736d429230f3fd1df497 Mon Sep 17 00:00:00 2001 From: CaidevOficial Date: Wed, 16 Feb 2022 02:05:26 -0300 Subject: [PATCH 6/9] Update Readme - Add Trophy --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index bc25c4a..49aa215 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,18 @@

Programming Student & Assistant Professor at the National Technological University [UTN] 👨‍💻

Backend programmer at Accenture 👨‍💻

+ +![](https://hit.yhype.me/github/profile?user_id=12877139) + +

+ caidevoficial +

+ +

+ + caidevoficial + +




--- From aae9191f24903921c1126c1e7554e0e5e85952e2 Mon Sep 17 00:00:00 2001 From: CaidevOficial Date: Wed, 16 Feb 2022 02:06:27 -0300 Subject: [PATCH 7/9] Update Readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 49aa215..7ee5732 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@

Pisces♓ | Developer👨‍💻 | Pythonpython | GCP GCP | Java java | C# csharp | Dreamer 💖 | Teacher👨‍🏫| A bit nerd🤓


-

Programming Student & Assistant Professor at the National Technological University [UTN] 👨‍💻

+

Programming Student & Assistant Professor at the
+ National Technological University [UTN] 👨‍💻

Backend programmer at Accenture 👨‍💻

From fd127574cf69da84f023981b37cf8a9112fe5cdd Mon Sep 17 00:00:00 2001 From: CaidevOficial Date: Wed, 16 Feb 2022 02:07:40 -0300 Subject: [PATCH 8/9] Update Readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7ee5732..345853c 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@

Pisces♓ | Developer👨‍💻 | Pythonpython | GCP GCP | Java java | C# csharp | Dreamer 💖 | Teacher👨‍🏫| A bit nerd🤓


-

Programming Student & Assistant Professor at the
+

📌 Programming Student & Assistant Professor at the
National Technological University [UTN] 👨‍💻

-

Backend programmer at Accenture 👨‍💻

+

📌 Backend programmer at Accenture 👨‍💻

![](https://hit.yhype.me/github/profile?user_id=12877139) From 2422af6b3fa38d5f412ca43c534db98f526b2bf5 Mon Sep 17 00:00:00 2001 From: CaidevOficial Date: Wed, 16 Feb 2022 02:13:48 -0300 Subject: [PATCH 9/9] Upgrade to V2.0.1 - Pandas --- GithubCloner2022.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GithubCloner2022.py b/GithubCloner2022.py index 54a7737..9f097cf 100644 --- a/GithubCloner2022.py +++ b/GithubCloner2022.py @@ -23,7 +23,7 @@ ##########? Start Basic Configuration ########## filename = 'Github_Repositories.csv' name = 'Github Repository Cloner' -version = '[V1.2.01]' +version = '[V2.0.1]' author = '[FacuFalcone - CaidevOficial]' fileConfigName = 'API_Info.json' ##########? End Basic Configuration ##########