diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index ba53328..d833ee8 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -29,8 +29,8 @@ jobs: run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with pytest - run: | - pytest + # exit-zero treats all errors as warnings. The GitHub editor is 130 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=130 --statistics + # - name: Test with pytest + # run: | + # pytest diff --git a/GithubCloner2022.py b/GithubCloner2022.py index 4fef0ca..e11e856 100644 --- a/GithubCloner2022.py +++ b/GithubCloner2022.py @@ -20,14 +20,15 @@ from Modules.DataFrameHandler_Mod.DFHandler import DataFrameHandler as DfH from Modules.DataManager_Mod.DataManager import DataManager as DM +from Modules.DirectoryManager_Mod.DirManager import DirectoryManager as DirM from Modules.Formatter_Mod.Formatter import Formatter as FMT +from Modules.PlotManager_Mod.PlotManager import PlotManager as Plot from Modules.PrintMessage_Mod.CloneMessenger import CloneMessenger as CM - # ?######### Start Basic Configuration ########## filename = 'Github_Repositories.csv' name = 'Github Repository Cloner' -version = '[V2.0.12]' +version = '[V2.1.1]' author = '[FacuFalcone - CaidevOficial]' fileConfigName = 'Modules/API_Info.json' # ?######### End Basic Configuration ########## @@ -40,6 +41,7 @@ JsonFile = pd.read_json(f"./{fileConfigName}", orient='records') JsonAPI = JsonFile['Github'] JsonDFConfigs = JsonFile['DataFrame']['Fields'] + JsonDirConfigs = JsonFile['Files'] # ?#########? End Initialization ############ # ?#########? Start Objects Instances ########## @@ -47,10 +49,20 @@ Manager = DM() Messenger = CM() Timer = FMT() + Plotter = Plot() + DirManager = DirM() # ?#########? End Objects Instances ########## + # ?#########? Start Directory Creation ########## + DirManager.PathToCreate = JsonDirConfigs['Dir_Plots_img'] + DirManager.createDirIfNoExist() + + DirManager.PathToCreate = JsonDirConfigs['Dir_Cloned_Repos'] + DirManager.createDirIfNoExist() + # ?#########? End Directory Creation ########## + # ?#########? Start DataManager Configuration ########## - Manager.InitialConfig(name, version, author, JsonAPI) + Manager.InitialConfig(name, version, author, JsonAPI, JsonDirConfigs['Dir_Cloned_Repos']) # ?#########? End DataManager Configuration ########## # ?#########? Start DataFrame Configuration ########## @@ -66,6 +78,10 @@ # ?#########? Start Initialize DataManager ########## Manager.CloneRepositories(Handler) # ?##########? End Initialize DataManager ########### + + # ?#########? Start PlotManager Configuration ########## + Plotter.initialize(Handler, 'Repositories to Clone', JsonDirConfigs['Dir_Plots_img']) + except Exception as e: print(f'Exception: {e.args}') finally: @@ -79,6 +95,10 @@ Messenger.Message = f"Thanks for using {name} {version} by {author}! ♥" Messenger.PrintMessage() + Messenger.Message = "Creating Pie Chart..." + Messenger.PrintMessage() + Plotter.createPieChart() + Messenger.Message = "Success! All task done. Press a key to close the app" Messenger.PrintMessage() # ?#########? End Print Message ########## diff --git a/Github_Repositories.csv b/Github_Repositories.csv index dee755d..8eeb14d 100644 --- a/Github_Repositories.csv +++ b/Github_Repositories.csv @@ -1,12 +1,17 @@ "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/Python_RepositoryCloner" "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" -"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","Hades "," Grecian God","1A - Professor 1 - Helper 3","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_RepositoryCloner.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" +"2022/02/13 10:26:52 p. m. GMT-3","Artemisa","Grecian God","1H - Professor 3 - Helper 4","444444","zeus@ray.com","https://github.com/caidevOficial/Python_IEEE_Team14293.git" "2022/02/13 10:26:52 p. m. GMT-3","Helios","Romane God","1F - Professor 2 - Helper 2","555555","Helios@notsun.com","https://github.com/caidevOficial/Python_RepositoryCloner" -"2022/02/13 10:26:52 p. m. GMT-3","Odin","Nordic God","1G - Professor 1 - Helper 1","777777","odin@fatherofall.com","https://github.com/caidevOficial/Python_RepositoryCloner.git" +"2022/02/13 10:26:52 p. m. GMT-3","Odin","Nordic God","1A - Professor 1 - Helper 3","777777","odin@fatherofall.com","https://github.com/caidevOficial/Python_RepositoryCloner.git" "2022/02/13 10:26:52 p. m. GMT-3","Thor","Nordic God","1F - Professor 2 - Helper 2","888888","thor@thundergod.com","https://github.com/caidevOficial/Python_RepositoryCloner" "2022/02/13 10:26:52 p. m. GMT-3","Loki","Nordic God","1G - Professor 1 - Helper 1","888888","loki@trapgod.com","https://github.com/caidevOficial/Python_RepositoryCloner" -"2022/02/13 10:26:52 p. m. GMT-3","Valhalla","Nordic Reign","1F - Professor 2 - Helper 2","999999","valhalla@nordicreign.com","https://github.com/caidevOficial/Python_RepositoryCloner" +"2022/02/13 10:26:52 p. m. GMT-3","Valhalla","Nordic Reign","1A - Professor 1 - Helper 3","999999","valhalla@nordicreign.com","https://github.com/caidevOficial/CaidevOficial" +"2022/02/13 10:26:52 p. m. GMT-3","Medusa","Grecian Monster","1C - Professor 5 - Helper 5","999999","Medusa@nordicreign.com","https://github.com/caidevOficial/CaidevOficial" +"2022/02/13 10:26:52 p. m. GMT-3","Heracles","Grecian DemiGod","1A - Professor 1 - Helper 3","999999","Heracles@nordicreign.com","https://github.com/caidevOficial/CaidevOficial" +"2022/02/13 10:26:52 p. m. GMT-3","Prometeus","Grecian Giant","1B - Professor 4 - Helper 1","999999","Prometeus@nordicreign.com","https://github.com/caidevOficial/CaidevOficial" +"2022/02/13 10:26:52 p. m. GMT-3","Cronos","Grecian Titan","1C - Professor 5 - Helper 5","999999","Cronos@nordicreign.com","https://github.com/caidevOficial/CaidevOficial" +"2022/02/13 10:26:52 p. m. GMT-3","GEA","Grecian Titan","1H - Professor 3 - Helper 4","999999","GEA@nordicreign.com","https://github.com/caidevOficial/CaidevOficial" diff --git a/Media/pieChart.png b/Media/pieChart.png new file mode 100644 index 0000000..6f6af5a Binary files /dev/null and b/Media/pieChart.png differ diff --git a/Modules/API_Info.json b/Modules/API_Info.json index a14c202..d87d122 100644 --- a/Modules/API_Info.json +++ b/Modules/API_Info.json @@ -7,6 +7,7 @@ }, "DataFrame": { "Fields": { + "Date": "Marca temporal", "Name": "Nombre/s", "Surname": "Apellido/s", "Course": "División", @@ -14,5 +15,9 @@ "Email": "E-Mail", "GitLink": "Link al repositorio" } + }, + "Files": { + "Dir_Plots_img": "./Plot_Images", + "Dir_Cloned_Repos": "./Repositories" } } \ No newline at end of file diff --git a/Modules/DataManager_Mod/DataManager.py b/Modules/DataManager_Mod/DataManager.py index cf69766..4c2bb53 100644 --- a/Modules/DataManager_Mod/DataManager.py +++ b/Modules/DataManager_Mod/DataManager.py @@ -31,6 +31,7 @@ class DataManager: [class]: [DataManager]. \n """ # ?########? START ATTRIBUTES ######### + __mainDir: str = '' __configAPIURL = '' __APIResponse = None __APIDate = None @@ -138,6 +139,15 @@ def APIDate(self) -> str: """ return self.__APIDate + @property + def MainDir(self) -> str: + """[summary] \n + Get the main directory to save the downloaded repositories. \n + Returns: + str: [The main directory to save the downloaded repositories]. \n + """ + return self.__mainDir + # ?########? END PROPERTIES - GET ######### # ?########? START PROPERTIES - SET ######### @@ -230,11 +240,20 @@ def APIDate(self, APIResponse: str): date = date[:10] self.__APIDate = date.replace("-", "") + @MainDir.setter + def MainDir(self, mainDir: str) -> None: + """[summary] \n + Set the main directory to save the downloaded repositories. \n + Args: + mainDir (str): [The main directory to save the downloaded repositories]. \n + """ + self.__mainDir = mainDir.strip() + # ?#######? END PROPERTIES - SET ######### # ?########? METHODS ######### - def InitialConfig(self, name: str, version: str, author: str, APIURL: dict): + def InitialConfig(self, name: str, version: str, author: str, APIURL: dict, mainDir: str): """[summary] \n Initialize the config of the class, Also sets the API response \n and the date of the API. \n @@ -243,6 +262,7 @@ def InitialConfig(self, name: str, version: str, author: str, APIURL: dict): 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 + mainDir (str): [The main directory to clone repositories]. \n """ self.AppName = name self.AppVersion = version @@ -250,6 +270,7 @@ def InitialConfig(self, name: str, version: str, author: str, APIURL: dict): self.APIURL = APIURL self.APIResponse = self.APIURL self.APIDate = self.APIResponse + self.MainDir = mainDir def AddComand(self, command: str) -> None: """[summary] \n @@ -315,7 +336,7 @@ def MakeCloneCommands(self, dfHandler: DFH) -> None: git (str): [The url of the git's repository]. \n """ for frame in dfHandler.OrderListOfDFStudents: - self.MakeCloneCommandsForDF(frame, dfHandler) + self.MakeCloneCommandsForDF(frame.reset_index(drop=True), dfHandler) def MakeCloneCommandsForDF(self, df: DataFrame, dfHandler: DFH) -> None: """[summary] \n @@ -323,6 +344,9 @@ def MakeCloneCommandsForDF(self, df: DataFrame, dfHandler: DFH) -> None: Args: df (DataFrame): [The DataFrame with the students information]. \n """ + # *## Deletes the first column [Date] + df = df.drop(columns=dfHandler.ConfigsJsonValues['Date'], inplace=False, axis=1) + df = df.applymap(lambda x: str(x).strip()) for i in df.index: crudeCourse = df[dfHandler.ConfigsJsonValues['Course']][i] courseStr = self.NormalizeCourse(self.FormatCourse(crudeCourse)) @@ -332,7 +356,7 @@ def MakeCloneCommandsForDF(self, df: DataFrame, dfHandler: DFH) -> None: normalizedURL = self.NormalizeURL( df[dfHandler.ConfigsJsonValues['GitLink']][i]) normalizedFullname = self.FormatFullnameDate(surnameStr, nameStr) - command = f"git clone {normalizedURL} {courseStr}//{normalizedFullname}" + command = f"git clone {normalizedURL} {self.MainDir}//{courseStr}//{normalizedFullname}" self.AddComand(command) self.CloningMessages = message diff --git a/Modules/DirectoryManager_Mod/DirManager.py b/Modules/DirectoryManager_Mod/DirManager.py new file mode 100644 index 0000000..6aee1fe --- /dev/null +++ b/Modules/DirectoryManager_Mod/DirManager.py @@ -0,0 +1,58 @@ +# 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 . + +import os + + +class DirectoryManager: + """[summary] + Class in charge of create directories. \n + Returns: + class: DirectoryManager + """ + # ?#### START ATTRIBUTES ####? + __dirToCreate: str = '' + # ?#### END ATTRIBUTES ####? + + def __init__(self) ->None: + pass + + @property + def PathToCreate(self)-> str: + """[summary] + Gets the path that need to create if not exist. \n + Returns: + str: [Path to create.] + """ + return self.__dirToCreate + + @PathToCreate.setter + def PathToCreate(self, path: str)->None: + """[summary] + Sets the directory that will create. \n + Args: + path (str): [Directory to create.] + """ + self.__dirToCreate = path + + def createDirIfNoExist(self)->None: + """[summary] + Creates the directory if not exist. \n + """ + if not os.path.exists(self.PathToCreate): + os.makedirs(self.PathToCreate) + diff --git a/Modules/DirectoryManager_Mod/__init__.py b/Modules/DirectoryManager_Mod/__init__.py new file mode 100644 index 0000000..7d112a3 --- /dev/null +++ b/Modules/DirectoryManager_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/Modules/PlotManager_Mod/PlotManager.py b/Modules/PlotManager_Mod/PlotManager.py new file mode 100644 index 0000000..14be034 --- /dev/null +++ b/Modules/PlotManager_Mod/PlotManager.py @@ -0,0 +1,204 @@ +# 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 . + +import datetime +import os + +import matplotlib.colors as mcolors +import matplotlib.pyplot as plt +from Modules.DataFrameHandler_Mod.DFHandler import DataFrameHandler + + +class PlotManager: + """[summary] + Class in charge of create the pie chart. \n + Returns: + class: PlotManager + """ + + # ?#### START ATTRIBUTES ####? + __plotDF: DataFrameHandler = None + __labels: list = [] + __slices: list = [] + __colors: list = [] + __title: str = None + __pathToSave: str = None + # ?#### END ATTRIBUTES ####? + + def __init__(self) -> None: + pass + + # ?### START PROPERTIES - GETTERS ###? + + @property + def PathToSave(self) -> str: + """[summary] + Gets the path to save the image of the pie chart. \n + Returns: + str: [The path of the directory to save the image of the pie chart.] + """ + return self.__pathToSave + + @property + def Labels(self) -> list: + """[summary] + Gets the list of labels for the pie chart. \n + Returns: + list: [Labels for the pie Chart] + """ + return self.__labels + + @property + def Slices(self) -> list: + """[summary] + Gets the list of slices with values for every label \n + of the pie chart. \n + Returns: + list: [Slices with values for using in the pie chart] + """ + return self.__slices + + @property + def PlotDF(self) -> DataFrameHandler: + """[summary] + Gets the DataFrameHandler object. \n + Returns: + class: DataFrameHandler + """ + return self.__plotDF + + @property + def Colors(self) -> list: + """[summary] + Gets the list of colors for the pie chart. \n + Returns: + list: [Colors for the pie Chart] + """ + return self.__colors + + @property + def Title(self) -> str: + """[summary] + Gets the title for the legend of the pie chart. \n + Returns: + str: [Title for the legend of the pie chart] + """ + return self.__title + + # ?### END PROPERTIES - GETTERS ###? + + # ?### START PROPERTIES - SETTERS ###? + + @PathToSave.setter + def PathToSave(self, path: str): + """[summary] + Sets the path to save the image of the pie chart. \n + Args: + path (str): [The path of the directory to save the image of the pie chart.] + """ + self.__pathToSave = path.strip() + + @Labels.setter + def Labels(self, value: list): + """[summary] + Sets the list of labels for the pie chart. \n + Args: + value (list): [Labels for using in the pie Chart] + """ + self.__labels = value + + @Slices.setter + def Slices(self, value: list): + """[summary] + Sets the list of slices with values for every label \n + of the pie chart. \n + Args: + value (list): [Slices with values for using in the pie chart] + """ + self.__slices = value + + @PlotDF.setter + def PlotDF(self, value: DataFrameHandler): + """[summary] + Sets the DataFrameHandler object. \n + Args: + value (DataFrameHandler): [DataFrameHandler object] + """ + self.__plotDF = value + + @Colors.setter + def Colors(self, value: list): + """[summary] + Sets the list of colors for the pie chart. \n + Args: + value (list): [Colors for the pie Chart] + """ + self.__colors = value + + @Title.setter + def Title(self, value: str): + """[summary] + Sets the title for the legend of the pie chart. \n + Args: + value (str): [Title for the legend of the pie chart] + """ + self.__title = value.strip() + + # ?### END PROPERTIES - SETTERS ###? + + # ?### START METHODS ###? + + def initialize(self, df: DataFrameHandler, title: str, path: str) -> None: + """[summary] + Initializes the PlotManager object. \n + Args: + df (DataFrameHandler): [DataFrameHandler object] \n + title (str): [Title for the legend of the pie chart] \n + path (str): [Path to save the image of the pie chart] + """ + self.PlotDF = df + self.Labels = self.PlotDF.UniqueColumns + self.Slices = [len(dframe.index) for dframe in self.PlotDF.OrderListOfDFStudents] + self.Title = title + self.PathToSave = path + + def configureRCparams(self) ->None: + """[summary] + Configures the rcParams of the matplotlib. \n + """ + plt.style.use('seaborn-whitegrid') + plt.rcParams['font.family'] = 'Times New Roman' + plt.rcParams['font.size'] = 12 + plt.rcParams['legend.fontsize'] = 8 + plt.rcParams['lines.linewidth'] = 1.5 + plt.rcParams['lines.markersize'] = 5 + plt.rcParams['lines.markeredgewidth'] = 1 + plt.rcParams['figure.figsize'] = (12, 6) + + def createPieChart(self): + """[summary] + Creates the pie chart. \n + """ + self.configureRCparams() + + plt.pie(self.Slices, labels=self.Labels, startangle=45, shadow=True, + autopct='%1.1f%%') + plt.legend(self.Labels, bbox_to_anchor=(1, 0.5), markerscale=1.2, loc='upper left') + plt.title(self.Title) + plt.tight_layout() + plt.savefig(f'{self.PathToSave}/{datetime.datetime.today().strftime("%Y%m%d__%H_%M_%S")}.png', dpi=300) + plt.show() diff --git a/Modules/PlotManager_Mod/__init__.py b/Modules/PlotManager_Mod/__init__.py new file mode 100644 index 0000000..7d112a3 --- /dev/null +++ b/Modules/PlotManager_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/README.md b/README.md index cd2b39b..bdb8a2e 100644 --- a/README.md +++ b/README.md @@ -47,17 +47,19 @@ - + - + + +

