From aedc0cd43ce14a2ae0487d9f5070548e1c9c9115 Mon Sep 17 00:00:00 2001 From: Jmateusribeiro Date: Sun, 2 Jun 2024 14:25:09 +0100 Subject: [PATCH 01/14] pylint action --- .github/workflows/pylint.yml | 23 ++++++++++++++++++ ...folder_synchronizer-0.0.1-py3-none-any.whl | Bin 4849 -> 0 bytes dist/folder_synchronizer-0.0.1.tar.gz | Bin 4764 -> 0 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 181 -> 0 bytes .../folder_operations.cpython-39.pyc | Bin 2415 -> 0 bytes .../classes/__pycache__/log.cpython-39.pyc | Bin 1115 -> 0 bytes 6 files changed, 23 insertions(+) create mode 100644 .github/workflows/pylint.yml delete mode 100644 dist/folder_synchronizer-0.0.1-py3-none-any.whl delete mode 100644 dist/folder_synchronizer-0.0.1.tar.gz delete mode 100644 folder_synchronizer/classes/__pycache__/__init__.cpython-39.pyc delete mode 100644 folder_synchronizer/classes/__pycache__/folder_operations.cpython-39.pyc delete mode 100644 folder_synchronizer/classes/__pycache__/log.cpython-39.pyc diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 0000000..e171d1d --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,23 @@ +name: Pylint + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + - name: Analyzing the code with pylint + run: | + pylint **/*.py diff --git a/dist/folder_synchronizer-0.0.1-py3-none-any.whl b/dist/folder_synchronizer-0.0.1-py3-none-any.whl deleted file mode 100644 index 0f2bdd000de6879235650dcd19701a4ed2facb32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4849 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 x&g%F{1bwn!OZIKRgGgZ^+Zd_Zm< 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 diff --git a/folder_synchronizer/classes/__pycache__/__init__.cpython-39.pyc b/folder_synchronizer/classes/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 184b6fbee9476b17127eb626f5a9badf03754bdc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 181 zcmYe~<>g`kg8Iq}sUZ3>h(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o11%#VRJ;Kgc`A zCAB!aB)=dgpeR2pwIsjTJ+maEG$|%6KPM%%NVm8$FFB(qKQFT?wFoK}k0O?wlUQ7w iS{xG}pP83g5+AQuPgfuSJ8p$Ak|NPHbyxd>E6wSu;eXOgU0?>aN% zLRJok!Vw|v6_Ru8wSPg?V^38NJTOXyj{Qdo44sKEma6y z-25Sbs}u4!PG&C-lbdks&p`+xXhPb>NZZuSS)0Mg65jXP9wqmP;KF-Bg!i1aeL)|R zrvDG@C9Nj4-u;f*>EdkmVlcT0xBd)-Bp4TBQo&x3HZP*JJ>iQI%zaT70gNTF0Ff)U zaW_kZR1f#k?vBdRIG3vHL73@x^Rw_XEb$cR3tBN9>2nA6yZ!<-8O5oxRGDcoR} zbT(978Ev@-*qV}qB<^;mbDm_|P0xB-CR+-hqI?u35I++Y!0@(v`^LulHxItrxGnW# zlMOcRtE?wYrtigOXSlghM82YD-hE<==4{=hqqWr2wQL|&$HZBxuMYNX5Jhnsn<&CZ zYJ5Cl6R^qX9$;}38t`W_TRdUVW2q{k1aUNfG6boKa88RA6i;iX|ij?eUKgcLHvajvsV_mvRc8rWN6%7XhHRs%Agxgi>Pf#G!WK0D;pts4RU-zkDJwEm@ z|4u87uyW*p9k6|tZ#r9f1a;9bogR!}rxJ#Z7f^E*tc(|-f^=HH^rG{uI?tD8Xu+N|I-D)vjBiCTJW24lv z^ip1X;7TDvEGjTje>Zoh5iPF7p(|OOZig4%dKbbgVRNOZy05mAY*S&jR278vdRd$n zq?DCRkRP>#3#C|Z!9#4RsQ!eKINi#uA8P=|mh{fh#0hxxjgrzX+oU7P1Fgi!h{2tw<;LTmqejL|CmRp_ddCxM;(+%eS5 z$C(2MThlvmuEBwy1?M$=boT$znSe8a1rQ4yL;w_kGdbqH0vt94XSX^7PBqUbz=5}V zRbT`=nR2@0sVYgmDJTpruwJiFy5&YXD->oB+hyx2_R@ zr9gG*RyK&`yXl(a^hP-Cci=SF?N$qpU0;c}LnI}LWDy-E6RT-TKnC$dA!A1kv=17WN4m&VjJy zzSO$2E!C%J!q3ZLYipm1qT^fPb@0=dKoHshW`SQmp_f%DW2dOcsvcNag;o6;KS}E} zzkDps$H#9q%lOSpJAD~Nwh~4CObip$t5LK&>?FmC!hdVj2^1?R8Ym8bgVV4_D-HsW yuSQcqgHq548qV4*6Ha}*KF5W$!576leTiSzX300+Z~s4;>?BZ2{manm?icBbvZ{;4xlg}OaK z!pWN-pd9%&p6BX`uV7;0n{5g8CG+O}nfIIb=DpppR`U=LQNQ>z8=>DunI^EI7qH|S zFoqaTP%z$cfI+tslv;t6l7L{eg&1M>Ibt>^XC$y0-bW4R8Y0n7!_say%vi#O)RpbL z)8QgAo@p|-8Iliyab#`?EM{MzHf9zh=hc8*AR7g?!PWjoU#dJEV_ln&h?wXJRbY2u z8JqkGOrbM0!VF(xj0!Ze6gjeutjNe=)+HVikSdpGXcuuRO^@qpyRSaw)?OC-v-1R?F#EErBM<}N$C~(bFjJI`nbFIwzbLSvC4a`_aZ;yDwkWa z>h=#?;~Q5byn0vUSv=rkVx#}rN<=POkmYLcRJc&HS@IDugnjH=9&us!uxH!2f-P+Q ztvEhN#x}S&J=Lo>?Py?OW*Md)I%K8-I^ Date: Sun, 2 Jun 2024 14:34:01 +0100 Subject: [PATCH 02/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 12dfa40..14beaba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Folder Synchronizer -[![Unit Tests](https://github.com/Jmateusribeiro/folder-synchronizer/actions/workflows/test.yml/badge.svg)](https://github.com/Jmateusribeiro/folder-synchronizer/actions/workflows/test.yml) +[![Unit Tests](https://github.com/Jmateusribeiro/folder-synchronizer/actions/workflows/test.yml/badge.svg)](https://github.com/Jmateusribeiro/folder-synchronizer/actions/workflows/test.yml) [![Pylint](https://github.com/Jmateusribeiro/folder-synchronizer/actions/workflows/pylint.yml/badge.svg)](https://github.com/Jmateusribeiro/folder-synchronizer/actions/workflows/pylint.yml) ## Goal From 97c87290b8bfe28848d0278f3c02626d9b14db37 Mon Sep 17 00:00:00 2001 From: Jmateusribeiro Date: Sun, 2 Jun 2024 14:36:18 +0100 Subject: [PATCH 03/14] Update __main__.py --- folder_synchronizer/__main__.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/folder_synchronizer/__main__.py b/folder_synchronizer/__main__.py index 79ddcd7..556afb1 100644 --- a/folder_synchronizer/__main__.py +++ b/folder_synchronizer/__main__.py @@ -1,26 +1,24 @@ """ main module """ - import argparse import os from folder_synchronizer.classes.log import CustomLogger from folder_synchronizer.classes.folder_operations import FolderSynchronizer - def parse_args(): parser = argparse.ArgumentParser() parser.add_argument("--sourceDir", - type=str, - help="Complete Directory of Source folder", + type=str, + help="Complete Directory of Source folder", required=True) - parser.add_argument("--replicaDir", - type=str, - help="Complete Directory of Replica folder", + parser.add_argument("--replicaDir", + type=str, + help="Complete Directory of Replica folder", required=True) - parser.add_argument("--logFolder", - type=str, - help="Complete Directory of Log folder", + parser.add_argument("--logFolder", + type=str, + help="Complete Directory of Log folder", required=True) return parser.parse_args() @@ -39,7 +37,7 @@ def main(): log.info("### Folder Synchronization ###") log.info(f"Source Folder: {source_folder}") log.info(f"Replica Folder: {replica_folder}") - folder_sync = FolderSynchronizer(source_folder, replica_folder, log) + folder_sync = FolderSynchronizer(source_folder, replica_folder, log) try: log.info("...Strating Synchronization...") From ef6272c7c625f54e8132367f2a474ac03bacd091 Mon Sep 17 00:00:00 2001 From: Jmateusribeiro Date: Sun, 2 Jun 2024 15:12:42 +0100 Subject: [PATCH 04/14] refactoring code --- folder_synchronizer/__main__.py | 22 +++-- .../classes/folder_operations.py | 62 ++++++++----- folder_synchronizer/classes/log.py | 23 +++-- tests/test_folder_operations.py | 88 +++++++++++-------- 4 files changed, 122 insertions(+), 73 deletions(-) diff --git a/folder_synchronizer/__main__.py b/folder_synchronizer/__main__.py index 556afb1..63b37df 100644 --- a/folder_synchronizer/__main__.py +++ b/folder_synchronizer/__main__.py @@ -1,14 +1,17 @@ -""" - main module -""" import argparse import os from folder_synchronizer.classes.log import CustomLogger from folder_synchronizer.classes.folder_operations import FolderSynchronizer -def parse_args(): - parser = argparse.ArgumentParser() - parser.add_argument("--sourceDir", +def parse_args() -> argparse.Namespace: + """ + Parses command-line arguments. + + Returns: + argparse.Namespace: Parsed command-line arguments. + """ + parser = argparse.ArgumentParser(description="Synchronize folders and log the process.") + parser.add_argument("--sourceDir", type=str, help="Complete Directory of Source folder", required=True) @@ -22,7 +25,10 @@ def parse_args(): required=True) return parser.parse_args() -def main(): +def main() -> None: + """ + Main function to synchronize source and replica folders and log the process. + """ args = parse_args() source_folder = args.sourceDir @@ -40,7 +46,7 @@ def main(): folder_sync = FolderSynchronizer(source_folder, replica_folder, log) try: - log.info("...Strating Synchronization...") + log.info("...Starting Synchronization...") folder_sync.synchronize() log.info("...Folders Synchronized...") except Exception as e: diff --git a/folder_synchronizer/classes/folder_operations.py b/folder_synchronizer/classes/folder_operations.py index 2676f05..4319421 100644 --- a/folder_synchronizer/classes/folder_operations.py +++ b/folder_synchronizer/classes/folder_operations.py @@ -1,40 +1,55 @@ """ - folder scynchronizer class +Folder Synchronizer class for synchronizing contents between source and replica folders. """ import os import filecmp import shutil from pathlib import Path - class FolderSynchronizer: - def __init__(self, source_folder, replica_folder, log): - # using 'Path' class to facilitate (method rglob) iteration over all subfiles and directories. + """ + A class to synchronize contents between a source folder and a replica folder. + + Attributes: + source_folder (Path): Path to the source folder. + replica_folder (Path): Path to the replica folder. + log (CustomLogger): CustomLogger instance for logging synchronization activities. + """ + + def __init__(self, source_folder: str, replica_folder: str, log: 'CustomLogger') -> None: + """ + Initializes the FolderSynchronizer with the given folders and logger. + + Args: + source_folder (str): The path to the source folder. + replica_folder (str): The path to the replica folder. + log (CustomLogger): CustomLogger instance for logging synchronization activities. + """ self.source_folder = Path(source_folder) self.replica_folder = Path(replica_folder) self.log = log - def synchronize(self): - # If source folder don't exists should be thrown error - # but if replica folder don't exists must be created + def synchronize(self) -> None: + """ + Synchronizes the contents of the source folder to the replica folder. + Raises: + FileNotFoundError: If the source folder does not exist. + """ if not os.path.exists(self.source_folder): raise FileNotFoundError(f"Source folder not found: {self.source_folder}") if not os.path.exists(self.replica_folder): os.makedirs(self.replica_folder) - # logging that action has a warning because - # this folder is created when the program starts, by default - self.log.warn(f"Replica folder was deleted: {self.replica_folder}") + self.log.warning(f"Replica folder was created: {self.replica_folder}") self.__remove_items() self.__create_or_update_items() - # since this class it will not be inherited (at least in this challenge) - # methods from here will be private (and not protected) - def __remove_items(self): + def __remove_items(self) -> None: + """ + Removes items from the replica folder that do not exist in the source folder. + """ for replica_item_path in self.replica_folder.rglob('*'): - # it could be used 'Path' methods to perform the actions over files and folder - # but I prefer using 'os' methods, since 'Path' methods use 'os' anyway source_item_path = os.path.join(self.source_folder, os.path.relpath(replica_item_path, self.replica_folder)) try: @@ -49,7 +64,10 @@ def __remove_items(self): except Exception as e: raise Exception(f"Error removing '{replica_item_path}' - {str(e)}") - def __create_or_update_items(self): + def __create_or_update_items(self) -> None: + """ + Creates or updates items in the replica folder to match the source folder. + """ for source_item_path in self.source_folder.rglob('*'): replica_item_path = os.path.join(self.replica_folder, os.path.relpath(source_item_path, self.source_folder)) @@ -57,16 +75,14 @@ def __create_or_update_items(self): if os.path.isdir(source_item_path): if not os.path.exists(replica_item_path): os.makedirs(replica_item_path) - self.log.info(f"Created folder: {replica_item_path}") + self.log.info(f"Created folder: {replica_item_path}") elif os.path.isfile(source_item_path): try: - # If file already exists and has the same content, should go to next iteration - if os.path.exists(replica_item_path) and filecmp.cmp(source_item_path, replica_item_path): + if os.path.exists(replica_item_path) and filecmp.cmp(source_item_path, replica_item_path, shallow=False): continue - + if os.path.exists(source_item_path): - #Logging different variables just because a matter of logic :) #Update item inside replica folder but copied from source folder if os.path.exists(replica_item_path): @@ -80,9 +96,7 @@ def __create_or_update_items(self): # but need to "catch" the action before # to understand if was a create or update action self.log.info(message) - else: self.log.error(f"Error: Source file not found: {source_item_path}") except Exception as e: - raise Exception(f"Error creatig item '{source_item_path}' - {str(e)}") - \ No newline at end of file + raise Exception(f"Error creating or updating item '{source_item_path}' - {str(e)}") diff --git a/folder_synchronizer/classes/log.py b/folder_synchronizer/classes/log.py index 29d1f46..16e7c27 100644 --- a/folder_synchronizer/classes/log.py +++ b/folder_synchronizer/classes/log.py @@ -1,11 +1,25 @@ import logging import re from logging import handlers, Logger - +import os class CustomLogger(Logger): + """ + A custom logger class that logs messages to both console and file with timed rotation. + + Attributes: + log_folder (str): Directory where log files will be stored. + backupCount_days (int): Number of backup log files to keep. + """ + + def __init__(self, log_folder: str, backupCount_days: int = 30) -> None: + """ + Initializes the custom logger. - def __init__(self, log_folder, backupCount_days=30): + Args: + log_folder (str): The directory where log files will be stored. + backupCount_days (int): The number of days to keep backup log files. + """ super().__init__('CustomLogger') self.setLevel(logging.DEBUG) @@ -18,9 +32,8 @@ def __init__(self, log_folder, backupCount_days=30): self.addHandler(console_handler) # File handler (with timed rotation each day) - # Think is better to have a log file per day file_handler = handlers.TimedRotatingFileHandler( - log_folder + '\\Folder_Sync', + os.path.join(log_folder, 'Folder_Sync'), when='midnight', backupCount=backupCount_days ) @@ -28,4 +41,4 @@ def __init__(self, log_folder, backupCount_days=30): file_handler.suffix = '%Y_%m_%d.log' file_handler.extMatch = re.compile(r"^\d{4}_\d{2}_\d{2}.log$") file_handler.setFormatter(formatter) - self.addHandler(file_handler) \ No newline at end of file + self.addHandler(file_handler) diff --git a/tests/test_folder_operations.py b/tests/test_folder_operations.py index bd24aa9..ac8d494 100644 --- a/tests/test_folder_operations.py +++ b/tests/test_folder_operations.py @@ -1,49 +1,66 @@ -""" - unit tests -""" - import os import sys import shutil from unittest.mock import Mock import pytest +from typing import Generator +from _pytest.fixtures import FixtureRequest + +# Adding the folder containing the folder_operations module to the path sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from folder_synchronizer.classes.folder_operations import FolderSynchronizer +# Global variables for the source and replica folders used in tests source_folder = 'test_source_folder' replica_folder = 'test_replica_folder' log = Mock() @pytest.fixture(scope="module", autouse=True) -def create_folders(request): - - #set up +def create_folders(request: FixtureRequest) -> None: + """ + Fixture to create and teardown test folders for the module. + + Args: + request (FixtureRequest): The fixture request object. + """ + # Setup os.makedirs(source_folder, exist_ok=True) os.makedirs(replica_folder, exist_ok=True) - def teardown(): - # Teardown code + def teardown() -> None: + # Teardown shutil.rmtree(source_folder) shutil.rmtree(replica_folder) assert not os.path.exists(source_folder) assert not os.path.exists(replica_folder) - + + # Register the teardown function to be called after tests request.addfinalizer(teardown) @pytest.fixture(scope="function") -def folder_sync(): - return FolderSynchronizer(source_folder, replica_folder, log) +def folder_sync() -> FolderSynchronizer: + """ + Fixture to provide a FolderSynchronizer instance for each test function. -def test_synchronize_nonexistent_source_folder(): + Returns: + FolderSynchronizer: An instance of FolderSynchronizer. + """ + return FolderSynchronizer(source_folder, replica_folder, log) +def test_synchronize_nonexistent_source_folder() -> None: + """ + Test synchronizing a non-existent source folder raises FileNotFoundError. + """ folder_sync = FolderSynchronizer('nonexistent_source', replica_folder, log) with pytest.raises(FileNotFoundError): folder_sync.synchronize() -def test_synchronize_create_replica_folder(): - +def test_synchronize_create_replica_folder() -> None: + """ + Test that the replica folder is created if it does not exist. + """ custom_replica_folder = 'custom_replica_folder' folder_sync = FolderSynchronizer(source_folder, custom_replica_folder, log) @@ -54,9 +71,10 @@ def test_synchronize_create_replica_folder(): shutil.rmtree(custom_replica_folder) assert not os.path.exists(custom_replica_folder) -def test_synchronize_remove_folder(folder_sync): - - # Create a folder in replica folder (to be removed by 'synchronize' method) +def test_synchronize_remove_folder(folder_sync: FolderSynchronizer) -> None: + """ + Test that a folder in the replica that does not exist in the source is removed. + """ empty_folder = os.path.join(replica_folder, 'folder_removal/folder') os.makedirs(empty_folder) @@ -64,9 +82,10 @@ def test_synchronize_remove_folder(folder_sync): assert not os.path.exists(empty_folder) -def test_synchronize_remove_file(folder_sync): - - # Create a file replica folder (to be removed by 'synchronize' method) +def test_synchronize_remove_file(folder_sync: FolderSynchronizer) -> None: + """ + Test that a file in the replica that does not exist in the source is removed. + """ empty_folder = os.path.join(replica_folder, 'file_removal/folder/') os.makedirs(empty_folder) empty_file = os.path.join(empty_folder, 'file.txt') @@ -76,21 +95,21 @@ def test_synchronize_remove_file(folder_sync): assert not os.path.exists(empty_file) -def test_synchronize_create_folder(folder_sync): - +def test_synchronize_create_folder(folder_sync: FolderSynchronizer) -> None: + """ + Test that a folder in the source is copied to the replica. + """ folder_name = 'folder_creation/folder/folder' - # Create a folder in the source folder (to be coppied by 'synchronize' method) os.makedirs(os.path.join(source_folder, folder_name)) folder_sync.synchronize() - # Even though the folder was created on source - # needs to be checked on the replica assert os.path.exists(os.path.join(replica_folder, folder_name)) -def test_synchronize_create_file(folder_sync): - - # Create a file in the source folder (to be coppied by 'synchronize' method) +def test_synchronize_create_file(folder_sync: FolderSynchronizer) -> None: + """ + Test that a file in the source is copied to the replica. + """ folder_name = 'file_creation/folder/folder/' os.makedirs(os.path.join(source_folder, folder_name)) file_name = folder_name + 'file.txt' @@ -98,23 +117,20 @@ def test_synchronize_create_file(folder_sync): folder_sync.synchronize() - # Even though the file was created on source - # needs to be checked on the replica assert os.path.exists(os.path.join(replica_folder, file_name)) -def test_synchronize_update_file(folder_sync): - +def test_synchronize_update_file(folder_sync: FolderSynchronizer) -> None: + """ + Test that a file in the replica is updated to match the source. + """ file_name = 'file.txt' content = 'test update file' - # creat empty file in replica folder open(os.path.join(replica_folder, file_name), 'w').close() - # create the same file in source folder but with content with open(os.path.join(source_folder, file_name), 'w') as f: f.write(content) folder_sync.synchronize() - # Check if the file is updated in replica folder with open(os.path.join(replica_folder, file_name), 'r') as f: assert f.read() == content From 34e719fd555f1a3d059db1b49269a9dcb0e7e4e4 Mon Sep 17 00:00:00 2001 From: Jmateusribeiro Date: Sun, 2 Jun 2024 15:25:39 +0100 Subject: [PATCH 05/14] refactoring code --- folder_synchronizer/__main__.py | 9 ++++++--- folder_synchronizer/classes/log.py | 10 +++------- tests/test_folder_operations.py | 25 +++++++++++++++---------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/folder_synchronizer/__main__.py b/folder_synchronizer/__main__.py index 63b37df..1caa376 100644 --- a/folder_synchronizer/__main__.py +++ b/folder_synchronizer/__main__.py @@ -1,3 +1,6 @@ +""" +main module +""" import argparse import os from folder_synchronizer.classes.log import CustomLogger @@ -30,7 +33,7 @@ def main() -> None: Main function to synchronize source and replica folders and log the process. """ args = parse_args() - + source_folder = args.sourceDir replica_folder = args.replicaDir logs_folder = args.logFolder @@ -44,14 +47,14 @@ def main() -> None: log.info(f"Source Folder: {source_folder}") log.info(f"Replica Folder: {replica_folder}") folder_sync = FolderSynchronizer(source_folder, replica_folder, log) - + try: log.info("...Starting Synchronization...") folder_sync.synchronize() log.info("...Folders Synchronized...") except Exception as e: log.error(f"Error during synchronization: {str(e)}") - + log.info("...Ending Synchronization...") if __name__ == "__main__": diff --git a/folder_synchronizer/classes/log.py b/folder_synchronizer/classes/log.py index 16e7c27..9c9110f 100644 --- a/folder_synchronizer/classes/log.py +++ b/folder_synchronizer/classes/log.py @@ -1,16 +1,12 @@ +""" +A custom logger class that logs messages to both console and file with timed rotation. +""" import logging import re from logging import handlers, Logger import os class CustomLogger(Logger): - """ - A custom logger class that logs messages to both console and file with timed rotation. - - Attributes: - log_folder (str): Directory where log files will be stored. - backupCount_days (int): Number of backup log files to keep. - """ def __init__(self, log_folder: str, backupCount_days: int = 30) -> None: """ diff --git a/tests/test_folder_operations.py b/tests/test_folder_operations.py index ac8d494..c93bc84 100644 --- a/tests/test_folder_operations.py +++ b/tests/test_folder_operations.py @@ -1,9 +1,11 @@ +""" +unit tests +""" import os import sys import shutil from unittest.mock import Mock import pytest -from typing import Generator from _pytest.fixtures import FixtureRequest # Adding the folder containing the folder_operations module to the path @@ -52,19 +54,19 @@ def test_synchronize_nonexistent_source_folder() -> None: """ Test synchronizing a non-existent source folder raises FileNotFoundError. """ - folder_sync = FolderSynchronizer('nonexistent_source', replica_folder, log) + folder_sync_test = FolderSynchronizer('nonexistent_source', replica_folder, log) with pytest.raises(FileNotFoundError): - folder_sync.synchronize() + folder_sync_test.synchronize() def test_synchronize_create_replica_folder() -> None: """ Test that the replica folder is created if it does not exist. """ custom_replica_folder = 'custom_replica_folder' - folder_sync = FolderSynchronizer(source_folder, custom_replica_folder, log) + folder_sync_test = FolderSynchronizer(source_folder, custom_replica_folder, log) - folder_sync.synchronize() + folder_sync_test.synchronize() assert os.path.exists(custom_replica_folder) @@ -89,7 +91,8 @@ def test_synchronize_remove_file(folder_sync: FolderSynchronizer) -> None: empty_folder = os.path.join(replica_folder, 'file_removal/folder/') os.makedirs(empty_folder) empty_file = os.path.join(empty_folder, 'file.txt') - open(empty_file, 'w').close() + with open(empty_file, 'w', encoding='utf-8'): + pass folder_sync.synchronize() @@ -113,7 +116,8 @@ def test_synchronize_create_file(folder_sync: FolderSynchronizer) -> None: folder_name = 'file_creation/folder/folder/' os.makedirs(os.path.join(source_folder, folder_name)) file_name = folder_name + 'file.txt' - open(os.path.join(source_folder, file_name), 'w').close() + with open(os.path.join(source_folder, file_name), 'w', encoding='utf-8'): + pass folder_sync.synchronize() @@ -125,12 +129,13 @@ def test_synchronize_update_file(folder_sync: FolderSynchronizer) -> None: """ file_name = 'file.txt' content = 'test update file' - open(os.path.join(replica_folder, file_name), 'w').close() + with open(os.path.join(replica_folder, file_name), 'w', encoding='utf-8'): + pass - with open(os.path.join(source_folder, file_name), 'w') as f: + with open(os.path.join(source_folder, file_name), 'w', encoding='utf-8') as f: f.write(content) folder_sync.synchronize() - with open(os.path.join(replica_folder, file_name), 'r') as f: + with open(os.path.join(replica_folder, file_name), 'r', encoding='utf-8', ) as f: assert f.read() == content From 5d19f5f5bb864837b58d0d29dd3be06d2d9cc977 Mon Sep 17 00:00:00 2001 From: Jmateusribeiro Date: Sun, 2 Jun 2024 15:32:17 +0100 Subject: [PATCH 06/14] refactoring unit tests --- requirements.txt | 1 + tests/test_folder_operations.py | 56 ++++++++++++++++----------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/requirements.txt b/requirements.txt index e69de29..55b033e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1 @@ +pytest \ No newline at end of file diff --git a/tests/test_folder_operations.py b/tests/test_folder_operations.py index c93bc84..c966391 100644 --- a/tests/test_folder_operations.py +++ b/tests/test_folder_operations.py @@ -7,14 +7,14 @@ from unittest.mock import Mock import pytest from _pytest.fixtures import FixtureRequest +from folder_synchronizer.classes.folder_operations import FolderSynchronizer # Adding the folder containing the folder_operations module to the path sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) -from folder_synchronizer.classes.folder_operations import FolderSynchronizer # Global variables for the source and replica folders used in tests -source_folder = 'test_source_folder' -replica_folder = 'test_replica_folder' +SOURCE_FOLDER = 'test_source_folder' +REPLICA_FOLDER = 'test_replica_folder' log = Mock() @pytest.fixture(scope="module", autouse=True) @@ -26,16 +26,16 @@ def create_folders(request: FixtureRequest) -> None: request (FixtureRequest): The fixture request object. """ # Setup - os.makedirs(source_folder, exist_ok=True) - os.makedirs(replica_folder, exist_ok=True) + os.makedirs(SOURCE_FOLDER, exist_ok=True) + os.makedirs(REPLICA_FOLDER, exist_ok=True) def teardown() -> None: # Teardown - shutil.rmtree(source_folder) - shutil.rmtree(replica_folder) + shutil.rmtree(SOURCE_FOLDER) + shutil.rmtree(REPLICA_FOLDER) - assert not os.path.exists(source_folder) - assert not os.path.exists(replica_folder) + assert not os.path.exists(SOURCE_FOLDER) + assert not os.path.exists(REPLICA_FOLDER) # Register the teardown function to be called after tests request.addfinalizer(teardown) @@ -48,13 +48,13 @@ def folder_sync() -> FolderSynchronizer: Returns: FolderSynchronizer: An instance of FolderSynchronizer. """ - return FolderSynchronizer(source_folder, replica_folder, log) + return FolderSynchronizer(SOURCE_FOLDER, REPLICA_FOLDER, log) def test_synchronize_nonexistent_source_folder() -> None: """ Test synchronizing a non-existent source folder raises FileNotFoundError. """ - folder_sync_test = FolderSynchronizer('nonexistent_source', replica_folder, log) + folder_sync_test = FolderSynchronizer('nonexistent_source', REPLICA_FOLDER, log) with pytest.raises(FileNotFoundError): folder_sync_test.synchronize() @@ -64,31 +64,31 @@ def test_synchronize_create_replica_folder() -> None: Test that the replica folder is created if it does not exist. """ custom_replica_folder = 'custom_replica_folder' - folder_sync_test = FolderSynchronizer(source_folder, custom_replica_folder, log) + folder_sync_test = FolderSynchronizer(SOURCE_FOLDER, custom_replica_folder, log) folder_sync_test.synchronize() assert os.path.exists(custom_replica_folder) - + shutil.rmtree(custom_replica_folder) assert not os.path.exists(custom_replica_folder) -def test_synchronize_remove_folder(folder_sync: FolderSynchronizer) -> None: +def test_synchronize_remove_folder(folder_sync: 'FolderSynchronizer') -> None: """ Test that a folder in the replica that does not exist in the source is removed. """ - empty_folder = os.path.join(replica_folder, 'folder_removal/folder') + empty_folder = os.path.join(REPLICA_FOLDER, 'folder_removal/folder') os.makedirs(empty_folder) folder_sync.synchronize() assert not os.path.exists(empty_folder) -def test_synchronize_remove_file(folder_sync: FolderSynchronizer) -> None: +def test_synchronize_remove_file(folder_sync: 'FolderSynchronizer') -> None: """ Test that a file in the replica that does not exist in the source is removed. """ - empty_folder = os.path.join(replica_folder, 'file_removal/folder/') + empty_folder = os.path.join(REPLICA_FOLDER, 'file_removal/folder/') os.makedirs(empty_folder) empty_file = os.path.join(empty_folder, 'file.txt') with open(empty_file, 'w', encoding='utf-8'): @@ -98,44 +98,44 @@ def test_synchronize_remove_file(folder_sync: FolderSynchronizer) -> None: assert not os.path.exists(empty_file) -def test_synchronize_create_folder(folder_sync: FolderSynchronizer) -> None: +def test_synchronize_create_folder(folder_sync: 'FolderSynchronizer') -> None: """ Test that a folder in the source is copied to the replica. """ folder_name = 'folder_creation/folder/folder' - os.makedirs(os.path.join(source_folder, folder_name)) + os.makedirs(os.path.join(SOURCE_FOLDER, folder_name)) folder_sync.synchronize() - assert os.path.exists(os.path.join(replica_folder, folder_name)) + assert os.path.exists(os.path.join(REPLICA_FOLDER, folder_name)) -def test_synchronize_create_file(folder_sync: FolderSynchronizer) -> None: +def test_synchronize_create_file(folder_sync: 'FolderSynchronizer') -> None: """ Test that a file in the source is copied to the replica. """ folder_name = 'file_creation/folder/folder/' - os.makedirs(os.path.join(source_folder, folder_name)) + os.makedirs(os.path.join(SOURCE_FOLDER, folder_name)) file_name = folder_name + 'file.txt' - with open(os.path.join(source_folder, file_name), 'w', encoding='utf-8'): + with open(os.path.join(SOURCE_FOLDER, file_name), 'w', encoding='utf-8'): pass folder_sync.synchronize() - assert os.path.exists(os.path.join(replica_folder, file_name)) + assert os.path.exists(os.path.join(REPLICA_FOLDER, file_name)) -def test_synchronize_update_file(folder_sync: FolderSynchronizer) -> None: +def test_synchronize_update_file(folder_sync: 'FolderSynchronizer') -> None: """ Test that a file in the replica is updated to match the source. """ file_name = 'file.txt' content = 'test update file' - with open(os.path.join(replica_folder, file_name), 'w', encoding='utf-8'): + with open(os.path.join(REPLICA_FOLDER, file_name), 'w', encoding='utf-8'): pass - with open(os.path.join(source_folder, file_name), 'w', encoding='utf-8') as f: + with open(os.path.join(SOURCE_FOLDER, file_name), 'w', encoding='utf-8') as f: f.write(content) folder_sync.synchronize() - with open(os.path.join(replica_folder, file_name), 'r', encoding='utf-8', ) as f: + with open(os.path.join(REPLICA_FOLDER, file_name), 'r', encoding='utf-8', ) as f: assert f.read() == content From cd7eebc321046a06460c5a4f25343ffb2562ade8 Mon Sep 17 00:00:00 2001 From: Jmateusribeiro Date: Sun, 2 Jun 2024 15:36:30 +0100 Subject: [PATCH 07/14] changes pylint --- .github/workflows/pylint.yml | 2 +- tests/test_folder_operations.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index e171d1d..5d9b7a1 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -20,4 +20,4 @@ jobs: pip install pylint - name: Analyzing the code with pylint run: | - pylint **/*.py + pylint $(git ls-files '*.py') diff --git a/tests/test_folder_operations.py b/tests/test_folder_operations.py index c966391..6353bd9 100644 --- a/tests/test_folder_operations.py +++ b/tests/test_folder_operations.py @@ -73,7 +73,7 @@ def test_synchronize_create_replica_folder() -> None: shutil.rmtree(custom_replica_folder) assert not os.path.exists(custom_replica_folder) -def test_synchronize_remove_folder(folder_sync: 'FolderSynchronizer') -> None: +def test_synchronize_remove_folder(folder_sync) -> None: """ Test that a folder in the replica that does not exist in the source is removed. """ @@ -84,7 +84,7 @@ def test_synchronize_remove_folder(folder_sync: 'FolderSynchronizer') -> None: assert not os.path.exists(empty_folder) -def test_synchronize_remove_file(folder_sync: 'FolderSynchronizer') -> None: +def test_synchronize_remove_file(folder_sync) -> None: """ Test that a file in the replica that does not exist in the source is removed. """ @@ -98,7 +98,7 @@ def test_synchronize_remove_file(folder_sync: 'FolderSynchronizer') -> None: assert not os.path.exists(empty_file) -def test_synchronize_create_folder(folder_sync: 'FolderSynchronizer') -> None: +def test_synchronize_create_folder(folder_sync) -> None: """ Test that a folder in the source is copied to the replica. """ @@ -109,7 +109,7 @@ def test_synchronize_create_folder(folder_sync: 'FolderSynchronizer') -> None: assert os.path.exists(os.path.join(REPLICA_FOLDER, folder_name)) -def test_synchronize_create_file(folder_sync: 'FolderSynchronizer') -> None: +def test_synchronize_create_file(folder_sync) -> None: """ Test that a file in the source is copied to the replica. """ @@ -123,7 +123,7 @@ def test_synchronize_create_file(folder_sync: 'FolderSynchronizer') -> None: assert os.path.exists(os.path.join(REPLICA_FOLDER, file_name)) -def test_synchronize_update_file(folder_sync: 'FolderSynchronizer') -> None: +def test_synchronize_update_file(folder_sync) -> None: """ Test that a file in the replica is updated to match the source. """ From a329107fa2ed636e7900de7d5cdf629f1b59c35e Mon Sep 17 00:00:00 2001 From: Jorge Ribeiro <50589410+Jmateusribeiro@users.noreply.github.com> Date: Sun, 2 Jun 2024 15:42:53 +0100 Subject: [PATCH 08/14] Update pylint.yml --- .github/workflows/pylint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 5d9b7a1..d7005bf 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -18,6 +18,7 @@ jobs: run: | python -m pip install --upgrade pip pip install pylint + pip install pytest - name: Analyzing the code with pylint run: | pylint $(git ls-files '*.py') From f996eee700752a600f0aaf06fe936cbf38b4588c Mon Sep 17 00:00:00 2001 From: Jmateusribeiro Date: Sun, 2 Jun 2024 16:58:16 +0100 Subject: [PATCH 09/14] refactoring v1 --- .../classes/folder_operations.py | 22 ++++++++++--------- folder_synchronizer/classes/log.py | 13 +++++++++++ setup.py | 2 +- tests/test_folder_operations.py | 12 +++++----- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/folder_synchronizer/classes/folder_operations.py b/folder_synchronizer/classes/folder_operations.py index 4319421..35ee1e5 100644 --- a/folder_synchronizer/classes/folder_operations.py +++ b/folder_synchronizer/classes/folder_operations.py @@ -37,7 +37,7 @@ def synchronize(self) -> None: """ if not os.path.exists(self.source_folder): raise FileNotFoundError(f"Source folder not found: {self.source_folder}") - + if not os.path.exists(self.replica_folder): os.makedirs(self.replica_folder) self.log.warning(f"Replica folder was created: {self.replica_folder}") @@ -50,7 +50,7 @@ def __remove_items(self) -> None: Removes items from the replica folder that do not exist in the source folder. """ for replica_item_path in self.replica_folder.rglob('*'): - source_item_path = os.path.join(self.source_folder, + source_item_path = os.path.join(self.source_folder, os.path.relpath(replica_item_path, self.replica_folder)) try: if not os.path.exists(source_item_path): @@ -62,24 +62,25 @@ def __remove_items(self) -> None: self.log.info(f"Removed Folder: {replica_item_path}") except Exception as e: - raise Exception(f"Error removing '{replica_item_path}' - {str(e)}") - + raise Exception(f"Error removing '{replica_item_path}' - {str(e)}") from e + def __create_or_update_items(self) -> None: """ Creates or updates items in the replica folder to match the source folder. """ for source_item_path in self.source_folder.rglob('*'): - replica_item_path = os.path.join(self.replica_folder, + replica_item_path = os.path.join(self.replica_folder, os.path.relpath(source_item_path, self.source_folder)) if os.path.isdir(source_item_path): if not os.path.exists(replica_item_path): os.makedirs(replica_item_path) self.log.info(f"Created folder: {replica_item_path}") - + elif os.path.isfile(source_item_path): try: - if os.path.exists(replica_item_path) and filecmp.cmp(source_item_path, replica_item_path, shallow=False): + if os.path.exists(replica_item_path) and filecmp.cmp(source_item_path, + replica_item_path, shallow=False): continue if os.path.exists(source_item_path): @@ -91,12 +92,13 @@ def __create_or_update_items(self) -> None: message = f"Copied file: {source_item_path}" shutil.copy2(source_item_path, replica_item_path) - + # logging after the action was really performed - # but need to "catch" the action before + # but need to "catch" the action before # to understand if was a create or update action self.log.info(message) else: self.log.error(f"Error: Source file not found: {source_item_path}") except Exception as e: - raise Exception(f"Error creating or updating item '{source_item_path}' - {str(e)}") + error_msg = f"Error creating or updating item '{source_item_path}' - {str(e)}" + raise Exception(error_msg) from e diff --git a/folder_synchronizer/classes/log.py b/folder_synchronizer/classes/log.py index 9c9110f..21bd12e 100644 --- a/folder_synchronizer/classes/log.py +++ b/folder_synchronizer/classes/log.py @@ -7,7 +7,20 @@ import os class CustomLogger(Logger): + """ + A custom logger class that logs messages to both console and file with timed rotation. + This class extends the standard Logger class from the logging module and provides additional functionality + for logging messages to both console and file. Log files are rotated daily, and old log files are automatically + deleted after a specified number of days. + + Attributes: + log_folder (str): The directory where log files will be stored. + backupCount_days (int): The number of days to keep backup log files. + + Methods: + __init__: Initializes the custom logger. + """ def __init__(self, log_folder: str, backupCount_days: int = 30) -> None: """ Initializes the custom logger. diff --git a/setup.py b/setup.py index b298867..0280f23 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ """ from setuptools import setup, find_packages -with open('requirements.txt') as f: +with open('requirements.txt', encoding='utf-8') as f: requirements = f.read().splitlines() setup( diff --git a/tests/test_folder_operations.py b/tests/test_folder_operations.py index 6353bd9..35ef6d1 100644 --- a/tests/test_folder_operations.py +++ b/tests/test_folder_operations.py @@ -40,8 +40,8 @@ def teardown() -> None: # Register the teardown function to be called after tests request.addfinalizer(teardown) -@pytest.fixture(scope="function") -def folder_sync() -> FolderSynchronizer: +@pytest.fixture(scope="function", name="folder_sync") +def define_folder_sync() -> FolderSynchronizer: """ Fixture to provide a FolderSynchronizer instance for each test function. @@ -54,19 +54,19 @@ def test_synchronize_nonexistent_source_folder() -> None: """ Test synchronizing a non-existent source folder raises FileNotFoundError. """ - folder_sync_test = FolderSynchronizer('nonexistent_source', REPLICA_FOLDER, log) + folder_sync = FolderSynchronizer('nonexistent_source', REPLICA_FOLDER, log) with pytest.raises(FileNotFoundError): - folder_sync_test.synchronize() + folder_sync.synchronize() def test_synchronize_create_replica_folder() -> None: """ Test that the replica folder is created if it does not exist. """ custom_replica_folder = 'custom_replica_folder' - folder_sync_test = FolderSynchronizer(SOURCE_FOLDER, custom_replica_folder, log) + folder_sync = FolderSynchronizer(SOURCE_FOLDER, custom_replica_folder, log) - folder_sync_test.synchronize() + folder_sync.synchronize() assert os.path.exists(custom_replica_folder) From 91a2ff20a0864fd200bdc1e986ec8169dc798c4a Mon Sep 17 00:00:00 2001 From: Jmateusribeiro Date: Sun, 2 Jun 2024 17:03:24 +0100 Subject: [PATCH 10/14] refactoring v2 --- folder_synchronizer/classes/folder_operations.py | 12 ++++++------ folder_synchronizer/classes/log.py | 6 ++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/folder_synchronizer/classes/folder_operations.py b/folder_synchronizer/classes/folder_operations.py index 35ee1e5..8c66ec3 100644 --- a/folder_synchronizer/classes/folder_operations.py +++ b/folder_synchronizer/classes/folder_operations.py @@ -42,10 +42,10 @@ def synchronize(self) -> None: os.makedirs(self.replica_folder) self.log.warning(f"Replica folder was created: {self.replica_folder}") - self.__remove_items() - self.__create_or_update_items() + self.remove_items() + self.create_or_update_items() - def __remove_items(self) -> None: + def remove_items(self) -> None: """ Removes items from the replica folder that do not exist in the source folder. """ @@ -64,7 +64,7 @@ def __remove_items(self) -> None: except Exception as e: raise Exception(f"Error removing '{replica_item_path}' - {str(e)}") from e - def __create_or_update_items(self) -> None: + def create_or_update_items(self) -> None: """ Creates or updates items in the replica folder to match the source folder. """ @@ -79,8 +79,8 @@ def __create_or_update_items(self) -> None: elif os.path.isfile(source_item_path): try: - if os.path.exists(replica_item_path) and filecmp.cmp(source_item_path, - replica_item_path, shallow=False): + if os.path.exists(replica_item_path) and filecmp.cmp(source_item_path, + replica_item_path, shallow=False): continue if os.path.exists(source_item_path): diff --git a/folder_synchronizer/classes/log.py b/folder_synchronizer/classes/log.py index 21bd12e..6f87db5 100644 --- a/folder_synchronizer/classes/log.py +++ b/folder_synchronizer/classes/log.py @@ -10,8 +10,10 @@ class CustomLogger(Logger): """ A custom logger class that logs messages to both console and file with timed rotation. - This class extends the standard Logger class from the logging module and provides additional functionality - for logging messages to both console and file. Log files are rotated daily, and old log files are automatically + This class extends the standard Logger class + from the logging module and provides additional functionality + for logging messages to both console and file. + Log files are rotated daily, and old log files are automatically deleted after a specified number of days. Attributes: From 67211f6c30b42ee6edde2e39e9711922137176ab Mon Sep 17 00:00:00 2001 From: Jorge Ribeiro <50589410+Jmateusribeiro@users.noreply.github.com> Date: Sun, 2 Jun 2024 17:07:27 +0100 Subject: [PATCH 11/14] Update pylint.yml --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index d7005bf..b8a3263 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -21,4 +21,4 @@ jobs: pip install pytest - name: Analyzing the code with pylint run: | - pylint $(git ls-files '*.py') + pylint $(git ls-files '*.py') disable=logging-fstring-interpolation, broad-exception-caught From 7517cbc46c7a07d14be90e68550a24940aa76cdd Mon Sep 17 00:00:00 2001 From: Jorge Ribeiro <50589410+Jmateusribeiro@users.noreply.github.com> Date: Sun, 2 Jun 2024 17:09:02 +0100 Subject: [PATCH 12/14] Update pylint.yml --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index b8a3263..640be14 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -21,4 +21,4 @@ jobs: pip install pytest - name: Analyzing the code with pylint run: | - pylint $(git ls-files '*.py') disable=logging-fstring-interpolation, broad-exception-caught + pylint --disable=logging-fstring-interpolation, broad-exception-caugh $(git ls-files '*.py') From 3e696f9aa5cddf90af4ba5e6fb443d8cb778b11a Mon Sep 17 00:00:00 2001 From: Jorge Ribeiro <50589410+Jmateusribeiro@users.noreply.github.com> Date: Sun, 2 Jun 2024 17:12:00 +0100 Subject: [PATCH 13/14] Update pylint.yml --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 640be14..e482ccc 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -21,4 +21,4 @@ jobs: pip install pytest - name: Analyzing the code with pylint run: | - pylint --disable=logging-fstring-interpolation, broad-exception-caugh $(git ls-files '*.py') + pylint --disable=logging-fstring-interpolation,broad-exception-caugh,broad-exception-raised $(git ls-files '*.py') From 96905ca67a93d8c0b185d454d9ee97632c338469 Mon Sep 17 00:00:00 2001 From: Jorge Ribeiro <50589410+Jmateusribeiro@users.noreply.github.com> Date: Sun, 2 Jun 2024 17:14:07 +0100 Subject: [PATCH 14/14] Update pylint.yml --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index e482ccc..a7984b4 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -21,4 +21,4 @@ jobs: pip install pytest - name: Analyzing the code with pylint run: | - pylint --disable=logging-fstring-interpolation,broad-exception-caugh,broad-exception-raised $(git ls-files '*.py') + pylint --disable=logging-fstring-interpolation,broad-exception-caught,broad-exception-raised $(git ls-files '*.py')