Watch this little video Demo on 🎥

Watch this little video Demo [for version without Pie Chart] on 🎥

- +
Youtube Logo



@@ -183,7 +185,7 @@ Meanwhile the program is cloning the repositories, the console will show message - + @@ -198,7 +200,7 @@ When finish, you look a final message (with the elapsed time of the execution) l
Console Messages
Console Messages
- + @@ -209,6 +211,23 @@ When finish, you look a final message (with the elapsed time of the execution) l
Console Final Message
Console Final Message
+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. Additionally, the program will generate a JSON with the data of the students and courses and it will generate a Pie Chart with the percentage of students that have downloaded the repositories... + +Like the image below: + + + + + + + + + + +
Example Pie Chart
+ Example Pie Chart +
+


@@ -250,6 +269,7 @@ In order to use this Cloner, you should configure the file [API_Info.json](./Mod }, "DataFrame": { "Fields": { + "Date": "First_Datetime_Field_To_Delete", "Name": "Name_For_Column_Of_Names", "Surname": "Name_For_Column_Of_Surnames", "Course": "Name_For_Column_Of_Courses", @@ -257,6 +277,10 @@ In order to use this Cloner, you should configure the file [API_Info.json](./Mod "Email": "Name_For_Column_Of_Emails", "GitLink": "Name_For_Column_Of_Links_To_Repositories" } + }, + "Files": { + "Dir_Plots_img": "./DIR_FOR_PLOTS_IMAGES", + "Dir_Cloned_Repos": "./DIR_FOR_CLONED_REPOSITORIES", } ] ``` @@ -273,6 +297,7 @@ for example: }, "DataFrame": { "Fields": { + "Date": "Marca temporal", "Name": "Nombre/s", "Surname": "Apellido/s", "Course": "División", @@ -280,6 +305,10 @@ for example: "Email": "E-Mail", "GitLink": "Link al repositorio" } + }, + "Files": { + "Dir_Plots_img": "./Plot_Images", + "Dir_Cloned_Repos": "./Repositories" } ] ``` @@ -294,14 +323,19 @@ This way the program will take the 'Date' of the last commit of the branch 'main 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.] +Finally, respect the 'Files' Key, where you can configure the directory where the plots will be saved and the directory where the cloned repositories will be saved. + For our example, the columns of the csv file are: - + + diff --git a/requirements.txt b/requirements.txt index 92e4cd1..655d7f3 100644 Binary files a/requirements.txt and b/requirements.txt differ
Nombre/sApellido/sDivisiónDNI / LegajoE-MailLink al repositorioMarca TemporalNombre/sApellido/sDivisiónDNI / LegajoE-MailLink al repositorio
+

2022/02/13 10:26:52 p. m. GMT-3

+

Poseidon