From fc6fdd07f9ff0d92f18532e50ed627d27a1052b7 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 6 Jul 2022 12:52:59 -0400 Subject: [PATCH 001/195] merge python3_10 integration --- .github/workflows/ci.yml | 74 --------------- .github/workflows/deploy.yml | 34 +++++++ .github/workflows/docs.yml | 31 +++++++ .github/workflows/formatting.yml | 23 +++++ .github/workflows/linting.yml | 26 ++++++ .github/workflows/python-publish.yml | 2 +- .github/workflows/tests.yml | 46 ++++++++++ pyproject.toml | 11 ++- ...t_integration_fasttext_embeddings_model.py | 92 ++----------------- tests/parser/test_address_parser.py | 9 +- tests/requirements.txt | 1 + tests/test_fasttext_tools.py | 9 +- 12 files changed, 194 insertions(+), 164 deletions(-) delete mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/formatting.yml create mode 100644 .github/workflows/linting.yml create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index fe965c8d..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Continuous Integration - -on: [ push, pull_request ] - -jobs: - linuxbuild: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [ '3.7', '3.8', '3.9' ] - - steps: - - name: Set deploy environment variable - run: | - # DEEPARSE_RELEASE_BUILD is also used in setup.py - if [ $GITHUB_REF == "refs/heads/stable" ] && \ - [ $GITHUB_EVENT_NAME == "push" ] && \ - [ ${{ matrix.python-version }} == "3.8" ]; then - echo "DEEPPARSE_RELEASE_BUILD=1" >> $GITHUB_ENV - else - echo "DEEPPARSE_RELEASE_BUILD=0" >> $GITHUB_ENV - fi - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - pip install -Ur requirements.txt - pip install -Ur docs/requirements.txt - pip install -Ur tests/requirements.txt - pip install -Ur styling_requirements.txt - python setup.py develop - - name: Linting - run: | - black --diff --check . - pylint deepparse/ - pylint tests/ - - name: Test with pytest - run: | - python -m unittest - - name: Building doc - run: | - cd docs - ./rebuild_html_doc.sh - cd .. - - name: Deploy - if: env.DEEPPARSE_RELEASE_BUILD == '1' - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs/_build/html/ - - windowstesting: - runs-on: windows-latest - strategy: - matrix: - python-version: [ '3.7', '3.8', '3.9' ] - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - pip install -Ur requirements.txt - pip install -Ur tests/requirements.txt - python setup.py develop - - name: Test with pytest - run: | - python -m unittest diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..fcfdf599 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,34 @@ +name: Deployment + +on: + push: + branches: + - stable + +jobs: + deploy: + runs-on: ubuntu-latest + env: + DEEPPARSE_RELEASE_BUILD: "1" + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install dependencies + run: | + pip install -Ur requirements.txt + pip install -Ur docs/requirements.txt + python setup.py develop + - name: Building doc + run: | + cd docs + ./rebuild_html_doc.sh + cd .. + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/_build/html/ \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..c940b091 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,31 @@ +name: Docs + +on: + push: + pull_request: + branches: + - dev + +jobs: + docs: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10"] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install -Ur requirements.txt + pip install -Ur docs/requirements.txt + pip install -e . + - name: Building doc + run: | + cd docs + ./rebuild_html_doc.sh + cd .. \ No newline at end of file diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml new file mode 100644 index 00000000..e81bca29 --- /dev/null +++ b/.github/workflows/formatting.yml @@ -0,0 +1,23 @@ +name: Formatting + +on: [push, pull_request] + +jobs: + formatting: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10"] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install -Ur styling_requirements.txt + - name: Formatting + run: | + black --diff --check . \ No newline at end of file diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml new file mode 100644 index 00000000..3ddfab20 --- /dev/null +++ b/.github/workflows/linting.yml @@ -0,0 +1,26 @@ +name: Linting + +on: [push, pull_request] + +jobs: + linting: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10"] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install -Ur requirements.txt + pip install -Ur styling_requirements.txt + pip install -Ur tests/requirements.txt + pip install -e . + - name: PyLint + run: | + pylint deepparse/ tests/ \ No newline at end of file diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index e8d7b3a0..1713d323 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: '3.8' + python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..d332f603 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,46 @@ +name: Tests + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10"] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install -Ur requirements.txt + pip install -Ur tests/requirements.txt + python setup.py develop + - name: Test with pytest + run: | + pytest + + windowsbuild: + runs-on: windows-latest + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9"] # pip install requirements fail on 3.10 + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install -Ur requirements.txt + pip install -Ur tests/requirements.txt + python setup.py develop + - name: Test with pytest + run: | + pytest \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index bfaeb99e..fed5a3b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,12 @@ [tool.black] line-length = 120 skip-string-normalization = true -required-version = '22.3.0' -extend-exclude = '/(slides)/' +required-version = "22.3.0" +extend-exclude = "/(slides)/" + +[tool.pytest.ini_options] +addopts = "--cov ./deepparse --cov-report html --cov-report xml --cov-config=.coveragerc" + +testpaths = [ + "tests", +] \ No newline at end of file diff --git a/tests/embeddings_models/integration/test_integration_fasttext_embeddings_model.py b/tests/embeddings_models/integration/test_integration_fasttext_embeddings_model.py index 55ba0e25..3228e0aa 100644 --- a/tests/embeddings_models/integration/test_integration_fasttext_embeddings_model.py +++ b/tests/embeddings_models/integration/test_integration_fasttext_embeddings_model.py @@ -61,24 +61,6 @@ def test_givenAWindowsOS_whenFasttextModelCollateFnInDataLoader_thenWorkProperly dataset.append(data) self.assertGreater(len(dataset), 0) - @skipIf(platform.system() != "Windows", "Integration test on Windows env.") - def test_givenAWindowsOS_whenFasttextModelCollateFnInDataLoaderNumWorkers1_thenWorkProperly( - self, - ): - model = FastTextEmbeddingsModel(self.a_fasttext_model_path, verbose=self.verbose) - data_transform = MockedDataTransform(model) - - data_loader = DataLoader( - self.training_container, - collate_fn=data_transform.collate_fn, - batch_size=32, - num_workers=1, - ) - dataset = [] - for data in data_loader: - dataset.append(data) - self.assertGreater(len(dataset), 0) - @skipIf(platform.system() != "Windows", "Integration test on Windows env.") def test_givenAWindowsOS_whenFasttextModelCollateFnInDataLoaderNumWorkers2_thenWorkProperly( self, @@ -97,12 +79,12 @@ def test_givenAWindowsOS_whenFasttextModelCollateFnInDataLoaderNumWorkers2_thenW dataset.append(data) self.assertGreater(len(dataset), 0) - @skipIf(platform.system() != "Windows", "Integration test on Windows env.") + @skipIf(platform.system() == "Windows", "Integration test not on Windows env.") @patch("deepparse.embeddings_models.fasttext_embeddings_model.platform") - def test_givenAWindowsOS_whenFasttextModelCollateFnInDataLoaderEvenWithWindowsSetup_thenWorkProperly( + def test_givenUbuntu_whenFasttextModelCollateFnInDataLoaderEvenWithWindowsSetup_thenWorkProperly( self, platform_mock ): - platform_mock.system().__eq__.return_value = True + platform_mock.system().__eq__.return_value = False with platform_mock: model = FastTextEmbeddingsModel(self.a_fasttext_model_path, verbose=self.verbose) data_transform = MockedDataTransform(model) @@ -118,49 +100,14 @@ def test_givenAWindowsOS_whenFasttextModelCollateFnInDataLoaderEvenWithWindowsSe dataset.append(data) self.assertGreater(len(dataset), 0) - @skipIf(platform.system() != "Windows", "Integration test on Windows env.") - def test_givenAWindowsOS_whenFasttextModelCollateFnInDataLoaderForWindows_thenRaiseError( - self, - ): - model = FastTextEmbeddingsModel(self.a_fasttext_model_path, verbose=self.verbose) - - data_transform = MockedDataTransform(model) - - data_loader = DataLoader( - self.training_container, - collate_fn=data_transform.collate_fn, - batch_size=32, - num_workers=0, - ) - dataset = [] - for data in data_loader: - dataset.append(data) - self.assertGreater(len(dataset), 0) - - @skipIf(platform.system() != "Windows", "Integration test on Windows env.") - def test_givenAWindowsOS_whenFasttextModelCollateFnInDataLoaderForWindowsNumWorkers1_thenRaiseError( - self, - ): - model = FastTextEmbeddingsModel(self.a_fasttext_model_path, verbose=self.verbose) - - data_transform = MockedDataTransform(model) - - data_loader = DataLoader( - self.training_container, - collate_fn=data_transform.collate_fn, - batch_size=32, - num_workers=1, - ) - dataset = [] - for data in data_loader: - dataset.append(data) - self.assertGreater(len(dataset), 0) - - @skipIf(platform.system() != "Windows", "Integration test on Windows env.") - def test_givenAWindowsOS_whenFasttextModelCollateFnInDataLoaderForWindowsNumWorkers2_thenRaiseError( - self, + @skipIf(platform.system() == "Windows", "Integration test not on Windows env.") + @patch("deepparse.embeddings_models.fasttext_embeddings_model.platform") + def test_givenUbuntu_whenFasttextModelCollateFnInDataLoaderForWindowsNumWorkers2_thenWorkProperly( + self, platform_mock ): - model = FastTextEmbeddingsModel(self.a_fasttext_model_path, verbose=self.verbose) + platform_mock.system().__eq__.return_value = False + with platform_mock: + model = FastTextEmbeddingsModel(self.a_fasttext_model_path, verbose=self.verbose) data_transform = MockedDataTransform(model) @@ -174,22 +121,3 @@ def test_givenAWindowsOS_whenFasttextModelCollateFnInDataLoaderForWindowsNumWork for data in data_loader: dataset.append(data) self.assertGreater(len(dataset), 0) - - @skipIf(platform.system() != "Windows", "Integration test on Windows env.") - @patch("deepparse.embeddings_models.fasttext_embeddings_model.platform") - def test_givenAWindowsOS_whenFasttextModelCollateFnInDataLoaderForNotWindows_thenRaiseError(self, platform_mock): - platform_mock.system().__eq__.return_value = True - with platform_mock: - model = FastTextEmbeddingsModel(self.a_fasttext_model_path, verbose=self.verbose) - - data_transform = MockedDataTransform(model) - - data_loader = DataLoader( - self.training_container, - collate_fn=data_transform.collate_fn, - batch_size=32, - num_workers=0, - ) - with self.assertRaises(TypeError): - for _ in data_loader: - pass diff --git a/tests/parser/test_address_parser.py b/tests/parser/test_address_parser.py index 88ef71ea..6ae37aaf 100644 --- a/tests/parser/test_address_parser.py +++ b/tests/parser/test_address_parser.py @@ -3,6 +3,7 @@ import os import unittest +from tempfile import TemporaryDirectory from unittest import skipIf from unittest.mock import patch, MagicMock @@ -32,7 +33,9 @@ def setUpClass(cls): cls.a_gpu_torch_device = device(cls.a_gpu_device) cls.verbose = False cls.number_tags = 9 - cls.a_cache_dir = "a_cache_dir_path" + + cls.temp_dir_obj = TemporaryDirectory() + cls.a_cache_dir = cls.temp_dir_obj.name cls.correct_address_components = {"ATag": 0, "AnotherTag": 1, "EOS": 2} cls.incorrect_address_components = {"ATag": 0, "AnotherTag": 1} @@ -60,6 +63,10 @@ def setUpClass(cls): "EOS", ] + @classmethod + def tearDownClass(cls) -> None: + cls.temp_dir_obj.cleanup() + def setUp(self): super().setUp() self.BPEmb_mock = MagicMock() diff --git a/tests/requirements.txt b/tests/requirements.txt index 2b2c6b84..c68cf3af 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,3 +1,4 @@ pycountry pandas pytest +pytest_cov diff --git a/tests/test_fasttext_tools.py b/tests/test_fasttext_tools.py index bd63c540..1b9af11e 100644 --- a/tests/test_fasttext_tools.py +++ b/tests/test_fasttext_tools.py @@ -24,19 +24,20 @@ class ToolsTests(CaptureOutputTestCase): @classmethod def setUpClass(cls) -> None: cls.temp_dir_obj = TemporaryDirectory() - cls.a_directory_path = os.path.join(cls.temp_dir_obj.name, "./") + cls.a_directory_path = cls.temp_dir_obj.name cls.a_fasttext_file_name_path = os.path.join(cls.a_directory_path, "cc.fr.300.bin") cls.a_fasttext_gz_file_name_path = os.path.join(cls.a_directory_path, "cc.fr.300.bin.gz") cls.a_fasttext_light_name_path = os.path.join(cls.a_directory_path, "fasttext.magnitude") cls.a_fasttext_light_gz_file_name_path = os.path.join(cls.a_directory_path, "fasttext.magnitude.gz") - # the payload is a first "chunk" a, a second chunk "b" and a empty chunk "" to end the loop + # The payload is a first chunk "a", a second chunk "b" and a empty chunk ("") to end the loop cls.a_response_payload = ["a", "b", ""] cls.a_fake_embeddings_path = os.path.join(cls.temp_dir_obj.name, "fake_embeddings_cc.fr.300.bin") - def tearDown(self) -> None: - self.temp_dir_obj.cleanup() + @classmethod + def tearDownClass(cls) -> None: + cls.temp_dir_obj.cleanup() def assertStdoutContains(self, values): for value in values: From a4922ac24a5bb025bbcef8ba1ab5a7dc74be6512 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 6 Jul 2022 12:53:28 -0400 Subject: [PATCH 002/195] improve codecov script --- codecov_push.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/codecov_push.sh b/codecov_push.sh index d2881fab..62a00212 100755 --- a/codecov_push.sh +++ b/codecov_push.sh @@ -1,7 +1,20 @@ #!/bin/sh -pip install pytest-cov +# We do test on Python 3.10 + +# Create a new Python env for Deepparse tests and activate it +conda create --name deepparse_pytest_3_10 python=3.10 -y --force +conda activate deepparse_pytest_3_10 + +# Install dependencies pip install -Ur tests/requirements.txt pip install -Ur requirements.txt -pytest --cov=./deepparse --cov-report=xml --cov-config=.coveragerc + +# Run pytest from conda env +conda run -n deepparse_pytest_3_10 pytest --cov ./deepparse --cov-report html --cov-report xml --cov-config=.coveragerc + +# Push the coverage file ./codecov -f coverage.xml -n unittest-integrationtest -t $CODECOVKEYDEEPPARSE + +# close conda env +conda deactivate From 6f82c6e2483b7ff0efcb66cc44874c7ade4a7717 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 6 Jul 2022 13:03:23 -0400 Subject: [PATCH 003/195] improve codecov script with test verbosity --- codecov_push.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codecov_push.sh b/codecov_push.sh index 62a00212..0f7bbaa0 100755 --- a/codecov_push.sh +++ b/codecov_push.sh @@ -11,7 +11,9 @@ pip install -Ur tests/requirements.txt pip install -Ur requirements.txt # Run pytest from conda env -conda run -n deepparse_pytest_3_10 pytest --cov ./deepparse --cov-report html --cov-report xml --cov-config=.coveragerc +echo "Running test in Conda - No update" +# --live-stream is to remove the Conda capture of the stream +conda run -n deepparse_pytest_3_10 --live-stream pytest --cov ./deepparse --cov-report html --cov-report xml --cov-config=.coveragerc # Push the coverage file ./codecov -f coverage.xml -n unittest-integrationtest -t $CODECOVKEYDEEPPARSE From 0732c59820a7b11d7e2f43a0c20605d7352c71c8 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 6 Jul 2022 13:04:13 -0400 Subject: [PATCH 004/195] improve codecov script with test verbosity --- codecov_push.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codecov_push.sh b/codecov_push.sh index 0f7bbaa0..17195f6a 100755 --- a/codecov_push.sh +++ b/codecov_push.sh @@ -11,7 +11,7 @@ pip install -Ur tests/requirements.txt pip install -Ur requirements.txt # Run pytest from conda env -echo "Running test in Conda - No update" +echo "*****Running test in Conda*****" # --live-stream is to remove the Conda capture of the stream conda run -n deepparse_pytest_3_10 --live-stream pytest --cov ./deepparse --cov-report html --cov-report xml --cov-config=.coveragerc From 3f74aaed9f2e7c12e8afcc53f3bb5b8fec45c113 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 6 Jul 2022 13:06:21 -0400 Subject: [PATCH 005/195] add script to run tests on all python version supported --- run_tests_python_envs.sh | 69 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 run_tests_python_envs.sh diff --git a/run_tests_python_envs.sh b/run_tests_python_envs.sh new file mode 100644 index 00000000..42fda932 --- /dev/null +++ b/run_tests_python_envs.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +# We test on supported Python version, namely, 3.7, 3.8, 3.9 and 3.10 +echo "*****Starting of testing Deepparse on Python version 3.7, 3.8, 3.9, 3.10*****" + +# We export the reports into a directory. But to do so, we need to move into that directory +# and run pytest from there +mkdir -p export_html_reports +cd ./export_html_reports + +# Create a new Python env 3.7 +conda create --name deepparse_pytest_3_7 python=3.7 -y --force +conda activate deepparse_pytest_3_7 + +# Install dependencies +pip install -Ur tests/requirements.txt +pip install -Ur requirements.txt + +# Run pytest from conda env +echo "*****Running test in Conda Python version 3.7*****" +conda run -n deepparse_pytest_3_7 --live-stream pytest --cov ../deepparse --cov-report html:html_report_3_7 --cov-report xml:export_xml_report_3_7.xml --cov-config=.coveragerc ../tests + +# close conda env +conda deactivate + +# Create a new Python env 3.8 +conda create --name deepparse_pytest_3_8 python=3.8 -y --force +conda activate deepparse_pytest_3_8 + +# Install dependencies +pip install -Ur tests/requirements.txt +pip install -Ur requirements.txt + +# Run pytest from conda env +echo "*****Running test in Conda Python version 3.8*****" +conda run -n deepparse_pytest_3_8 --live-stream pytest --cov ../deepparse --cov-report html:html_report_3_8 --cov-report xml:export_xml_report_3_8.xml --cov-config=.coveragerc ../tests + +# close conda env +conda deactivate + +# Create a new Python env 3.9 +conda create --name deepparse_pytest_3_9 python=3.9 -y --force +conda activate deepparse_pytest_3_9 + +# Install dependencies +pip install -Ur tests/requirements.txt +pip install -Ur requirements.txt + +# Run pytest from conda env +echo "*****Running test in Conda Python version 3.9*****" +conda run -n deepparse_pytest_3_9 --live-stream pytest --cov ../deepparse --cov-report html:html_report_3_9 --cov-report xml:export_xml_report_3_9.xml --cov-config=.coveragerc ../tests + +# close conda env +conda deactivate + +# Create a new Python env 3.10 +conda create --name deepparse_pytest_3_10 python=3.10 -y --force +conda activate deepparse_pytest_3_10 + +# Install dependencies +pip install -Ur tests/requirements.txt +pip install -Ur requirements.txt + +# Run pytest from conda env +echo "*****Running test in Conda Python version 3.10*****" +conda run -n deepparse_pytest_3_10 --live-stream pytest --cov ../deepparse --cov-report html:html_report_3_10 --cov-report xml:export_xml_report_3_10.xml --cov-config=.coveragerc ../tests + +# close conda env +conda deactivate From 802793cd69f6ce4791f74671883b93bd041bde67 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 6 Jul 2022 13:26:11 -0400 Subject: [PATCH 006/195] fix path management --- run_tests_python_envs.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/run_tests_python_envs.sh b/run_tests_python_envs.sh index 42fda932..b9f2ed94 100644 --- a/run_tests_python_envs.sh +++ b/run_tests_python_envs.sh @@ -13,8 +13,8 @@ conda create --name deepparse_pytest_3_7 python=3.7 -y --force conda activate deepparse_pytest_3_7 # Install dependencies -pip install -Ur tests/requirements.txt -pip install -Ur requirements.txt +pip install -Ur ../tests/requirements.txt +pip install -Ur ../requirements.txt # Run pytest from conda env echo "*****Running test in Conda Python version 3.7*****" @@ -28,8 +28,8 @@ conda create --name deepparse_pytest_3_8 python=3.8 -y --force conda activate deepparse_pytest_3_8 # Install dependencies -pip install -Ur tests/requirements.txt -pip install -Ur requirements.txt +pip install -Ur ../tests/requirements.txt +pip install -Ur ../requirements.txt # Run pytest from conda env echo "*****Running test in Conda Python version 3.8*****" @@ -43,8 +43,8 @@ conda create --name deepparse_pytest_3_9 python=3.9 -y --force conda activate deepparse_pytest_3_9 # Install dependencies -pip install -Ur tests/requirements.txt -pip install -Ur requirements.txt +pip install -Ur ../tests/requirements.txt +pip install -Ur ../requirements.txt # Run pytest from conda env echo "*****Running test in Conda Python version 3.9*****" @@ -58,8 +58,8 @@ conda create --name deepparse_pytest_3_10 python=3.10 -y --force conda activate deepparse_pytest_3_10 # Install dependencies -pip install -Ur tests/requirements.txt -pip install -Ur requirements.txt +pip install -Ur ../tests/requirements.txt +pip install -Ur ../requirements.txt # Run pytest from conda env echo "*****Running test in Conda Python version 3.10*****" From bee19af8ed3af8fab372fd6a40af67a8e9724e9f Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 6 Jul 2022 13:27:20 -0400 Subject: [PATCH 007/195] change mod of executable file --- run_tests_python_envs.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 run_tests_python_envs.sh diff --git a/run_tests_python_envs.sh b/run_tests_python_envs.sh old mode 100644 new mode 100755 From 15f0908123c38839d4f8d9edcda732dc448a5d7c Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 6 Jul 2022 13:36:49 -0400 Subject: [PATCH 008/195] add interactive shell to handle conda --- run_tests_python_envs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_tests_python_envs.sh b/run_tests_python_envs.sh index b9f2ed94..5fb81752 100755 --- a/run_tests_python_envs.sh +++ b/run_tests_python_envs.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash -l # We test on supported Python version, namely, 3.7, 3.8, 3.9 and 3.10 echo "*****Starting of testing Deepparse on Python version 3.7, 3.8, 3.9, 3.10*****" From acaa54fc9e58e8e7cc04ccea398b4e8431408c8b Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 6 Jul 2022 13:41:22 -0400 Subject: [PATCH 009/195] remove shebang arg --- run_tests_python_envs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_tests_python_envs.sh b/run_tests_python_envs.sh index 5fb81752..b9f2ed94 100755 --- a/run_tests_python_envs.sh +++ b/run_tests_python_envs.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash -l +#!/bin/sh # We test on supported Python version, namely, 3.7, 3.8, 3.9 and 3.10 echo "*****Starting of testing Deepparse on Python version 3.7, 3.8, 3.9, 3.10*****" From 6e2a809fd6e0fa466315db51b380ec0322a4615d Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 6 Jul 2022 13:45:57 -0400 Subject: [PATCH 010/195] fix contributing and minor typo in tests script --- .github/CONTRIBUTING.md | 24 ++++++++++++++++-------- run_tests_python_envs.sh | 2 +- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 3c8b8ae4..a740549f 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -64,14 +64,14 @@ We created three requirements files to install all the tools used for the develo You can install all the requirements with -``` +``` shell pip install -r tests/requirements.txt pip install -r styling_requirements.txt pip install -r docs/requirements.txt ``` Also, you should run `python setup.py develop` to build the project and be able to build the documentation. -``` +``` shell python setup.py develop ``` @@ -80,14 +80,14 @@ python setup.py develop All of the code is formatted using [black](https://black.readthedocs.io) with the associated [config file](https://github.com/GRAAL-Research/deepparse/blob/master/pyproject.toml). In order to format the code of your submission, simply run > See the [styling requirements](https://github.com/GRAAL-Research/deepparse/blob/master/styling_requirements.txt) for the proper black version to use. -``` +``` shell black . ``` We also have our own `pylint` [config file](https://github.com/GRAAL-Research/deepparse/blob/master/.pylintrc). Try not to introduce code incoherences detected by the linting. You can run the linting procedure with > See the [styling requirements](https://github.com/GRAAL-Research/deepparse/blob/master/styling_requirements.txt) for the proper pylint version to use. -``` +``` shell pylint deepparse pylint tests ``` @@ -98,32 +98,40 @@ If your pull request introduces a new feature, please deliver it with tests that For any pull request submitted, **ALL** of the tests must succeed. You can run the tests with -``` +``` shell python -m unittest ``` > The integration tests need to be executed on a device with a GPU. +We also provide a script, `run_tests_python_envs.sh`, to run all the tests in all the supported versions. To do so, you need to install Conda and run +the following command + +``` shell +bash -l run_tests_python_envs.sh # For bash terminal +zsh -i run_tests_python_envs.sh # For ZSH terminal +``` + ## Documentation When submitting a pull request for a new feature, try to include documentation for the new objects/modules introduced and their public methods. All of Deepparse's html documentation is automatically generated from the Python files' documentation. To have a preview of what the final html will look like with your modifications, first start by rebuilding the html pages. - ``` + ``` shell cd docs ./rebuild_html_doc.sh ``` You can then see the local html files in your favorite browser. Here is an example using Firefox: -``` +``` shell firefox _build/html/index.html ``` or using Python -```python +```shell python -m http.server -d _build/html/ ``` diff --git a/run_tests_python_envs.sh b/run_tests_python_envs.sh index b9f2ed94..a1f8c639 100755 --- a/run_tests_python_envs.sh +++ b/run_tests_python_envs.sh @@ -1,6 +1,6 @@ #!/bin/sh -# We test on supported Python version, namely, 3.7, 3.8, 3.9 and 3.10 +# We test on Deepparse supported Python versions, namely, 3.7, 3.8, 3.9 and 3.10 echo "*****Starting of testing Deepparse on Python version 3.7, 3.8, 3.9, 3.10*****" # We export the reports into a directory. But to do so, we need to move into that directory From b3f1b4bf21c3dcecdce0888747dfc3544b51474e Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 8 Jul 2022 13:47:13 -0400 Subject: [PATCH 011/195] improv example code and remove dead example --- docs/source/examples/retrained_model_parsing.rst | 9 +++++---- examples/parse_address_csv_dataset.py | 0 examples/retrained_model_parsing.py | 9 +++++---- 3 files changed, 10 insertions(+), 8 deletions(-) delete mode 100644 examples/parse_address_csv_dataset.py diff --git a/docs/source/examples/retrained_model_parsing.rst b/docs/source/examples/retrained_model_parsing.rst index de54a94d..43a877ca 100644 --- a/docs/source/examples/retrained_model_parsing.rst +++ b/docs/source/examples/retrained_model_parsing.rst @@ -17,10 +17,10 @@ First, let's download the train and test data from the public repository. .. code-block:: python - saving_dir = "./data" + data_saving_dir = "./data" file_extension = "p" test_dataset_name = "predict" - download_from_url(test_dataset_name, saving_dir, file_extension=file_extension) + download_from_url(test_dataset_name, data_saving_dir, file_extension=file_extension) Now let's load the dataset using one of our dataset container. @@ -33,13 +33,14 @@ Let's download a BPEmb retrained model create just for this example, but you can .. code-block:: python + model_saving_dir = "./retrained_models" retrained_model_name = "retrained_light_bpemb_address_parser" model_file_extension = "ckpt" - download_from_url(retrained_model_name, saving_dir, file_extension=model_file_extension) + download_from_url(retrained_model_name, model_saving_dir, file_extension=model_file_extension) address_parser = AddressParser( model_type="bpemb", device=0, - path_to_retrained_model=os.path.join(saving_dir, retrained_model_name + "." + model_file_extension) + path_to_retrained_model=os.path.join(model_saving_dir, retrained_model_name + "." + model_file_extension) ) We can now parse some addresses diff --git a/examples/parse_address_csv_dataset.py b/examples/parse_address_csv_dataset.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/retrained_model_parsing.py b/examples/retrained_model_parsing.py index df05ef19..00c47185 100644 --- a/examples/retrained_model_parsing.py +++ b/examples/retrained_model_parsing.py @@ -6,23 +6,24 @@ # Here is an example on how to parse multiple addresses # First, let's download the train and test data from the public repository. -saving_dir = "./data" +data_saving_dir = "./data" file_extension = "p" test_dataset_name = "predict" -download_from_url(test_dataset_name, saving_dir, file_extension=file_extension) +download_from_url(test_dataset_name, data_saving_dir, file_extension=file_extension) # Now let's load the dataset using one of our dataset container addresses_to_parse = PickleDatasetContainer("./data/predict.p", is_training_container=False) # Let's download a BPEmb retrained model create just for this example, but you can also use one of yours. +model_saving_dir = "./retrained_models" retrained_model_name = "retrained_light_bpemb_address_parser" model_file_extension = "ckpt" -download_from_url(retrained_model_name, saving_dir, file_extension=model_file_extension) +download_from_url(retrained_model_name, model_saving_dir, file_extension=model_file_extension) address_parser = AddressParser( model_type="bpemb", device=0, - path_to_retrained_model=os.path.join(saving_dir, retrained_model_name + "." + model_file_extension), + path_to_retrained_model=os.path.join(model_saving_dir, retrained_model_name + "." + model_file_extension), ) # We can now parse some addresses From ebaeeb0a86a08a9cf1cd8cc815b94dbc98c9e12e Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 8 Jul 2022 15:04:23 -0400 Subject: [PATCH 012/195] squash handling from url branch --- CHANGELOG.md | 4 +- deepparse/fasttext_tools.py | 4 +- deepparse/tools.py | 13 ++++--- docs/source/examples/fine_tuning.rst | 6 +-- .../examples/fine_tuning_with_csv_dataset.rst | 6 +-- docs/source/examples/parse_addresses.rst | 4 +- .../examples/retrain_attention_model.rst | 6 +-- .../retrain_with_new_prediction_tags.rst | 6 +-- .../retrain_with_new_seq2seq_params.rst | 6 +-- .../examples/retrained_model_parsing.rst | 6 +-- examples/fine_tuning.py | 6 +-- examples/fine_tuning_with_csv_dataset.py | 6 +-- examples/parse_addresses.py | 4 +- examples/retrain_attention_model.py | 6 +-- examples/retrain_with_new_prediction_tags.py | 6 +-- examples/retrain_with_new_seq2seq_params.py | 6 +-- examples/retrained_model_parsing.py | 6 +-- models_evaluation/speed_test_evaluation.py | 4 +- ...t_integration_fasttext_embeddings_model.py | 4 +- tests/network/integration/base.py | 10 ++--- .../integration/test_integration_decoder.py | 4 +- .../integration/test_integration_encoder.py | 4 +- tests/parser/base.py | 8 ++-- tests/parser/integration/base_predict.py | 4 +- tests/parser/integration/base_retrain.py | 8 ++-- ...tion_address_parser_new_params_new_tags.py | 4 +- ...s_parser_retrain_new_address_components.py | 4 +- tests/test_fasttext_tools.py | 10 ++--- tests/test_tools.py | 38 +++++++++---------- 29 files changed, 103 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7537721d..3b643fd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -231,4 +231,6 @@ to `cache_dir`. `saving_dir` is now deprecated and will be remove in version 0.8 - Add hyphen parsing cleaning step (with a bool flag to activate or not) to improve some country address parsing (see [issue 137](https://github.com/GRAAL-Research/deepparse/issues/137)). - Add ListDatasetContainer for Python list dataset. -## dev \ No newline at end of file +## dev + +- Refactored function `download_from_url` to `download_from_public_repository`. \ No newline at end of file diff --git a/deepparse/fasttext_tools.py b/deepparse/fasttext_tools.py index bd9ad64f..65152f65 100644 --- a/deepparse/fasttext_tools.py +++ b/deepparse/fasttext_tools.py @@ -7,7 +7,7 @@ from fasttext.FastText import _FastText -from .tools import download_from_url +from .tools import download_from_public_repository def download_fasttext_magnitude_embeddings(cache_dir: str, verbose: bool = True, saving_dir=None) -> str: @@ -34,7 +34,7 @@ def download_fasttext_magnitude_embeddings(cache_dir: str, verbose: bool = True, "this process will take several minutes." ) extension = extension + ".gz" - download_from_url(file_name=model, saving_dir=cache_dir, file_extension=extension) + download_from_public_repository(file_name=model, saving_dir=cache_dir, file_extension=extension) gz_file_name = file_name + ".gz" with gzip.open(os.path.join(cache_dir, gz_file_name), "rb") as f: with open(os.path.join(cache_dir, file_name), "wb") as f_out: diff --git a/deepparse/tools.py b/deepparse/tools.py index 81837122..df2943c1 100644 --- a/deepparse/tools.py +++ b/deepparse/tools.py @@ -1,6 +1,7 @@ import os import shutil import warnings +from pathlib import Path from typing import List import poutyne @@ -37,7 +38,7 @@ def latest_version(model: str, cache_path: str, verbose: bool) -> bool: tmp_cache = os.path.join(cache_path, "tmp") os.makedirs(tmp_cache, exist_ok=True) - download_from_url(model, tmp_cache, "version") + download_from_public_repository(model, tmp_cache, "version") # Reading of the server-side version with open(os.path.join(tmp_cache, model + ".version"), encoding="utf-8") as remote_model_hash_file: @@ -81,16 +82,16 @@ def latest_version(model: str, cache_path: str, verbose: bool) -> bool: return is_latest_version -def download_from_url(file_name: str, saving_dir: str, file_extension: str): +def download_from_public_repository(file_name: str, saving_dir: str, file_extension: str): """ - Simple function to download the content of a file from a distant repository. + Simple function to download the content of a file from Deepparse public repository. The repository URL string is ̀`'https://graal.ift.ulaval.ca/public/deepparse/{}.{}'`` where the first bracket is the file name and the second is the file extension. """ url = BASE_URL.format(file_name, file_extension) r = requests.get(url, timeout=5) r.raise_for_status() # Raise exception - os.makedirs(saving_dir, exist_ok=True) + Path(saving_dir).mkdir(parents=True, exist_ok=True) with open(os.path.join(saving_dir, f"{file_name}.{file_extension}"), "wb") as file: file.write(r.content) @@ -105,8 +106,8 @@ def download_weights(model: str, saving_dir: str, verbose: bool = True) -> None: """ if verbose: print(f"Downloading the weights for the network {model}.") - download_from_url(model, saving_dir, "ckpt") - download_from_url(model, saving_dir, "version") + download_from_public_repository(model, saving_dir, "ckpt") + download_from_public_repository(model, saving_dir, "version") def handle_poutyne_version() -> str: diff --git a/docs/source/examples/fine_tuning.rst b/docs/source/examples/fine_tuning.rst index 4e39626b..e30dab2e 100644 --- a/docs/source/examples/fine_tuning.rst +++ b/docs/source/examples/fine_tuning.rst @@ -8,7 +8,7 @@ Retrain a Pretrained Model import poutyne - from deepparse import download_from_url + from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer from deepparse.parser import AddressParser @@ -21,8 +21,8 @@ First, let's download the train and test data from the public repository. file_extension = "p" training_dataset_name = "sample_incomplete_data" test_dataset_name = "test_sample_data" - download_from_url(training_dataset_name, saving_dir, file_extension=file_extension) - download_from_url(test_dataset_name, saving_dir, file_extension=file_extension) + download_from_public_repository(training_dataset_name, saving_dir, file_extension=file_extension) + download_from_public_repository(test_dataset_name, saving_dir, file_extension=file_extension) Now let's create a training and test container. diff --git a/docs/source/examples/fine_tuning_with_csv_dataset.rst b/docs/source/examples/fine_tuning_with_csv_dataset.rst index 2699a406..f5c67193 100644 --- a/docs/source/examples/fine_tuning_with_csv_dataset.rst +++ b/docs/source/examples/fine_tuning_with_csv_dataset.rst @@ -8,7 +8,7 @@ Retrain a Pretrained Model Using a CSV Dataset import poutyne - from deepparse import download_from_url + from deepparse import download_from_public_repository from deepparse.dataset_container import CSVDatasetContainer from deepparse.parser import AddressParser @@ -21,8 +21,8 @@ First, let's download the train and test data from the public repository. file_extension = "csv" training_dataset_name = "sample_incomplete_data" test_dataset_name = "test_sample_data" - download_from_url(training_dataset_name, saving_dir, file_extension=file_extension) - download_from_url(test_dataset_name, saving_dir, file_extension=file_extension) + download_from_public_repository(training_dataset_name, saving_dir, file_extension=file_extension) + download_from_public_repository(test_dataset_name, saving_dir, file_extension=file_extension) Now let's create a training and test container. diff --git a/docs/source/examples/parse_addresses.rst b/docs/source/examples/parse_addresses.rst index 20db10d6..3df6da91 100644 --- a/docs/source/examples/parse_addresses.rst +++ b/docs/source/examples/parse_addresses.rst @@ -8,7 +8,7 @@ Parse Addresses import pandas as pd - from deepparse import download_from_url + from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer from deepparse.parser import AddressParser @@ -19,7 +19,7 @@ Here is an example on how to parse multiple addresses. First, let's download the saving_dir = "./data" file_extension = "p" test_dataset_name = "predict" - download_from_url(test_dataset_name, saving_dir, file_extension=file_extension) + download_from_public_repository(test_dataset_name, saving_dir, file_extension=file_extension) Now let's load the dataset using one of our dataset container diff --git a/docs/source/examples/retrain_attention_model.rst b/docs/source/examples/retrain_attention_model.rst index 57a14f0c..5f134b0a 100644 --- a/docs/source/examples/retrain_attention_model.rst +++ b/docs/source/examples/retrain_attention_model.rst @@ -10,7 +10,7 @@ Retrain an attention mechanism model import poutyne - from deepparse import download_from_url + from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer from deepparse.parser import AddressParser @@ -22,8 +22,8 @@ First, let's download the train and test data with "new tags" from the public re file_extension = "p" training_dataset_name = "sample_incomplete_data" test_dataset_name = "test_sample_data" - download_from_url(training_dataset_name, saving_dir, file_extension=file_extension) - download_from_url(test_dataset_name, saving_dir, file_extension=file_extension) + download_from_public_repository(training_dataset_name, saving_dir, file_extension=file_extension) + download_from_public_repository(test_dataset_name, saving_dir, file_extension=file_extension) Now let's create a training and test container. diff --git a/docs/source/examples/retrain_with_new_prediction_tags.rst b/docs/source/examples/retrain_with_new_prediction_tags.rst index 05d8e0af..29297a8c 100644 --- a/docs/source/examples/retrain_with_new_prediction_tags.rst +++ b/docs/source/examples/retrain_with_new_prediction_tags.rst @@ -10,7 +10,7 @@ Retrain With New Prediction Tags import poutyne - from deepparse import download_from_url + from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer from deepparse.parser import AddressParser @@ -22,8 +22,8 @@ First, let's download the train and test data with "new tags" from the public re file_extension = "p" training_dataset_name = "sample_incomplete_data_new_prediction_tags" test_dataset_name = "test_sample_data_new_prediction_tags" - download_from_url(training_dataset_name, saving_dir, file_extension=file_extension) - download_from_url(test_dataset_name, saving_dir, file_extension=file_extension) + download_from_public_repository(training_dataset_name, saving_dir, file_extension=file_extension) + download_from_public_repository(test_dataset_name, saving_dir, file_extension=file_extension) Now, let's create a training and test container. diff --git a/docs/source/examples/retrain_with_new_seq2seq_params.rst b/docs/source/examples/retrain_with_new_seq2seq_params.rst index d4dd9c90..87cb7f79 100644 --- a/docs/source/examples/retrain_with_new_seq2seq_params.rst +++ b/docs/source/examples/retrain_with_new_seq2seq_params.rst @@ -10,7 +10,7 @@ Retrain With New Seq2Seq Parameters import poutyne - from deepparse import download_from_url + from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer from deepparse.parser import AddressParser @@ -22,8 +22,8 @@ First, let's download the train and test data with "new tags" from the public re file_extension = "p" training_dataset_name = "sample_incomplete_data_new_prediction_tags" test_dataset_name = "test_sample_data_new_prediction_tags" - download_from_url(training_dataset_name, saving_dir, file_extension=file_extension) - download_from_url(test_dataset_name, saving_dir, file_extension=file_extension) + download_from_public_repository(training_dataset_name, saving_dir, file_extension=file_extension) + download_from_public_repository(test_dataset_name, saving_dir, file_extension=file_extension) Now, let's create a training and test container. diff --git a/docs/source/examples/retrained_model_parsing.rst b/docs/source/examples/retrained_model_parsing.rst index 43a877ca..f12fda6c 100644 --- a/docs/source/examples/retrained_model_parsing.rst +++ b/docs/source/examples/retrained_model_parsing.rst @@ -8,7 +8,7 @@ Use a Retrained Model to Parse Addresses import os - from deepparse import download_from_url + from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer from deepparse.parser import AddressParser @@ -20,7 +20,7 @@ First, let's download the train and test data from the public repository. data_saving_dir = "./data" file_extension = "p" test_dataset_name = "predict" - download_from_url(test_dataset_name, data_saving_dir, file_extension=file_extension) + download_from_public_repository(test_dataset_name, data_saving_dir, file_extension=file_extension) Now let's load the dataset using one of our dataset container. @@ -36,7 +36,7 @@ Let's download a BPEmb retrained model create just for this example, but you can model_saving_dir = "./retrained_models" retrained_model_name = "retrained_light_bpemb_address_parser" model_file_extension = "ckpt" - download_from_url(retrained_model_name, model_saving_dir, file_extension=model_file_extension) + download_from_public_repository(retrained_model_name, model_saving_dir, file_extension=model_file_extension) address_parser = AddressParser( model_type="bpemb", device=0, diff --git a/examples/fine_tuning.py b/examples/fine_tuning.py index bafbb4dc..edb7afba 100644 --- a/examples/fine_tuning.py +++ b/examples/fine_tuning.py @@ -1,7 +1,7 @@ import os import poutyne -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer from deepparse.parser import AddressParser @@ -10,8 +10,8 @@ file_extension = "p" training_dataset_name = "sample_incomplete_data" test_dataset_name = "test_sample_data" -download_from_url(training_dataset_name, saving_dir, file_extension=file_extension) -download_from_url(test_dataset_name, saving_dir, file_extension=file_extension) +download_from_public_repository(training_dataset_name, saving_dir, file_extension=file_extension) +download_from_public_repository(test_dataset_name, saving_dir, file_extension=file_extension) # Now let's create a training and test container. training_container = PickleDatasetContainer(os.path.join(saving_dir, training_dataset_name + "." + file_extension)) diff --git a/examples/fine_tuning_with_csv_dataset.py b/examples/fine_tuning_with_csv_dataset.py index d6ebc051..3d0bb0a3 100644 --- a/examples/fine_tuning_with_csv_dataset.py +++ b/examples/fine_tuning_with_csv_dataset.py @@ -2,7 +2,7 @@ import poutyne -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.dataset_container import CSVDatasetContainer from deepparse.parser import AddressParser @@ -11,8 +11,8 @@ file_extension = "csv" training_dataset_name = "sample_incomplete_data" test_dataset_name = "test_sample_data" -download_from_url(training_dataset_name, saving_dir, file_extension=file_extension) -download_from_url(test_dataset_name, saving_dir, file_extension=file_extension) +download_from_public_repository(training_dataset_name, saving_dir, file_extension=file_extension) +download_from_public_repository(test_dataset_name, saving_dir, file_extension=file_extension) # Now let's create a training and test container. training_container = CSVDatasetContainer( diff --git a/examples/parse_addresses.py b/examples/parse_addresses.py index dfbe87ce..9e4ec8f2 100644 --- a/examples/parse_addresses.py +++ b/examples/parse_addresses.py @@ -1,6 +1,6 @@ import pandas as pd -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer from deepparse.parser import AddressParser @@ -9,7 +9,7 @@ saving_dir = "./data" file_extension = "p" test_dataset_name = "predict" -download_from_url(test_dataset_name, saving_dir, file_extension=file_extension) +download_from_public_repository(test_dataset_name, saving_dir, file_extension=file_extension) # Now let's load the dataset using one of our dataset container addresses_to_parse = PickleDatasetContainer("./data/predict.p", is_training_container=False) diff --git a/examples/retrain_attention_model.py b/examples/retrain_attention_model.py index f498985b..e97ba4f3 100644 --- a/examples/retrain_attention_model.py +++ b/examples/retrain_attention_model.py @@ -2,7 +2,7 @@ import poutyne -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer from deepparse.parser import AddressParser @@ -11,8 +11,8 @@ file_extension = "p" training_dataset_name = "sample_incomplete_data" test_dataset_name = "test_sample_data" -download_from_url(training_dataset_name, saving_dir, file_extension=file_extension) -download_from_url(test_dataset_name, saving_dir, file_extension=file_extension) +download_from_public_repository(training_dataset_name, saving_dir, file_extension=file_extension) +download_from_public_repository(test_dataset_name, saving_dir, file_extension=file_extension) # Now let's create a training and test container. training_container = PickleDatasetContainer(os.path.join(saving_dir, training_dataset_name + "." + file_extension)) diff --git a/examples/retrain_with_new_prediction_tags.py b/examples/retrain_with_new_prediction_tags.py index c49a8fa8..492b95fa 100644 --- a/examples/retrain_with_new_prediction_tags.py +++ b/examples/retrain_with_new_prediction_tags.py @@ -2,7 +2,7 @@ import poutyne -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer from deepparse.parser import AddressParser @@ -11,8 +11,8 @@ file_extension = "p" training_dataset_name = "sample_incomplete_data_new_prediction_tags" test_dataset_name = "test_sample_data_new_prediction_tags" -download_from_url(training_dataset_name, saving_dir, file_extension=file_extension) -download_from_url(test_dataset_name, saving_dir, file_extension=file_extension) +download_from_public_repository(training_dataset_name, saving_dir, file_extension=file_extension) +download_from_public_repository(test_dataset_name, saving_dir, file_extension=file_extension) # Now let's create a training and test container. training_container = PickleDatasetContainer(os.path.join(saving_dir, training_dataset_name + "." + file_extension)) diff --git a/examples/retrain_with_new_seq2seq_params.py b/examples/retrain_with_new_seq2seq_params.py index 2af5df0e..318c62cc 100644 --- a/examples/retrain_with_new_seq2seq_params.py +++ b/examples/retrain_with_new_seq2seq_params.py @@ -2,7 +2,7 @@ import poutyne -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer from deepparse.parser import AddressParser @@ -11,8 +11,8 @@ file_extension = "p" training_dataset_name = "sample_incomplete_data_new_prediction_tags" test_dataset_name = "test_sample_data_new_prediction_tags" -download_from_url(training_dataset_name, saving_dir, file_extension=file_extension) -download_from_url(test_dataset_name, saving_dir, file_extension=file_extension) +download_from_public_repository(training_dataset_name, saving_dir, file_extension=file_extension) +download_from_public_repository(test_dataset_name, saving_dir, file_extension=file_extension) # Now let's create a training and test container. training_container = PickleDatasetContainer(os.path.join(saving_dir, training_dataset_name + "." + file_extension)) diff --git a/examples/retrained_model_parsing.py b/examples/retrained_model_parsing.py index 00c47185..d4e5b457 100644 --- a/examples/retrained_model_parsing.py +++ b/examples/retrained_model_parsing.py @@ -1,6 +1,6 @@ import os -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer from deepparse.parser import AddressParser @@ -9,7 +9,7 @@ data_saving_dir = "./data" file_extension = "p" test_dataset_name = "predict" -download_from_url(test_dataset_name, data_saving_dir, file_extension=file_extension) +download_from_public_repository(test_dataset_name, data_saving_dir, file_extension=file_extension) # Now let's load the dataset using one of our dataset container addresses_to_parse = PickleDatasetContainer("./data/predict.p", is_training_container=False) @@ -18,7 +18,7 @@ model_saving_dir = "./retrained_models" retrained_model_name = "retrained_light_bpemb_address_parser" model_file_extension = "ckpt" -download_from_url(retrained_model_name, model_saving_dir, file_extension=model_file_extension) +download_from_public_repository(retrained_model_name, model_saving_dir, file_extension=model_file_extension) address_parser = AddressParser( model_type="bpemb", diff --git a/models_evaluation/speed_test_evaluation.py b/models_evaluation/speed_test_evaluation.py index 7990ab0c..1a1322ec 100644 --- a/models_evaluation/speed_test_evaluation.py +++ b/models_evaluation/speed_test_evaluation.py @@ -2,11 +2,11 @@ import pickle from statistics import mean -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.parser import AddressParser from models_evaluation.timer.timer import Timer -download_from_url("speed_test_dataset", "./data", "p") +download_from_public_repository("speed_test_dataset", "./data", "p") addresses = pickle.load(open("./data/speed_test_dataset.p", "rb")) addresses, tags = zip(*addresses) diff --git a/tests/embeddings_models/integration/test_integration_fasttext_embeddings_model.py b/tests/embeddings_models/integration/test_integration_fasttext_embeddings_model.py index 3228e0aa..483a005b 100644 --- a/tests/embeddings_models/integration/test_integration_fasttext_embeddings_model.py +++ b/tests/embeddings_models/integration/test_integration_fasttext_embeddings_model.py @@ -8,7 +8,7 @@ from gensim.models.fasttext import FastTextKeyedVectors from torch.utils.data import DataLoader -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.embeddings_models import FastTextEmbeddingsModel from tests.embeddings_models.integration.tools import MockedDataTransform from tests.parser.integration.base_retrain import AddressParserRetrainTestCase @@ -21,7 +21,7 @@ def setUpClass(cls): cls.file_name = "fake_embeddings_cc.fr.300" # We download fake embeddings for the tests cls.temp_dir_obj = TemporaryDirectory() cls.fake_cache_path = os.path.join(cls.temp_dir_obj.name, "fake_cache") - download_from_url(cls.file_name, cls.fake_cache_path, "bin") + download_from_public_repository(cls.file_name, cls.fake_cache_path, "bin") cls.a_fasttext_model_path = os.path.join(cls.fake_cache_path, cls.file_name + ".bin") diff --git a/tests/network/integration/base.py b/tests/network/integration/base.py index 03b04b9b..a2624cde 100644 --- a/tests/network/integration/base.py +++ b/tests/network/integration/base.py @@ -7,7 +7,7 @@ import torch -from deepparse import download_from_url, download_weights +from deepparse import download_from_public_repository, download_weights class Seq2SeqIntegrationTestCase(TestCase): @@ -30,13 +30,13 @@ def setUpClass(cls): cls.temp_dir_obj = TemporaryDirectory() cls.weights_dir = os.path.join(cls.temp_dir_obj.name, "./weights") - download_from_url(file_name="to_predict_bpemb", saving_dir=cls.weights_dir, file_extension="p") - download_from_url( + download_from_public_repository(file_name="to_predict_bpemb", saving_dir=cls.weights_dir, file_extension="p") + download_from_public_repository( file_name="to_predict_fasttext", saving_dir=cls.weights_dir, file_extension="p", ) - download_from_url(file_name="decoder_hidden", saving_dir=cls.weights_dir, file_extension="p") + download_from_public_repository(file_name="decoder_hidden", saving_dir=cls.weights_dir, file_extension="p") cls.path = os.path.join(cls.temp_dir_obj.name, ".cache", "deepparse") cls.retrain_file_name_format = "retrained_{}_address_parser" @@ -50,7 +50,7 @@ def models_setup(cls, model_type: str, cache_dir: str) -> None: # We also download the "pre_trained" model model = cls.retrain_file_name_format.format(model_type) - download_from_url(file_name=model, saving_dir=cache_dir, file_extension="ckpt") + download_from_public_repository(file_name=model, saving_dir=cache_dir, file_extension="ckpt") cls.re_trained_output_dim = 3 @classmethod diff --git a/tests/network/integration/test_integration_decoder.py b/tests/network/integration/test_integration_decoder.py index 1dc47181..4881c6ad 100644 --- a/tests/network/integration/test_integration_decoder.py +++ b/tests/network/integration/test_integration_decoder.py @@ -8,7 +8,7 @@ import torch -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.network import Decoder @@ -18,7 +18,7 @@ def setUpClass(cls): cls.temp_dir_obj = TemporaryDirectory() cls.weights_dir = os.path.join(cls.temp_dir_obj.name, "./weights") - download_from_url(file_name="decoder_hidden", saving_dir=cls.weights_dir, file_extension="p") + download_from_public_repository(file_name="decoder_hidden", saving_dir=cls.weights_dir, file_extension="p") cls.a_torch_device = torch.device("cuda:0") cls.a_cpu_device = torch.device("cpu") diff --git a/tests/network/integration/test_integration_encoder.py b/tests/network/integration/test_integration_encoder.py index 712da10f..bf2b67f6 100644 --- a/tests/network/integration/test_integration_encoder.py +++ b/tests/network/integration/test_integration_encoder.py @@ -8,7 +8,7 @@ import torch -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.network import Encoder @@ -25,7 +25,7 @@ def setUpClass(cls): cls.temp_dir_obj = TemporaryDirectory() cls.weights_dir = os.path.join(cls.temp_dir_obj.name, "weights") - download_from_url( + download_from_public_repository( file_name="to_predict_fasttext", saving_dir=cls.weights_dir, file_extension="p", diff --git a/tests/parser/base.py b/tests/parser/base.py index b78d7c21..00e70275 100644 --- a/tests/parser/base.py +++ b/tests/parser/base.py @@ -8,7 +8,7 @@ import torch from torch import tensor -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.parser import formatted_parsed_address from tests.base_capture_output import CaptureOutputTestCase @@ -199,13 +199,13 @@ def download_pre_trained_weights(self): self.model_weights_temp_dir = TemporaryDirectory() self.fake_cache_path = os.path.join(self.model_weights_temp_dir.name, "fake_cache") - download_from_url("retrained_fasttext_address_parser", self.fake_cache_path, "ckpt") + download_from_public_repository("retrained_fasttext_address_parser", self.fake_cache_path, "ckpt") self.path_to_retrain_fasttext = os.path.join(self.fake_cache_path, "retrained_fasttext_address_parser.ckpt") - download_from_url("retrained_bpemb_address_parser", self.fake_cache_path, "ckpt") + download_from_public_repository("retrained_bpemb_address_parser", self.fake_cache_path, "ckpt") self.path_to_retrain_bpemb = os.path.join(self.fake_cache_path, "retrained_bpemb_address_parser.ckpt") - download_from_url("retrained_named_address_parser", self.fake_cache_path, "ckpt") + download_from_public_repository("retrained_named_address_parser", self.fake_cache_path, "ckpt") self.path_to_named_model = os.path.join(self.fake_cache_path, "retrained_named_address_parser.ckpt") @classmethod diff --git a/tests/parser/integration/base_predict.py b/tests/parser/integration/base_predict.py index 1907b77e..8f703c9c 100644 --- a/tests/parser/integration/base_predict.py +++ b/tests/parser/integration/base_predict.py @@ -9,7 +9,7 @@ import torch -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer, DatasetContainer from deepparse.parser import AddressParser, FormattedParsedAddress @@ -43,7 +43,7 @@ def setUpClass(cls): os.makedirs(cls.a_data_saving_dir, exist_ok=True) file_extension = "p" training_dataset_name = "sample_incomplete_data" - download_from_url(training_dataset_name, cls.a_data_saving_dir, file_extension=file_extension) + download_from_public_repository(training_dataset_name, cls.a_data_saving_dir, file_extension=file_extension) cls.training_container = PickleDatasetContainer( os.path.join(cls.a_data_saving_dir, training_dataset_name + "." + file_extension) diff --git a/tests/parser/integration/base_retrain.py b/tests/parser/integration/base_retrain.py index ba627d7d..229b1dde 100644 --- a/tests/parser/integration/base_retrain.py +++ b/tests/parser/integration/base_retrain.py @@ -8,7 +8,7 @@ import torch -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer, DatasetContainer from deepparse.parser import CACHE_PATH, AddressParser @@ -22,8 +22,8 @@ def setUpClass(cls): file_extension = "p" training_dataset_name = "sample_incomplete_data" test_dataset_name = "test_sample_data" - download_from_url(training_dataset_name, cls.a_data_saving_dir, file_extension=file_extension) - download_from_url(test_dataset_name, cls.a_data_saving_dir, file_extension=file_extension) + download_from_public_repository(training_dataset_name, cls.a_data_saving_dir, file_extension=file_extension) + download_from_public_repository(test_dataset_name, cls.a_data_saving_dir, file_extension=file_extension) cls.a_train_pickle_dataset_path = os.path.join( cls.a_data_saving_dir, training_dataset_name + "." + file_extension @@ -32,7 +32,7 @@ def setUpClass(cls): cls.a_test_pickle_dataset_path = os.path.join(cls.a_data_saving_dir, test_dataset_name + "." + file_extension) file_extension = "csv" - download_from_url(training_dataset_name, cls.a_data_saving_dir, file_extension=file_extension) + download_from_public_repository(training_dataset_name, cls.a_data_saving_dir, file_extension=file_extension) cls.a_train_csv_dataset_path = os.path.join(cls.a_data_saving_dir, training_dataset_name + "." + file_extension) diff --git a/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py b/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py index bfb87688..3d37c98f 100644 --- a/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py +++ b/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py @@ -8,7 +8,7 @@ import torch -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer, DatasetContainer from deepparse.parser import ( AddressParser, @@ -28,7 +28,7 @@ def setUpClass(cls): os.makedirs(cls.a_data_saving_dir, exist_ok=True) file_extension = "p" training_dataset_name = "test_sample_data_new_prediction_tags" - download_from_url(training_dataset_name, cls.a_data_saving_dir, file_extension=file_extension) + download_from_public_repository(training_dataset_name, cls.a_data_saving_dir, file_extension=file_extension) cls.training_container = PickleDatasetContainer( os.path.join(cls.a_data_saving_dir, training_dataset_name + "." + file_extension) diff --git a/tests/parser/integration/test_integration_address_parser_retrain_new_address_components.py b/tests/parser/integration/test_integration_address_parser_retrain_new_address_components.py index 60ca6e7e..393e2fa7 100644 --- a/tests/parser/integration/test_integration_address_parser_retrain_new_address_components.py +++ b/tests/parser/integration/test_integration_address_parser_retrain_new_address_components.py @@ -9,7 +9,7 @@ from poutyne import Callback -from deepparse import download_from_url +from deepparse import download_from_public_repository from deepparse.dataset_container import PickleDatasetContainer from deepparse.parser import AddressParser from tests.parser.integration.base_retrain import AddressParserRetrainTestCase @@ -26,7 +26,7 @@ def setUpClass(cls): file_extension = "p" training_dataset_name = "test_sample_data_new_prediction_tags" - download_from_url(training_dataset_name, cls.a_data_saving_dir, file_extension=file_extension) + download_from_public_repository(training_dataset_name, cls.a_data_saving_dir, file_extension=file_extension) cls.new_prediction_data_container = PickleDatasetContainer( os.path.join(cls.a_data_saving_dir, training_dataset_name + "." + file_extension) diff --git a/tests/test_fasttext_tools.py b/tests/test_fasttext_tools.py index 1b9af11e..469f3803 100644 --- a/tests/test_fasttext_tools.py +++ b/tests/test_fasttext_tools.py @@ -12,7 +12,7 @@ from deepparse import ( download_fasttext_embeddings, download_fasttext_magnitude_embeddings, - download_from_url, + download_from_public_repository, load_fasttext_embeddings, ) from deepparse.fasttext_tools import _print_progress @@ -84,7 +84,7 @@ def test_givenAFasttextLightEmbeddingsNotLocal_whenDownloadFasttextLightEmbeddin with gzip.open(self.a_fasttext_light_gz_file_name_path, "wb") as f: f.write(self.a_fasttext_light_name_path.encode("utf-8")) - with patch("deepparse.fasttext_tools.download_from_url") as _: + with patch("deepparse.fasttext_tools.download_from_public_repository") as _: actual = download_fasttext_magnitude_embeddings(self.a_directory_path) expected = self.a_fasttext_light_name_path self.assertEqual(expected, actual) @@ -103,7 +103,7 @@ def test_givenAFasttextLightEmbeddingsNotLocal_whenDownloadFasttextEmbeddingsNoV with gzip.open(self.a_fasttext_light_gz_file_name_path, "wb") as f: f.write(self.a_fasttext_light_name_path.encode("utf-8")) - with patch("deepparse.fasttext_tools.download_from_url"): + with patch("deepparse.fasttext_tools.download_from_public_repository"): download_fasttext_magnitude_embeddings(self.a_directory_path, verbose=False) expected = "" @@ -123,7 +123,7 @@ def test_givenAFasttextLightEmbeddingsNotLocal_whenDownloadFasttextEmbeddingsVer with gzip.open(self.a_fasttext_light_gz_file_name_path, "wb") as f: f.write(self.a_fasttext_light_name_path.encode("utf-8")) - with patch("deepparse.fasttext_tools.download_from_url"): + with patch("deepparse.fasttext_tools.download_from_public_repository"): download_fasttext_magnitude_embeddings(self.a_directory_path, verbose=True) expected = ( @@ -225,7 +225,7 @@ def test_givenADownloadFasttext_whenPrintProgressSetToVerbose_thenPrint( self.assertIn(expected, actual) def test_givenAFasttextEmbeddingsToLoad_whenLoad_thenLoadProperly(self): - download_from_url("fake_embeddings_cc.fr.300", self.a_directory_path, "bin") + download_from_public_repository("fake_embeddings_cc.fr.300", self.a_directory_path, "bin") embeddings_path = self.a_fake_embeddings_path embeddings = load_fasttext_embeddings(embeddings_path) diff --git a/tests/test_tools.py b/tests/test_tools.py index 88d38484..9dbc0b22 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -11,7 +11,7 @@ from urllib3.exceptions import MaxRetryError from deepparse import ( - download_from_url, + download_from_public_repository, latest_version, download_weights, handle_poutyne_version, @@ -72,8 +72,8 @@ def test_givenAHTTPError_whenLatestVersionCall_thenReturnTrue( an_http_error_msg = "An http error message" response_mock = MagicMock() response_mock.status_code = 400 - with patch("deepparse.tools.download_from_url") as download_from_url_mock: - download_from_url_mock.side_effect = HTTPError(an_http_error_msg, response=response_mock) + with patch("deepparse.tools.download_from_public_repository") as download_from_public_repository_mock: + download_from_public_repository_mock.side_effect = HTTPError(an_http_error_msg, response=response_mock) self.assertTrue(latest_version("bpemb", self.fake_cache_path, verbose=False)) @@ -83,8 +83,8 @@ def test_givenANotHandledHTTPError_whenLatestVersionCall_thenRaiseError(self): an_http_error_msg = "An http error message" response_mock = MagicMock() response_mock.status_code = 300 - with patch("deepparse.tools.download_from_url") as download_from_url_mock: - download_from_url_mock.side_effect = HTTPError(an_http_error_msg, response=response_mock) + with patch("deepparse.tools.download_from_public_repository") as download_from_public_repository_mock: + download_from_public_repository_mock.side_effect = HTTPError(an_http_error_msg, response=response_mock) with self.assertRaises(HTTPError): latest_version("bpemb", self.fake_cache_path, verbose=False) @@ -97,8 +97,8 @@ def test_givenAHTTPErrorRemoteServer_whenLatestVersionCall_thenPrintWarning( an_http_error_msg = "An http error message" response_mock = MagicMock() response_mock.status_code = 400 - with patch("deepparse.tools.download_from_url") as download_from_url_mock: - download_from_url_mock.side_effect = HTTPError(an_http_error_msg, response=response_mock) + with patch("deepparse.tools.download_from_public_repository") as download_from_public_repository_mock: + download_from_public_repository_mock.side_effect = HTTPError(an_http_error_msg, response=response_mock) with self.assertWarns(UserWarning): latest_version("bpemb", self.fake_cache_path, verbose=True) @@ -108,8 +108,8 @@ def test_givenANoInternetError_whenLatestVersionCall_thenReturnTrue( ): self.create_cache_version("bpemb", self.latest_fasttext_version) - with patch("deepparse.tools.download_from_url") as download_from_url_mock: - download_from_url_mock.side_effect = MaxRetryError(pool=MagicMock(), url=MagicMock()) + with patch("deepparse.tools.download_from_public_repository") as download_from_public_repository_mock: + download_from_public_repository_mock.side_effect = MaxRetryError(pool=MagicMock(), url=MagicMock()) self.assertTrue(latest_version("bpemb", self.fake_cache_path, verbose=False)) @@ -118,8 +118,8 @@ def test_givenANoInternetError_whenLatestVersionCall_thenPrintWarning( ): self.create_cache_version("bpemb", self.latest_fasttext_version) - with patch("deepparse.tools.download_from_url") as download_from_url_mock: - download_from_url_mock.side_effect = MaxRetryError(pool=MagicMock(), url=MagicMock()) + with patch("deepparse.tools.download_from_public_repository") as download_from_public_repository_mock: + download_from_public_repository_mock.side_effect = MaxRetryError(pool=MagicMock(), url=MagicMock()) with self.assertWarns(UserWarning): latest_version("bpemb", self.fake_cache_path, verbose=True) @@ -136,7 +136,7 @@ def test_givenAModelVersion_whenVerifyIfLastVersion_thenCleanTmpRepo(self, os_pa def test_givenFasttextVersion_whenDownloadOk_thenDownloadIt(self): file_name = "fasttext" - download_from_url(file_name, self.fake_cache_path, self.a_file_extension) + download_from_public_repository(file_name, self.fake_cache_path, self.a_file_extension) self.assertTrue(os.path.exists(os.path.join(self.fake_cache_path, f"{file_name}.{self.a_file_extension}"))) @@ -144,12 +144,12 @@ def test_givenFasttextVersion_whenDownload404_thenHTTPError(self): wrong_file_name = "wrong_fasttext" with self.assertRaises(requests.exceptions.HTTPError): - download_from_url(wrong_file_name, self.fake_cache_path, self.a_file_extension) + download_from_public_repository(wrong_file_name, self.fake_cache_path, self.a_file_extension) def test_givenBPEmbVersion_whenDownloadOk_thenDownloadIt(self): file_name = "bpemb" - download_from_url(file_name, self.fake_cache_path, self.a_file_extension) + download_from_public_repository(file_name, self.fake_cache_path, self.a_file_extension) self.assertTrue(os.path.exists(os.path.join(self.fake_cache_path, f"{file_name}.{self.a_file_extension}"))) @@ -157,16 +157,16 @@ def test_givenBPEmbVersion_whenDownload404_thenHTTPError(self): wrong_file_name = "wrong_bpemb" with self.assertRaises(requests.exceptions.HTTPError): - download_from_url(wrong_file_name, self.fake_cache_path, self.a_file_extension) + download_from_public_repository(wrong_file_name, self.fake_cache_path, self.a_file_extension) def test_givenModelWeightsToDownload_whenDownloadOk_thenWeightsAreDownloaded(self): - with patch("deepparse.tools.download_from_url") as downloader: + with patch("deepparse.tools.download_from_public_repository") as downloader: download_weights(model="fasttext", saving_dir="./", verbose=self.verbose) downloader.assert_any_call("fasttext", "./", "ckpt") downloader.assert_any_call("fasttext", "./", "version") - with patch("deepparse.tools.download_from_url") as downloader: + with patch("deepparse.tools.download_from_public_repository") as downloader: download_weights(model="bpemb", saving_dir="./", verbose=self.verbose) downloader.assert_any_call("bpemb", "./", "ckpt") @@ -176,7 +176,7 @@ def test_givenModelFasttextWeightsToDownloadVerbose_whenDownloadOk_thenVerbose( self, ): self._capture_output() - with patch("deepparse.tools.download_from_url"): + with patch("deepparse.tools.download_from_public_repository"): download_weights(model="fasttext", saving_dir="./", verbose=True) actual = self.test_out.getvalue().strip() @@ -186,7 +186,7 @@ def test_givenModelFasttextWeightsToDownloadVerbose_whenDownloadOk_thenVerbose( def test_givenModelBPEmbWeightsToDownloadVerbose_whenDownloadOk_thenVerbose(self): self._capture_output() - with patch("deepparse.tools.download_from_url"): + with patch("deepparse.tools.download_from_public_repository"): download_weights(model="bpemb", saving_dir="./", verbose=True) actual = self.test_out.getvalue().strip() From df9651a8ec12b3cb62ec4f7001bbd09f7936b550 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 8 Jul 2022 15:08:49 -0400 Subject: [PATCH 013/195] cleanup dead file --- examples/data/predict.p | Bin 1467560 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/data/predict.p diff --git a/examples/data/predict.p b/examples/data/predict.p deleted file mode 100644 index b8cf078af64cf7c23a93942a5c113927e8f0e6b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1467560 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

mnrzvf zC?iD{xhGZ?lT{f+^k!bPHT(n8CvoSGim}fInUKDLqK|aVZLF5<`MX7G{8!H8VM|jq z@n*i3g=+9U%rC5T1VZaSwhm!byXl7n)x*Neq+zQLq&B9hs#IUIZr2?jOGu9YXleBI zOUR`M(t%&dYmd(f^RCp;b4zuQTBwwyDyCo{g?Y5l8|7*Mh><7QSA<$f+qTO*NgKco zCI5@m8Y$p&QlbEDVKh2Uhh;?0UhtnLC=#%+V=IZ^3{nRw0Mv>#?07gS!MtxXyXpjB z5|QQk3LeU^*bHrKO9$K%w)8F|W;8)YhYN>YB9u_!)t6v0Ch*hOOXYQli5UD(B{VyL zsIx@YeK1UARqnV#1f@>Rz zic&2*aA5HJtTUASZZ~{*5Gk|Tn!^-#Wb%ZU$Ryzk$v2SiP~VFj!Len6W+&y$J9^vR zp46W%iKAFjKbVIU(2+3lnnwn*^1OdG z{%q65=|3hS-O?J{J?_Jf+98B=Fe-mRSE$V@cad{bm$V5dU367J87{Gux_0e{{=O!1g#XG# zY9}SHm;--@b~sLzje&}>o|;u&MSOmey9|*gVz$%s)BFpx_3`^a>Za+WG8>ES6p#;Y zI7H_+tRlibm>tPQuq%pcxWK{!J`ks+H30N93Pt}dHNrB>ZLA^fb5Yi**l4sNqP9Ln6QB;*xNCZb#**JH_2&1R}&U zCwL3HL1eCtE&!>W#A-YFsdJ-w8AvmSUO0^$VrsqJ@4FH{(Yvt*71~+Q1H<|htyWkV#90ESdMQSWb%yOyQ zqKrrjgK{|YpPZ^=|5?1GqVb$%irtn^%p_4SQGI`PLph|kHmu6w5OHi_YtjWYg}{(4 z^G`=d0GEt9-HC}~-m9Mzz!MskY+3XZOuFS2IvMlR*a@qOj1KktuurZy}y+b zzUQ^`86y&8SNjUx>BbM~KK{zZ%DK+?GuIzxgv+w?wYKQLtVQn1^^6j@IpTy+Y>#6<#WPIgbs&dM*=yO$h&|*D#it=cheLK8~!*t03 zU|OfasI;p3Siad?rZVf9h#arT5in5VN&9!a0$ti=_`6eToUX~14dZClOz^BHF!6`d zicdWvR-18Y7u=Ju1zu&A4Wh0)n;04FQTC3X3T1e(YI(}?@lf98J`%3@eS)Sr8Rpp@vGEvp{om(%jfB2mTL;DFD zhBFn(H4ilK0|rzigh$WP$}Cf&qb=`_SPr_>NH&%eWP2(Yzuu*0NzVnb!8-%P%!BtF z395lh337@&xk5ExdNH-846rD@#)Fgc2-E*3sfNU!t65(*_{-4XeRkS&q7;tM(wcsX zn=6AHDy45sHt}*m?d$p1XX=3Oy7o2ZlF9X|vDkC-Jifn6l|Nmen5aUbe3J2Pyk#2$ zUVFy3^-=%jpE@$Eo{Tw1ilk2hl5tNDlWD^yxXI$RHfmaSeTQlrRnTmOQf-yc< zwmmPuG5y4WW+r}2jMoQSya5}`8}#AF3ZeqrBdWw2YNiU8KWaWFnOKtzSfYTTFICtY)|@I;58e`hFOwS~ zf)M4~&TJj2rTswc%sZp}j2D`{Z8gaV&(e|jldgrea{yr0v>m~SWx_+8nwHlSoS)Dv z>vW1?xD+FG_{LR}RSunpabFV!pb=HoeY?>*bv5e*D6QyD=Ri^WuF~qDyj2SX7bdK5 zP8m!xwL^N{G(?b1t(};4qK-Ib9}+9WnsEL);D9Ob%aM_+i8jBjq$wl-qVzPpmvC)# zF2AXz-o(Lp5z2#h*DU0HtBOY6;i!%M`0)+ zs_@G?BkO;aFVhv9emzKXN5#NnXcB_IA_-DD6palK>2D2IyeDB`8F%O|u-tKW!7fv? zJcuVT?DTp0(`p^f#0neTe#Mm^9+btM2y6|<)j|KEX1&j`YRhI3;tsaqlA`QysXfr7 z7LMR&*_f;P9l{)g=Yh~gA<5>+YZ4$$_X$C#j6DxrMNoutTY^)k^tKKMc<4yKuZL_@D4lDo` z)hwegMP4N<-r*~vYt(XvFCaIbJzJXBVVFF464}6ea1<$QZ>=dpO1wrX!^FAbX%rM1 zcU1jjirLP<7(eKPL!6@W`nHP1f@(o zimE_QzJ;C{3x$fSTAGHWGf?m4TG_^i;MF?&Zd4XeDxCT7$ zTN5`F^Hgg&e07Dt#=7Su7v2865MH?;SkaZc9X>&Ud&e`W!#on)b7&MSW!wQ zWclC8s{XHKiWv*TNY{aKGb`;zGZo4qO1Gqs2l(6cez5e_YjMFyN#RjwI**8OIn};{ z*%e7&O1ap8rzOfkc7&r(Gq%D=S7qZ`b`#?-DYwDef&U&#wyB&HNr*Zlf)!jWT}IQ{ zC+b*5Ziu;9`Y^@Bk;WDtojFD_gsL@!f3W{z?L{`Q$*zMb9yfoCfyo>dR)w&6kGfO! zOYSivzqhhg8HkufFv@UsLtookpfM|Y4@S6y~qhbiMYT8e*g~GA>w5w{|DY|Z014g<^`Op&=J!aZwX+EjzS8yUxPLsb`6$+G< zgd$(!gijjjeh>=OQ}B(XR3)|v=Wfg^7>axt#jHB&%96?_!HNplA8sdnI|dBu<9#pL zGLc}c=51$uIaj!J2pR26E9tMEBpA!9Th{y9-KKGB>Xz8>71L3wIWivN5BQ6~DVyYJ z72NkuX7%G~Y}Mg|pxTuc6fki=30qBpLOw>rGZL{d4EbC52Z1$(~JJnM@SaZcDh zCr^0Vu6euiSPAL*a9pNI|@MyQj7SX`6(n*AW$Mit@8W}#EYVCMwjYn;j zWZC2%(tE=6i8>h?P@JuyIM;|rK&CQ;qmn!4DAl8vV#@(JXUHYaS&gf~?O3MzOO%OK zqI`y2;Q$Hgas<4|$E?Vy<(ICO=gRk+i`K!Fa8|*`s0G zU-1l3J?FGX`EJsrr!ddTgr*K{uoO!mHhYWBStXj0z*yy!1Elh)I$M#3)6Lo8X^zYR zSL+v7t0UUrd{NSa8-kKmr5THR=OxPBkJwG-01-0g>gi$8KXAOb7V}`7s^d1}NSp0h zK1uwc@xF~ZR^OHO2e>QaR3%|i-G&3fMMo0B5VV5p!C=+b z2s!IB9HT}ZCYi6yiZ9*xC05sok66)!K@C zg$PYmO-Of9Gr6Fcaw{mRt<4_`Umz4=R3dhK$bD11*cChUeWL0IBo+qxH3ZS?igW&C%lv>baiC_>%5+fJcR5mHTCnI!QWPl-2(9Dcm?CL; zkRqY;HdA{E&AxciY7VI{Sd*;k25SVCY)gbE5_g+D0JQ z(@~^KryUzW%%Bt47-19HARW>%Eiu&{Xw14cnKCETH;BPh9{yw)!co0+yG=9Zjlzb( z27(1@jOREhO{Rlkj1rT#4>0X|r?V3oa&+$2{7?mhzOqzIX(i2QgY%|N`8>LgK z47*3@10s2Oc4e6w#|IpY!PjNMJRYvnGNr)tgz0c#kz)?Bw}tmH6HY%!i?Y&oaKn+} zjNR}>A}sY9B!wOPw9$TG7y2T$?H2F1p}Pcba&X;9f`=LdA1R=Z`GmKk^njNevUVPhY_Tc+3K2$B6_BrVKoNiQWeVg?2E+Y zE=he9g?~cAVy0_kLclmLZ2Nff@?;uD0>K2zUO|{~4_-!*+<|F`Ru&dE>Er?k3x4bKZ&w!b zTZOGH%%AeoPH5#o+j6v9?MD$4L{9$3GIfAJ-HxdS(r`rfnMO_AsRP?}i&W7@D7IZC zYRbxpzVV2YH4G%(0FFsC%o=q>z__Dhj~Ja8mK|@DJ|VslDDehP?=tWP?nr-U+6s)k zR~eDv<2tbYri^;jQ3IHu$nlT~5EQJ-C5r@ei>?}Jzja|jPSmJ`&3qjaDj#3i;#WsF zv1XDI96mv+H_0v9^<*?(zfg%izTyizi~y43XH0yH?Ncgrin~H7jfdCwdh(1#qR#O@ zRmSu+GwNW)&ehP_9k8A^i>{i^$kqtm1C^iYrCC=n%tz}^WNla0L=;uv<%=Uv+jk1Q zx5VQk$Zv21bPl3gCXcM)g`^$EPa_#>L!~e=$GcMyX)0gi=$9>d-V!h4IuvY(+5LC> z7EuCVad84JAPAwLzmhj@?Ui+>(l*ZXj-D0ZVi;==-CdXfY)XP+v47i<+IrJ#VUy$v z;0(0ME}utQO3(RP^~5q|tc)r`e{ZbJ9|%a2S-dM>@|;I`q#lECX1-lGI=5cZZ$4Lv z%ErjStC_YM*3*y|F(-g4%A^qt1>RyjS>B?>@ZgttS({70$on)-K)+NQ1jJh(vYowk z6e^&=gU|YCezC@gn9j8>xQ~BY2@} zYHE}HZ?#N|`W=}?uOo*(0~8gJ#Xr($C(pkcfrY&>{(dBs zrw=TN=0Jh1QNz#b6iU5<>gt5CZt9|CAtd#Gl?E z#cwsi3ZD&;yl1Hz9_aT-Tq8->%D!cyy!_yX*t56T%B0g8}BaM|0PfJydC zk+nMisP_DS(%y7Ck!;J-&3c3TyYgnGhpTsWSPOV~oU9u6L23ZPL81`}NVxnN>Sxva z-fymLh5)m@&EnJd_!)$hG(sQy^f~{M#dL&=<*>h>i(#%pc1c97C%r zRY|3ZhO?b`TO9UwTS(Q8z~MS(RrSo~Q1$5=1LNjEU*PH5Ogy{Aw2j`dZ4M9cf(XM% zdKZtx!^Zt6MIHQEd^9x8!7hCOC4|VSZ&z)#+~(0V2#~u~2-8&G@2WHXQPH%U0fcpc zMaxV16fZ{>J|vh3#ENlq2Xue`5oHAEJKF%W8KUl*9vH^S*Sb;oymGgaWv9>8`bD){ z71uG2q_zt>1T-_FXXg!;N6qP-*qq8A!qYtpKtT+9X)U+kaJa|qf3>9z*Zy}0SU!1M zi7rh1C^Z`uEr)~U?3nnH;`~^7Y%S6(kp8|rZw9;rww%(CisU#4wRSuZ>eLWQHi^i4 zeR0H@S$J{zX=?9SnW$#n< z;V7=3%iRD&%Qw|1`7mMI%Fszn4Q;#t%>FxfdmKq#luj5U`Ms{DAMH*1kjCBr{lETD z&Lk~V-llrUIO=SXiQ__TQxQSMp$5KMyukt@k-)~ZY1LL+{0$g^;U4$nhx?}ibm_XY z{2q%+ao$B>%AFD(xz373Le=vGL}l~5SUDfw3IH=++`pn?%&pc$S9b2y2!G2d(kSlR zxLC?#NpTu1T*{!ZVxG>+_=yU*U3rTn%6~tK8fjMA;m6<}BRpVQD2Jnv0>D;KZZAoyJ92624csvPCgjTpiJ79D%JaV5fN!dN?eA#IuV3cGZR7d# zZ?p$pkXiY|o%KFk6UWHmVju`)#$4K}-AX=UwVvPQVs>E=gqBnM5L8pnNusifLnAaK)-ikoPOGfUY_)Yvm z$w1|+<#r;p%V5r6FgwcxMRHQ4UbG~5DMIIPaf66Qt=pFvPau2~Ib>&Q6h(moG~r9L z?Ok_)Zr4*8R(Y)}-37c0o#ea<-O*IBMOBQ=9vbGrBWq@zgahNUP9lWe>%L19dQHZ$$1 z_%eqS{=$91oIfKL?5{CyGtE=IMnd8X&jQnbGIh#c)dM!M3IIcFNMbBmE|gScP=W`B z?1#6A)zlvKhdPsJHnO6y(k<7QoBrRFkYU`7I4j91tMeZQEj@lCFifRsi7OV?-3^H_^@&r7xz z*=C)XamXz$q+IF@yG>=wRqp_CV?B$2r9 zO;k>;q?cZ+UZj@iAa2N>?k5SH+Lr8>P4E2y6In?ocSP{1wxr#|jnOHPTM~NmtEhd{ zjDE=8vkBJx3QcvVd?e6vsPOglf%27U`xqAt;fj_#wt1}HqyLWJiUeP_wEmVsE8eBu zn{2EvO%_nmuS%Ot>1e1(En-pN$pf(BPA|F$G7Z-Ymf)G%#77-H8&E`qDgzAclm5$D zB7&-cYi)p38VmBMP1`IHX_UM`Vl|4k&wq?^sXU;EM@NGxJ-M0gh4u75SZ|tHTTG%J z9Aa*YMOMrRTFr5+OzIk&Y-~)f1ZB|L5eP+rJa?oxB>DGQjM}_H){GJhM?-U0DRAIy z$?k~8$b;yGhTbk6Xx9EXoONvi7mPZVAGg4!%pQ(%CYX!3G=Ns*lBP0dPWRvP46c*+ za`xttB+9RpY#m0PHEG!}Q6~zTT7*kmvSs>XrC=QR?v43An;*srS&iwtDR$3mzZaPr zYkuU)#z+wV{!B*QJ`&>khVcXkGA8>gJhl$__E6q9L)Vz1uW?9d z|H>4xa@rMHwCrDR6O$&2oKdn>YnQUf$|x=C8ndB=%x8gsPFoaW#5L(my66b4_|9LU zY;m<56Gke~YiCkN*@R48f-zPjaM4>+W&FjuQ_n%0s1^#N^8^x^Oh<>O7tO+9a}Cyd zV`UJc&qdx>(S&<3k=U=Xs9Mh^I$RA-7+$A4bWZaTQ+cs#^>Q{$5?*o?QD4SA zK2Z%4kVleSNg~Jjf@^Y(hUS;~+mnUBzS_h)1{IHKCcS7v zYr?wq=R~{+yv%REcZpbi%qJtslDxR!vckwVKVnyC&>3U+Vj8pP?yg7gO~#boB~5%d zt|^44`u3-}>swJQ#41cE`*L(^@DEgoB<0iNNj~jMzpXxo;yKz!wd)nK2!Ki@u0!gP zbryF`snr4ds%HOZx!}8N09iz=Ovd|l=c*T#ubr5xN?*nQy6F=k&(oVGzj+C=e&p$% ze>&_3Uyka8j6yl;sY=OB=CLv>>qD|cKg#XHc0~QBrav&WtAne96o)+1H$zgpTIUcE8}EG1E?IWkUautPv4@rS*|4#9*&>vzp)BB5obd>5H{ z4oxuEx7h^uDopJ4hzThV{_V)jGk;pV!yEc{+m589jev0PZArGMu6}&rj>;PJ#@g5z zvB?!b@RiL66Mx${Vhu+9P@^h+$;2J~nj_R7>~Z8Y{OH+)vgs>UtnFyW;$IFGagvdY9^B5$sT)4x3JvtWPxiTXEmOxK;EG|~-w0ngdK~*O8 zC;Q8eMQ+p~zO%a8m$59Elcp{-K0t;ssb;)_u%+g6e@*&5imb2D;c7xy zA`cM?Z@OOqH#Ddnu;2xMGV; zJurVz`G&YRnAfQ-p}DR;0_9E!g0KT6yyEu({b8*2g{mhyAd_eJoqk>L5C7+Sw0!Y7 z#6JPQKsqP^6w#UAzGRXmf<#X2^8PGoT*#O;kv1X;yl5%}mEhhQ8*QDMQq@FdAx45Uj4|IcKT0hy1e@RvtE0K7Tmy z6CzG9C|;$aTc1fNoK_Aob?PnxP#w++995Asl8XDCu}cxy(}gYJHITjrU|L@21+lsk z|NUyJUUM=CJxVpniRG-Imq1ISt$8}w$;!iIhRI@teJaeS;ihm*K6J}EAXB2gdzHKV zhsuV*M&ND)kr??hc?N_ENW`jbU(L0+9z)U_5r#=DdL^pi09IT+ChN)zF+-dl{ZP$E z#M}qalfe^p>*9j=H=sYtRkUD;u~ZVbdz&Bl#5C5sFq)MGIBG)-;vT)R9g!=kHqbim z7Omb?E@Oqqmm0!xIC!DDZ_InZrXt=Th-#@&t+-siTF@e1aR*Rn)sh0DxS5o7WJnY^SkYo1pHh*>vah0?4JC^A{lg)bDi}CAij4Pw*S;lULUC^YlPkxW$t1#uq>>y)l z%v>?$f&{|aN`&zHoZv1|zukF{c1EJOC1$QRdqft1KZ9|IwWnenz;gFqiLGH3J5vKtkdEZa_YUT zYwJKF<5o_;ex;qSxDt&S{WMdL1tysaI+ZE16MM997}eFtq6cYs%?BT@LpMxnNY(v9 zOOGNGjhi{7Sg7GUuz(fMdriAq`2Waz;ukV8kTXqJ1W|&5 zwpPWpfnBZtm!|LW)6PIp-uL(&PJELX0&9Gz*jfz@ygsq2&BBkFSXPw`A5 zoiMCg&h^8cltHTtkV)nugE(iy2Q0;HQw!fF(n`33S0s<}jMsg7wveS_pnj!7rkE6E zkc*1AUeqrZDe#WeMD%Kn-*kWK1yhxP)k}xx!J}J9hu_5TXpIMQ|1ucCPp$>4r^!$r zjV4NfwY46~jFR64u5)8}O5kOTg$MX)3KJc#P~3WIz)}n?KM^>kyGRhJX5$`=p!EMe z11P1S8^hr+tG#3prbRG8dou1QbNS)Ge0sMS&xR5DPlFBGSl3(u^Lf@G=EJz)BcE#w zMDh`Bc{FYVV8(jJ88@?!9|Dv-{ zx)3?1c>DH?LS(r^F6;8IJK8up{y%hlKDk)sfw1T_SkqgWq0=;AnLyjjg({*O=<69h^OC5ykXbnz zLWKx{hO;W#sj*;-zEW9Ir9&s-cJk=w2bs29qe%#TN3%kVfOkagV)G>&0>VMzaqhbW zE^~TvxB&7R%bgDb8+6r>^GJ3D6@4_dyKI-=%kCa^jPNc#0woK@%1y@+<}L^8&j?C| z=@673aPa|9N~oCETU%HBAMkJo3rAwo1v_e*W(tYkf)ix$xI=~7nD{ZD)JpgG_M(z* z`(b2)JPKxQ=nL{z=gffMmKE}}JlF|*Nb>%UeZjV-I!ow4v0(T@fJrYXDSj}UZ*9JW_EkYF^sCf%rFp)9*OK9Ww z@-B>hUBmLcS;^sNDBfE{is0|NpdZg-aO)z6knoZ9+;%9TavCCwZxOZBm+|| zmewuDNOs#oCAYbOe0bdvMDy>2N!w{j89l~chLPfngjtd7zNTg<*h6WWdvNscM`w+m zeO>B1rOnQ47-xsiaIWjDt8t1LIilhh=Yz&IaaLshPVGhh693kvghfP3UgTg<3c*fA zkTd4-(X*(#4>vD~TMffDE7^oDP*0R(%`J#q-=e*hQgR-@J|mx7u#=%Ig4rj8{4`?W zOFXuAg0udY!3z?oytR0B$AP%m%|`8mL#lR2KuSusjkgy4I=R>j%){VqLTSE;RV%AG zj^)?Rpeq>GDA^K=HI5I#z*5);dgU;+OATY@VMCV-y-!m8%8njlQ{G?k($nB9k$A$R)B(pkMU32du{y`CP1rC{$81t$NV( zJyro6d;#DED!cadv%N5cnlSWBgGLzcs?wL6c35i;rjbf~e-WP3eDI7EB7{t<&9L%q zn5KjRxZg)9s!ir5+1LVu*>9=WV)i=N zey9Y6N&qV<(L&fZq$+#Q7`RcJNV&ud0r5^O$ zec43Dt5rguXi_aLr(}~v#d`{(|xFGu4wcc4Fa66LPD*^h*&sI2(>GQ+w%0u>`a2GElmZwsxaOEQp1uz)FP9 zm})9x5Ow=65MhAGGFONg5dDy3?L(i6A9eF3G^qQy!qd2w^ZbLYM2iw%#NjMv0@s|E zd920Jq=}0C_rF1JLiivCl1NJqLN$+3yQ(vr4}e0NX+^o#2_im^|5_ySqKtmyn_(H7 zR=ms-*Q2sJQ;SaFJa^r74V)XaY_Kigq~F1eXVgR81b})Ux-gPMN4C2ZD6t$cupo2O zS-cVlK!#Lm4*;hiZKn^Ny(hvP?xfz%h#afjVxsx`l4gGtk)RGJVi~eFQYs?g!PT)8 zfeT_&G_PnS1r()aZ8Lr$VXSka%um7|osn$gmaRHgx5l$b^gJ;=hfGh}ci@%(eDtu% zZzD8a>oPlo=*(jcmR-!mMfz5#J&_!S5KB{8t&d0`_IGcCaE}s$*G{|sU`W6Gi4>;` z*rX|i8yLyx9#BRBBR5pz3U~*fVc$g(NMPr8gl^8fm?H=rmtkkY{rQFqMRWkaQBX#9 zUlb#qKpFXqjo$hToY2$04b&JgH_SMZOSnP$#0m_fsWnQO7q0vk^-s1NYy$V88ASHt zp^$uH9OD#sX@{69*W$pVdADYntGDu2Lrut&$wEgu*4|Qv^%MUAiIp7G@|5Z-tt68C zzkXyn@zY|&bJ!lV_Ff~a!eD3#WGH}^1A4+On2;(|+Pr`oK+%JzC!xRwTDowA-wjQp z>`JFqhH~G0SAC6_L44Oi7L9Z)lM$ z*<>m{RBq72L@|{ip0B9zG!Ei3zUW!ZY+i69>0H+tJ4ARDMs~@v)FnV@=jy6$P=8Y` zKBp^7`gp{4W4+bs{mMQ}Qw?T}?u*6vZYY@fmvHH^Kd>nwmodB`LO@p;PwQ}xc`BcQ zRv&Z>zSyKvo6?VAN5eTH+!7H9+U+b)BZfDPl}u?auv4uvyTS*>G4g?)LKH`2JHMbaRgwnh8&Zyw4PuNRUwwhgq@eT; zZd^~m$T64LZ|*&ZQ7eRlE%LxZBtm8QlP^z3)~I)ljM%B$JH03PJo$&O5l+t?64tc> zVU2BcAPxHT7`t%=m;$X}-~gKB$AhZ(w15B^H{1(smDID)d^`q7n$OD%}TR7Ot5Il|S4SaSRYcTx{>RFNkbZzbHiL{Ibk)A}{Mxf6DO}>nY4(;@} z>bqEURDaR9@_mic*?ZY$f49yg-SS=!s2jUVzvANH77la0%tMAvb9>lu;lRO0-25|u zX$%#zZbgEBT;9u928UiMPHoKTF=$NUf*EB*Xmv~}rh58t{+N3i%opTNzu!81y^%s6~-jx=pMFN zDBuii+RA)d)}>lx2!L#maWi?nP;5VpmE{vEiq=V!?@rz=QN|YZPKm^SZs4*NgC*6; z>#2XwvK^PJLl!_Pd=LrwUj~CLeVh(G54abh>qWrr{|IfPADnf{{Jia}Us9!Y7KibOEnveiL@Y=tSYb1IUb`zQFMuTZo8oL3mr~Q zZc$hM{3mK+!|~|LOFE4t_xJDWPZ{iVMXRo}tH|h|9BZ2zW(@0CB|GzO{0{lGe+!CT z>!;ud2R$dn)+sIWf3}Ak<85*w@$8*7+T_?*B8SCOXKK5|qbsXF7>uq%^HDg?Cu zboac3nzeJ|Vf#>V*)tdzt}I@&4=<+H+3WL|6(=B0Tj1Go0-VGff_-(k4M7NWOzQEf zxwTqUW_IltAd0&N&T#kkZ0=oj&VX zs@Eb_*@x#KpGR4{44J|xKyPPVCQxJ{{IsO+_NSTX)*X7Y5&ao33ZmJJA(+YrM1=BW zm^f7Qo1NZd(z_2Kg`co8zdP1-}-R0WBPoag8CxS1au(8_nCK4!;>HA_U4I^(kc)DU-9ajX3P z#Pr|b^SN-o`M3MiSpC{}C?c8IO(W2oNv-W0vGWieA<-W~nPX(dA#qPeQccoD66HL2 zk22*W%u+_wMpxFP8QYlTT;)t8O^Db;Sus99VITtLc5BnyDIsH`k4wlYUle>&O32)_ zA3_IC%Z85ZnT$f-%1t`$;N7M9NtU9eZ>Y*eDxxwcg*p$J%f>}yH5IBw&#XLldw_1Y zn@OTXy51L(7!cSzqPeqC1E&NI!`2&4i~Is?JVd2j##J2hz)HIrh6#bO)Nf>oi`@w( zPm5Tf#wFxsWZ@{v!N9E#Lq9Cbx)s5J5us(??nlRU_kj(7LG-m&NvT`NT;SV{y0lh6 z)gZQjKU_~@*VDU4j;x!M#b{Sj{{UzGY*Gq_T<@mEPZI%ttsG(7diIQ<>8=QYHVYAO zrd(8YY6N1XaUuy+j;;ly+C6MqlWPRHkYMJ6P!uZ9%g?&7ug=4mIkpdtgGwBlwTVN4 zB{W`Hfl8g@NGAqOVZuT6kc(GINRE`BtL)A%v$f5{h#vTgc4aNVWWcUMV7$QWbd*goZU0J+a7k~PuwdXrQk%LC*Xgh}>na{mJ1 ze^fm?SNO>BW81_D*(Yjch>$YSiB)-oQ(0?VQoE-DPr!NA3+vMP4xZj6;U5z~pi$t3T!C&^yg=&p1WAYa-5ahxxK?rb{Boq$N;5k+L;1+(F2 zuyA)=N0jH(BM{DJ-XlS2AfmXzG3mLT;K6$}_?~8BA3ch7JJ#g)77{0cKPBs7WG7fk``|q5k7%W^H5IQlwCPUQ*u=yxc|5v|XuXn;{`KYtov zU+1-~D;p&P$t_Nh5g;p5C8{R;ohK7K&ly2GtOHODLZ+%;Ll9ra?ixY|ZE{oE@&dt4 zB6RB-?jMDu!4xhcFi!n?Oj9*^B5s1%;X)F1vV&A#Yi?&(h9(vQd6HTL&K-#bA4(8= zF?GN>b6QIC68O}xqZt1?D*aA)|M-q-6$2k$*^KA!s_QsGMcd)F^QJRpB3GKdHYCBJ zq^^U@hM(F@q7sD*_jB!KN}^fHtG6(eINe7_;bT%uH|`Z?b$j#e?4ys~oH zd#vK`|1E?44snrEJ&OYyXs|NHl0+O01PQTWpKd;IieAjeU+GN_Des009`}k26A5!c zNpfi`OYck5i2rmMau7QRf0+4KZh9jMFv_LD&s`>^XC9H7qF64hE>-MF{y2k+Pc0|ZuR- zub~f_QK;HyXk3z@y0dv?pFO**dwr(pj6Omk#G3q3rOXCa49!PvUZ;h1ONSm2=f}6& zL;-6H2rN8uMhY&}F)~7x+_Do#S2()9qjbk>-Xz$*_wjG#p{NRbxd$o|iqZH2^S9`7 zw9C;jg>0NCq7aEm{%C7RPZJtC%uSBT?EdtPPI*chr3DIH;bBKSrbpw|ktGX7Z2;Ux z#;^TBixM!mBtMsGN^Z{q%ru{kVOH=++#`BE2E#B zK^;J_!XYe`xWf7nH(F~GO66r*m2y(WZHTv!Zh{4x9L5NFa3+y75C;nKSq$QPl)K!Swv58*@>ubb3FBC-d#*FA;s|d){tD{p`E|gD-Y8$Qjk%v@w$128+ zMlls)@;$}|9AO`=D(mx(D(lSa1wW*{w#4!4J=HSW@Mzb4f_R=xqg^qIM8!47dKJsX zOa9qj+YEtTvIFquq0;$rd>^p@%@EmWQw! z>1G82tOeHo*R1fOt8Var*f7b>^PG4D5Jm!* zJ%MKJg$0ARUbU^OF3f*s7P=B3j<#-XQ}u>FXf&Mh8?8Uq5wkrv!IHA*O8#Uzp-3z3y{DdCEqP=3X}yV4O>$ zQv&hh`A!Is#RZkqoHLu#jI0LimLA>iWGMaa4 zWaV81bb70h372<@xm1d}e}YiL_bacd&z`Px4KBUz>X=JOvNUmZn-kl-b-kvEChLKH z_aRq76PO>kM8z$@5~4%mz2KQ;U$7oGr9uxct4Oc@q%0CmSh?&g0)1nOIzG1c4?DCt zh;5ik%cqdRZwS!D>|sN{ip7l<@W86Er>l((6RY@~G1m0?l$syaO3^H}dAQ1nDg?5I zuCMo4DpqNZxapo5+$P@2cO&hU~5PujUYRq{^6 zPNrbMEnTcQFIID|Ueak~=fx5h9LK#o!MZ3XU%0ke17zQT>$q_8w*Abz@>y8l_)z1{f#yRKiNZ$Xj6|{R=8S7eoBG*5DN3>-1t&q)v}) ze?Ku=(oTWxnk3?0&^sf&mAAF}cMDoa^+co+5=p6HRq3GqU!b8!MaoBtW@vROTWYHE z_Zi~IdH$gxdo%?Ul_RZiyA*Jk=rECfU?=7)GPHdhU}iwbkM|_@CpLTIA6Z|&JwWQ| z?~ceB7{T!}g8_vclQm^w2y2Y%!ZKBgw_Qg;wAfa=T<;Q?J~KH~ESDAe z3tWt2pc#k;c>axK-7;_pf_Ux^i2-4hQ7$L0t)xfet{g;)h_2JxAOcj<-C%Bevuwk= z{DlA&Z=x>W!#vtT!exlmAWzEA-bb1Y|F+mb=anl6;JUFg0%Xl`G=`DLB-uj-upx#e z@gop62*hk8n$a9e_ZloPGgO7&162ZHS&Vm^_G>>(r6|JG2a$;MyBhoAg#cHvQ_mBY ziMcQT-haFm2yr)%BNb*k9W0lU6?c@Pnj?zxE=%wTOae%R)TR7l_=(o3F8lu=*2R$6 ziFV~c{*i+%m9f{Ke2Kn~z^UZUADLDSzUOohNW|>X#_@R@Pn3;0dq6b_JHyYs?rdz2 z1^%jGRgBpp(iTxKKXwPSV0KtJlz>zikt^x+0;R5e2N2Ta z(iVgdEGfwpsopmK`Syyg>+7?E#hOvnRM6+dI~3mtqF;ZR#bJdtGNWb=%`{zN9Ij|) z<%Yq82h7y8v1-beFFJ}UzV@hwxX!HF_lz`n<>v` z7?|dH#Ph$5$14|^D{erG9vmL&QR;Zd2YM0-629s}9@=Z`dsfL>G%pGJ1%5#lkzJF~ zA^^c;?fY2a`U#Arg&!|h_U++j_|t}Ek!$?_7z`^uq3ZPHA(B*bK#W-$!F7g|>AID?bTKivFeMgpk+UA*WCKe) zdeFS-v^f~Z&?XqRi9WjXn-3y07u~iRPjH^5fUjk7v+lffr11ZZCcnXa`DVEr(-c8; z-Q>@sO=p&deB8Afm|6^RVl&azVEgt5gpJ>Pf$n|mbIEIfh}k^$g_>!T58Fm+^n75a zju+i)gx0bA!`GM$WMaLx>}~b%#fs0!PGRga2%KTywSoFJa(|Zb^OOq^K&q?WbK)hi z$5A$_C5a=Km7}aMEque(jCY0~bCXI;^`i2qzY>j3??4x4_{nzg_Df+*1IalaDXCWZ z8V~tqMXg4F;iaSK>@K4^z=0LxS!y-Bh9{V_ir6D?E3;<=*f;%v7|%S_qcvlJSkuSQ zwD)T_Nl@}yCOt5y&GWL~i6~2qXVREL^ql5s2Jc+LcDkDR#7I6KC4`)-?f09762gTN z4Q`|BFLdYT*s}dH=2}E6;`(DIto7F%(7B6daSNWNH45qc7oPiY+x`?Ss#E>M)f9po7Z0i!2Y zwD@~wMNOk$c-J!$gawBeOQ3H&HV94?l9&2ZL#wQ^Rj!!hZ6OvJRGL{CHQ^Enn;QZm zK+Ol9-Ou?Bgfnxu6p>a9)XuK(rP{8Zh}!)i$1G8GoUud_KPktm)IaSs)(uZtClg4*XX@NG z-rIqxCbMeXFBIdX9VallvbszfWp)2he1w~YOkKk>np@*I!IlNB$aN69)de=pzR~Qs zhXR8sQx^Z_q=8$k>2I1!c65ZyIdhJWopYpbzv5sK-4eg8ha7eVDwUU-rJ6&34Z$yk zECTwi-SRAND#ycAx!JRgH?w{ew%6xgcjjCk#O%??O{FC|xMUU-09~2m*dPCnlob>2 z14d)xkVh_n(!jDTt$UABG;H#+GeXp>HepCd^?^tFNTp5$b4&}RMLPJm;|JVEQshRT z4cY^JGZ-8{b7#=AB6(y1!+7VCZ(X#u8P=GGyE8Rwe8bZk5iED>eJ#wG*B`OEx-@7; zW4A@cXE)pEYBC;{l|Rd|{H~q9yyi!zY8b2Td#`aQv+?t97K`@@YWXTl?Ds)=QLEAA z#nu#CYR5LPuUng9#gJV91EI3YG(2My% z(ngAXiiJn0FW{-6wyMRBW6sH78(LH;iSE}#_sqg^(p2RS8I;r5XrrSHs?Ww#K>k|; z7YA0BQp1zdFUo_hq)<~4$?v+@y8YlQ_0Q>Ynl(L%N9-m0M^Q|eak ztfTB{wn={T_vi8sTXmnP)K_y7=c%N(zky>2gwlIDK*c8~jD3CtJ^?QnA!J)_hc|v& z<|_Z6bASiX{cXHFx4UA_N9hL_nM3=jBHL_3Y4qZ|Z_{+3y3lg0DK*?}U)B_<#`3+V zj`i*9iduk?Zrd3e6s$S&ytp{vzlzHdyoWEQ=aP>R?U<(<2%*3HRy%ZBy z6QsM!aYr6qDE%Qb!dLoSSOdRD(Zci)y@i27(c&VdzHYb~izXLXxg<`%tcMm(efh!f zmim)a<7MdcqZh5x=ch@O{?GO{Hsk8NN66dY()fv+gjFLL6^D<-_3dFU%@C$bm4i6< zsJW?~`Im~$Kt9-mPDC;+sUqfO5CDnb-juX%bD<{xZ4bg!B&kLg+A>EQ?#T1J=%Ey~ zqvnLnGF8zv+QWo|@XQugDb^3AaY$4sbfX+t>T=st2Nr)E$uQDThO;#RcgJ~a_*j3M zN~;I;OGm4aSy!Eud3E6jd`W~uq0Y-hr6TGH7C)qqasxC@wPm16Xt1`*@?UgG8Euvb zE%rJfl|w#A#OF*@y+tc zWLt%|afz+}hk6^EZ_)unuz=G=S6bRYXcddc^bj*-@b8F0U4Xe$)YUK48E3NdaVp&e z+I>h>G!a6Pvk=qzBzu;vi3bQJS<^RR(quHk9a=b*g>iwwt@K1oR@bruHryF@Z&t+b zU1;QL*@{yyK#@o^?m)l5%F)X#3WoNLH9mooCinb8ny)BLNQlw4|Ma&hp=;ixVgfgd zc)Nb135>l{Arp~q@2(8l@r;7FI+BYvIJ@eEqd>{Z^5|3n20A`wzavdIWEcVzk3IDV zEV`G}6t(59gWFaFyb$U_}rx(xZ-!L9j4NL9&lSD#MC80Rbq8nO> zsTsC8TXQ3$(m~tZm!#d0TQ08O>`fvuC%RPcfQk>qwIuo_&*%MQXB`iT5GbRN=%P%e zcMeiP(+xfb5fw<#7GKK8%Qb0wQt%YL7<2oKF;kQR(3bX8k5Z8Y{_G)eVzg~YhvrS& zrK7`iT||W^EK$%<{~Gj1*710QQXTW#G)$RA@EBj}aGDtRmFl<~PE}iAW458fOy-Z4 zhlfpQV^|WYC&dV*OO=7kLvcV46I|G_QBpWio(Uuin-~Y%QQRS~cWh5-Suir*ettWeck_jnr*?D@6yQ z7Q2SLHa4L)aatuR8$(2K$RaOqte=M4+glktaEp-=?Y=OfqWz7OIcjeXb;Spr;K zB_FT|RLE(d7G*O0ci7rut_s z2NYv`IdWHXl_Dy6NG|PQs8C&2gb=b?BSw}HORH08AQZRx6ONR~l~SC$o>`NDr8BgB zJ}+M7*tSTdYB}Dh4G+G&_Yt>cb-yJ&yut#_gz>#Ny!C7)^zYO<(aSX1?^M^EY$B8_ zqic?dge8oSm9+*8srpSZ6C(CN@iK0n)jmq{3c3$nRo#I4lY_L?+@KF6RjL0+4v!#u z)b`sP?c%(cVF}%`-rO|b?{KA*n%Fy3sALItgY8yv-mG`2jK^)x=Xapa@X<)U=SR$(_%v zA*MTHwKH!^Im{@`)gzM3Q$3N&iN)klD0^C)(+DGf$D=Uwa@4K|z2^Qdm`vK^uV zswzs*&CPh%>G0HG6KRD3V<}!CVFweoTiJvPo31zOOL>qhwPq8kVmS+dYYVLJEK-DY zuRPOoe;EI$Da*3e@8p0CgTII7fmx=OQ2dA&Kw2dds0nKR zY;%xLwpX!0j8e_`8nhf_O!Hp%4a0#fdSey1Yg7&JeI-;0U#TW5o27Ci--vu(ua;v~ zjCfM75&Z6-Keh2eBR#f&9BoSFKuoCFNkXWwpZ@JZ!_hjeAhBxsG`>^6 z6ifj*yGjB7%z^&R<6HfS8{IBs9+<*4a1CkbHT6n}d9@6mQt; zz5|G4WHocxlCTYuXY zrg`=aP|%ps1jiVhAHz~6o9_yu2#5`t)f4_2!nEVAAr5i|V3N=Lrs z3Ke;lnviU~WQuz5znoqWQ~QQe=@li)gOB8(11JEl&i=76qRj~S%J_!(PdOK4o~ z*I+l|m>_gGp0Jxdtpcu~TS9i4Jry_(t)Y;-VFg1~g;6X5CulQaPf^Ay4sT#F(O70d z80%sK$WTcURuE1$5gp(<{*`GF2|?AjMv+{hX}gsiTZhs`29jlJ;&z~F96v>De&>vk z3%16aqDMKRQ?~zybp_E)jl2aqm-62Bg_S>iH`JPkCPbxy04Ug^1KnD`!Rigot}>6N z9MQffp*Q(nWb~z+j0Sv0O-g&!SFfR3$C__^Lrp}<@p22fOTjMIq$UOc2*O~R z(zFXAeS5j+iy+&>U|M0QaMGWaJg@`wzj8wFi4;{RTSDw~B{r}1H1W7Ih)=X+EEVw7 zSRTS6VaU=m`1!nM^{kDRmqZzw?5Z~VP2MA2Fy>UrC$GT`d?(E$k^bsJI)4yf()!ZqNDy5C;}^h6Up`o2J*YE*hXfP=;|IYeTOUT3g*_$7D6e8cFn%YGx1`U z%;Q%*if#jA**rKjlRE zhRRBe9F1C2n8jXpJ)JCboxwA~qI0E~RuvLjpVoY%jl@Op^lxx5oT565`MLv3<>za$ z%~>MR3)*|Bha3Xb=HMCzylm117wQp}Vz`Ec2cL`tALEE7QtTcxz@^7SHm}-cZ5{k2 z6J7IBl0OXbYg0&?+6)+#8ebSP{E45MEKJ})t>O<}qC@thU>LEVGLj11JR@Q-DOvVu zW6Bs-W~o}s7n;KmVS$#rpE14Mx(^gC*V^E{k$QiSs<-SR7ET71hBdSS454SUaVEJY zxfl^cJ71r1%_DKl|BdTPopfyTKCyOW)yuTK(bk7>R!TGAR@%K(fGzXkkr@(y%zOx$b{;umEv z3#QPyDt^t4Tp0NKEy~zk!u%-?(d2AYiavb?I)D_+rd>kieVMw=+GL8}fr!Z63SVyV zS2K9KFf2?0;zHrpIu12!!*fYu@UL!9#UqWQcXP~S>RUy=(~jo!pgE`1R< z3GY?bpGmzGd9CnTb1G*>ow>tHBU5jZS(X9$0-}#tq9(V5(Fk3Vu8+p|CAo+9K7nJ= z#FCL==nBlc!4{9Y1wGYOCqQlLpzY`+--6@r>8bLGR#{Q+!bxv{)z1;d(OCNVPp&HR z%S1XMlw+7l(=n%73(aIS{nY<&k_vmV(Y{o)#U6vMQJ9~`%^WAnmCpoNVL(CYf#9Y0 ze*7wf+|3%mE|VAnm5~l{s=OOwD3~TU;3ww?NRs!MuVU5rG_~Gnfo2BDTDLYo~c`0{s&UfS8JhhO##9F7L|g?P$*jgEj)2u${o$Zns1DblWkYg`K*WptBnUx&>+fMG5^B`h8GhsjGtbBoUY;lCay zd@sfirPrUMuQ7!sBoMkS1EYTdZ4;URuzg8#vvy|20_Wg8RS6QgMw4<9Cc{yySjcIj zN$Ho8M8{4;!{PDyzq+tTX|M|U;MU+kMQ3&p;`tTX;9CfMuk()uiGA%OOcL?XBETcQ zmxNxfx#{Jmx$32Hj}u^(Zz){9>cLf=c}8@@i7y;2K8c?;><89E?m*Kt3Z>F^4s>MQ zYnNOYRT}rSgqk&t)qZn@nFY6_CbQ3VE2|H=3R5e;CprihzyQU~&YQQ{e9Q|eFYn3A zwZjw~yCDHWLcjyP8tQsx@VEv3BU7-Kp;2-p&E(i#v}u52@)WNOmylcbhG8yt5aSgN zESwmGsEQV+7iG0duoq1d6CofPn$Y~qvMW(EX6@atHg~AZO3J>HN zn(#3x)AEXUI5p`XlsoVBfy-E!J3;&33J{luquxXq5UG7VT@BVs2y6WnaTT_K7JdQO zb1)k@arw43QM?7oSKze}I}q=&Q&ubDJCD?yX%rXKSHSKZ)8Bu{`m>G@^$Xx6d-c-v zS5Ct;QY9Vk2*&J#*}ZPqLx`Fsn)Fqkiter1hP-b5(Va({;sDoCgrPU$ARfC24*bsz z`$Cml5d@1s!7h}!qBQ8H`_o8TEVuNaZV9~$j+LxdGPu8F2YCK?c z+!u-^_znr>d7(?wgj6P=daauj(r_#2Rxfe*to!#t#rMX4<%tM;YFfL5m`hRll$Xl!|Up~6a z@WT=r46`=>t-SS9+fF4eSYCuQt40!q?6)Ss^+dPQl&VT9Qr3( zVM>s&<_{5f#N(p$g^Py#ZyB);7_591=#GWVm^!72V@%dihZH~x-dX08WejC~!*s@T zV+hRA#t|QDPK(~-EywLf?VsYyxf+2AuXQicRhWTWCrLQK`WYssAy45_lZ~cj*hxf~ zh!1hPOTo_|LxTnX-p4+nr9w=)b&+!`=yDtjnCVMj+{vCh2JR=Y=)ZvlRpq>=unUC< zZ~_*-F1?k`r1XmR`hqTR=mhzby|vAfhd-r?C^Q%sL3|)Lz_r~K<8Y|D5N$$k7XmM=lz5}CRvu| zd$6@}X%CRvP_IOjsQidGD_L9FEAMBlFEbn6=pPRk|`U{ z98Q9)ze}ySN^uktDG93fiZM;Gl~9aB{OW2?HKu9NQ|~qM##~3-CaQYs=;X`WKoP@O zauPRKaY4jxKBoy%)%}pA9Hd%Y6Wcw<#`JOy$3{wEs;Y7!6Jsg-bYRy|%H^^n(^-sy zU}`2O!-zI^Ny>jeI#*y-Qq=w`w2gQUi^9rQeA_Hiu>nyyR%!esnun)){gNtrp*^0W z`163H-FzdK)=u*cRSsz3##ndN?5FWYEU=@eEjX26jb%SX8NM9T?NZ5coD=aYWFfPa zC$7HL*gEggi*Xr^E9w`Cf8b87GhrTI3D5=v%SK2vD&NT?n3yZMwHn>5GE*Nits)V( z(8uE(9q8k6$b)VUbHHX_h$+t$h6Qupj2Kzjxsj!9eR@iOd_)Z_Y?_@2wA#XEQyp>G z4j>@w*e0OZeGpd$pKKVZOn)9DCiMYeWCyD5MnvYCXtia9C>ntfnBP!)*aJ~T(R{nv z7Z^(DFG{weROSd038`wl(;tNep74617goQ%>tHu;( zyZYjO7`A&5`=3T-!2M#QqOBs|Ls7T{LP96Z+E5>|7_;=rI1Uy#G+*YX3)dKlSLNFd zLa71bnGdb!`H^-AR3?}q=W#9SYh(3Bv8qLEn=;2e-Ah5~C(c$0zD-l|OoE=D?ETY) zLhR5+YN-`@WLTQTMc&h}$dre6aEIH$#~eL81u5|AH~ixZ(_pt!Pd~CA^hk@4^^kMs zBrm2jS90p>FX!6CFCwb~$_KJah(d(V|196IzB;86dwzl<=|Nas`@B)GczjeF%jt%J zgE#2d2tu+REBcmnlbCj*x;xfYf%OMJp`yzYB!-bKdWm)pZX8QZwkPrMIUV?)kgFy7 z4S2g0=q@wDRgr89ZJC-!1Sw=oZZ*sZy52J#a~7^kp{M4rXSvG$7a2$QKi{NxA|i6l z-@(HmEa{E=}BZ^Rp@y*~K7RW@I#yz$7m?t=jviiYFk?;1`6MS$WSaW?GCK8x48pMMvV~n zprTZkG^klVkGR_V_0f>OXM9%ygc=BNsi2)l~3DTo0p9o*t5XlIaei zFGNK_xC~aL;N2*0Jk8)=QneN{_>K#EDAC?`y`m+IvlTrdS=<^U^f?Emop%7XhyR1i0ge z^xy&gQ4yU1cm!~;;T&-S&|S?NL>o5zn^u4v!pym$2E{)gxvzATjyOvfWb->{ONanj zl~1|WDdR$oEQzEMP$b=qC4JO99dB$s;gUR}Hm^dH&`fs^{KhF!5SPInv5sXsdmwcJ zf`W(PrK{-pkv2yHIo1cMc6lcD*o*bYg8TYC)DXZjR&Km(OFuSQ_GTIhRy~i!2Ld8x6k2ToeG#k2lbMvP(s4P( zan0hN1UZ4qMy5h5Q>u81V@)K?NPQV%5D5}u05jI1c`~B{NX=g&S~(=b^@H`;-Z+|Y zP(>*tb7TvCx=tj8QNg1+j@4oP9rC;CJYYAjp3*X0O)TbtS}}lCSKhq_WPcKi8uo9x zeIhw1jb=-aJq`Xlf#v3^dS>{&R?6nVT~WuSH$oo5N3a3N7y#!naa`=j`9QXo>(1{U z3UWcNlsV!Oz!q`BaD~(I9|>yVim3nMMDKvJO6rlzr7UpI!+W99iyym2%s=|fl6f`k&aU2=4 zJ~cfG{_n?pnYfpVDF;(is?K&~$`4F*YuVkw#ZrzC1Ov#NcPLxKfl6Bj~Tn8l3VrUq3otdxf5Nl-K13^ z#(y$qkEC~?DWx!b+9Rn({7m7S(;2g{#@jX#%OqCx@fbsT+Kpid0P|t>g0-$p;no=q zyO?Ap2W5bBgx!sDvCwZsr4l{FxMEf>t_c`s*#|odcn8!>1z<$1)kyZRsT^py^VIW0 zDbAyZVDcW1dd=MjMJ4qyZl68H>4xSn7LKdRV?BFix*Ck&6FI)`QghX)!m>Mq6Vb-M zMq8|s8j7CCe6|D8E}VVwA$M!z1EG|M1J~WQC9OGiGt-dnMLKrae`_w~%Uj`6)}@gM zH&yA=I3&gH25$K{)qz0pPpFPGY8+`prn_ddSDgWW4T@z5H4&?+ID|9Bf~Z5Q0+51b zXT@SQV2r(4q>6q5{m)LIYXob`OR1@;f_VjG)ir)a5yw~8npQd?5g@}>@1nQaK?G*u z%ufWCjXFO!PkkDVRBfq@+*(0ZB!8oU`Gb6OpUfJGQpGdRb0a6!)B+dL1ncxwYV|~T zQyVUQ?6a*;Rz?>^Q*ypBg5S%{ex70gpusw_fK+N!+Fp!bEmjWv{nn^kSNw);?h4If zKi_a>Q5Wazqmb9Zu=vA3G*$S>xrU+*-kz*8uFj~> zO{SbtkKH{|O3H;+c!k}}Q0sAru@p5G=r@7Mas|4*t%a1HkuJg3nN3PP}pQ8HXhn9n3 zX`5W|(p682e;rYX=T2Poh<(Tg=3bqfX-CYT`v|hy3noxM67)L`#LD^F&0z617NxC} zKx!)BZ+M`4a0F+VnpuGw9`Wda2aCPTFb7_FnkkNgtOp=R6C60F`dyl6gxgH{*>%or z^`aq~Pu3?wa}(G7s@SApCOKbm;_dW(8t45&=S0zC;TnOgjmW=cW22*eQTKtYGDr(C zfcou@50T;oJ?nrQ*BnF7tZn%c)4Olq=UX=?-zgiDt$6N7*i-{5*>38HLr!rHnx=Dl zL72!Xk`Cno?jY3{qut8gRJ{7s2Iep#3T&%nR_G5y@y)Gpe@^WS9xh?$ z6S5=A@&B9-K-lN5u>#Fl)57^f&HGlqaT|XOCk<=@tIB?w=Z%}JF2f{MJ9!6sg^bB( z1FiquF&tRz_I}D_SMm%JWNSuj=j#Wl1znUhDg79WzA@;c!xuAVO^GTX6kOhe@iu_dX{1Du&V7MbcZU;`5^sOGG z0yE69xq~t>C}tL`nO2Em?Y<5&b=!dI!ux<@!dXPokhn;2q))7G=eR6HLV6Yx>njeB z=SPNi+Ac-182m(bl9dsex_UguhK1a~2c}1*(33Y2VME*q!Qg7y#v9LMF@ttDg zR72|jJ?e`c#g|Flr**32dbD6mSaE~T;Xg$3(1V>meeP)SH@V2R;7{td@S5qV*(Ks& z0%+pekK^hI>r*34AEQvlHqfhuUmg^CjScA!GAHkRhU%y^ohDO3k<9IGKb#k*(>+$l zvPk;GX2k>UEEB%5gVe;Oc2a+c3m~S8=^9INw1}5=R~l%mMPN_$Z);bK3Xtm=@gKY1 zOT55cND-vXbf5>91+le4>bSrD7SUgc7^UjLJ<<&;gB}uV?_KDvO-2IMcREX$gY`Pc z{jy@-3uL1^Az3r3kqEqv|s7ed)IFA4icxQq+ zEJ`(__tf+VZHa1z6)#hYp7z;gSs)KrX;b0KNabZs{cJ|w*`kZMBXCdXp?w0eO` zlhnYSjTKuE#1?{EKJLe;FRJ@l56l_@7Q~0TAd2=ec@%Pe^^L3Pdq3g5J`j9~G$aN_ ziBWIf9fsKgRh_4fs}Ez5Vz>g*CacEi7qy&--(`vId`vv0m-xi+f|r5XBg=(Gu~KuS zhJ~AAp_?!Unux&|tRjr4wlL7rMbalh`iDS8DN%3=B}q)-JO_Rfn!R;gw9{S8r&fT1 zvz{_CXHhnkTTt(hVpt2c$=*uC`RSQkB&?5PEUlZGi$Y@!=jooG*r6>qRi~AuN1nqP z?`#I&IeFcJNt7j<>h#gMBf9>#gi<O8z$L86QTrcK|8@LYXcqRD z6kPN_HNN${qws^OfjD`4hhKMb=Bf6B3L5&KhlGIR^lMn!b{26(%K#3Zbe=S(CW%b8 zv3wgBm2?MlvUy@hjf56-o8wr)!=vVBIxFK!P@x#h&hR6PgwnS)CbPS?Ocr`toBXlC zLb;TE`pQw=fT0LMsrtoyZ25_LBgAxYXp|0SWtTo|q$=|9ged#oFHagXwb6{1!kp~D zipeNvditfb_h_vBRRWT+XWO%lH71Xm2@6&+kTJM#&0jkMc?bVaW6K<4$Zvg#8C5dv z6CF9*!^Dr(*lDA4f6N>m{7*-#+!H&#aS0r;)#@tW&6^io&=-zt4`SN&JV;Ng?0Bdt@LK050@q zqCU)DH~vU(nyL5d0?18-RF?pgE>sfAv2{jV;$Yl}W23L~hgEBkiZp-{IHcrXMWCvn z))#@GuTQG?NW21j!m-48@L+`@70h`SL5Cu@EuyB==U|SSD2H;|s&A}csYEre#w1eO zoJcFS(w%YGt#M>M5~u~NJ?Iq$$JYI-D8eyg@nnDr@8rK@G^q>fgk$l6E#A*Y&%^b^ zWu%FmcO)2xMF;QYUs~T9eUWOPUw)WJYF@W-`b*dbg%qKG&s?^5{TFt(9b`3#G&P=D zffZpn1ugFQBDNWe#^RKrDpEHk9u0wdZx!2&Ma(x@D#mB#O3yqlRIbWN`qfcIR(<;= z9#@_uhixW7dL)Wpyc&GX#QZGzxE{1HVF8=BPBmyfLhiYc0daiqu&FUFG;Rz@AIQH= z#;nIk6K>Kazo1J^p+gr{K>ca%JGD7l(t4c`Nk0{!PndOrSd#i6I=uM4f?47u-eQzh z0EBPYz!2B7W5&KoBrg)@ zl8nen5*CP(Yqzkz`j-kT{@&{XsUZB#@B22ssB>MpaTfABuLDQ54Eqn&ZQZvLc_yX#n`wyIfq61=Z4wPdcqmnRxXU?Md zu_5)0vGIt`2_+8!k;<){+TbYUImvQBfixOemMHkuFLo^9|;tMMaZPE&ZIZdaQ09tMqX}f;Uss zEEu54?w^|A?3Z1(|4Mda0Uqb|^^D zg0Gsh8Kn(>N4O<|QysqWN#6Kpk?SC!F2onf{O_|Z&P{nr=NoF+ zn2680hcup~5&^%qdrDOKS#jVVK~wmw_v31F$vsil5e-(AhO%q~s)R?V=jYI!tRPur zlQ*C$&1)$Rn%}*WT4{cXMyObl=sw1Oqap1T1s!#yK5=hKKv2p#r*8ed*;1rdH6RXx z2XW+@uZYf7H8jwWneDGB9rE`ZS_cTfW?GgDTNP7jJT zOEbN6Mhw^R^obhHyCnU`jph^#8Z6(itD@;8{CIi}-%#7In4!kBEQ~h#C)u9%+0K(p zUO6|Ba0DZGAbxcW>->(T@&2{X%d05PuhiVSxv5WmMLdUMyVz~4WF?p!=K|+QB2>Fu zbCxY=Zycv4GQ*`{bxv`xfm@9fBo1 z|N8a(TsJ2v-6ooJ)TQ}>(k|_V!murCFs^IJvjJFhuD?n4+0b}-TLyutAXBYRVUPHk z3(BYtk7ZNq%dM&6%xka_U6#3sNhm`p%{Vn%*+(X7{e=dyk|7FB#S2vnAE<9=&URmI zeGGeHEMJt~7I+3>A zevAd{P;(h-{bAY^c6UlqT%Lk1v$bGjJsXnG;i zva_Vd7h`|ez_)X}57clqfz&_zBG!rOs8`b(E2NCht<K7S)o|T9)w6eb15F?*lJ}so+Q?jF-|h@cl@ZM&are?IDwkyaWrTHpiKxGyv~ zeM6Or})iRqMlH#HiXj+|v>x?oN)9gebh-4?0pAoLxA>x_R@MU4ZCFpEWa`j&Ek z#&1gvz1EfRH9R9tCZ7&}@A=(srSI-cZ0Kr~By543zhv=CFew=)B658@)vm`;*BB#Y zs`%rC(~vLSTJfI_gBY=t>j=CaT4JSnJ9K8)cFc zx?<#x>geyN{g`TB)kEaWTH++A1O->waoP=~J`RfPer|N~t zN<1%C2Pnw#Dh(2qy6MkUlm-HZVRA{44sm6V5QyS|(e0(a__o++?Td&sh#19!{qN}R z-q0-!S6r`0K;aAcQ*q?aA~EtA<-#f86;B@}`>@o zZgRWH7LpL|Fq8)LfyaL)S&{saSsoA2iOweWGAJ?%KbnuWwwL_1jXrfzu!l zW;2kiCB>EOFgJ@9kzwkj!2Wv|=)7R9{rz#Wky_>MMN@JkTE-u;^Zprm9X+W8-FWwj zV<$G0qY6I#pFIux(8(~SGz-#l>(ez)cxU)2p@54*EuXeuKaE3&Hgh2EgIF8xz*-gg zvHv6&5}yE&Bv7v`wc&Mv{o2`-@iQhN8SSN>Gz3hkOVwAcT^b00doUSg5DnNRf%iWV z3~*JF{#`SSAV&eaD%~X>JwBicW7OmTCIRt=1$DmBn4N(yU&e9-Uo%OmxvAsY%wtTN z_5-c^bsqqS?}p~V=~g zdQF{FXhzQ&eVNZRxB=TTQDCqfFls>cC@ePozE|AZkbCsre~$!K-AKttdL#vxhUcpB z%QO1m2p>|7RU!Zs+$2BI@+@}*bX;%`+_P&wXI@=RC&?EYm&W#WnPY2wbBy+5@# zXpjw9c?lBCVFb#8`5e|Rk|6_-1L-A{`e066S&|Q|%qEqH>Q|ZgvD$_73pG!xD$zB< zEPSUeh;bsYB9%X#Bnm0dRnO%rQ+KTgxyHtxBs^4#Z!9Z&eR1D|LGj2qX8!?XT2#=C zEKE|Vx|?oLQN9(0xcpy_Oe!6MAJ$XP^P!i8CQnnb9WMHWhzInZ(7SwZf;?5nA?Q#p zUDO=8WZ02@)ft5m-VhfJXj^v&yJG8Tm?vnKk|1}#eK6%Z5j@e;l%UTURm3xG!c!Zs zgJR;bZd}@yQY^RkopKCe?K8aCLS#l)wGk7a8H9n$<{qh}TEx_qvp$79EdCnF*Mxm+ zHU+(@G1DHXGHl*}p&Ij-{-~=GG4)6J!aeKI-om%$i`cY5oqWCqHbQ`|wUMz+_aH8Q z15(jF3Nr6Pe+APJq%{{7XTTL{5 ztvT!IJXTtMk>Ti}M`A`so^J3+q(s`?&{R0pJZpaZ3~ciMeEXOG@-O<2ro$hIiQ46| zau{5PFzvwDiyBM_u0g$)*LQPc!0RI5ODE6s0_WWu{aS%p<{>pHGY!~3i_y7YA7&O8K^?g zOvSEIB<25+BGi8%PqXzmf5^`S0Dwfi0Sarb|I`At*M~2`*m!JYd{w2`UR?Q#`6$X+ zPOn5pp_cnbU5U;h(_Z0GPX8z$CF&)kM~RE`rtTs@M83=yl1^fkCLpak3C5-cDkoD|~OY)BTxPivS1-=%Zi-tF<#6{~}^>MnhctDoH zBxAOTD142ghAS%t;?4I+QXe-|;%N3ow}zmHy?A1)E3xGB#6;lW{(v2+QIiy__yPX` zg=?lee<@3ai?ntK>$AhXo&pIrV8tYPtR;YGu=)9+uwu||feT=$57vwn6bwmBh&a5@W+;&0@YNhgngMZ#uIqI0du+5K`4M06|I3iSKf4;;%Q!j*&FNlp zGDOaTg@E}*9B^sjyK)-3`ql$xSbPy<0ux)g4c0-q|l}VAXU$;+o|m*i%g~dY2L0|Z7kjKD5C1&j(3RfB|02D)Nqhb1 z(e1SuNhgE3lp+cx9ayZ*L_b)Hn5~q}ECX4Edri#N9@#t}yXk_<&p=m}gUyVFlKM-B zoFnjffhE_hc(#vi*S8Wp6c|(<{fKJAmTuIY)27_I9+Ti5BjTe z;Wa2moAAWI5^S(8E^l^_|8Z>sD~Kop?~0a$;WD5+9^&l>hT?xPQAba+!(49>zk(>PhJC0p6X$>yBG2 zp;JuV9VqVztrza?(cc52i>L=ljKt3<8x*nHRdXxL?Zdzs*3qK9J)UC&@nWsFZu8j^t#&0Gs&T> z+MS4r5-M;{7%)G!N1z!L9|`Q-K`jeDfpf4)g`Zp#QXJPGYKF*6t<{a5R>}VCAz^pB zgIV)DTJS3Oc^4gJr-OJucz|b5K+pWAieo{OK$z}L(x4+S z=V3P$K@_54GX93Ac>`KU6Yjw5Q!b2Ex6B}T%U(=+-Bk8g$5V$&xV#CBQ zu)>gEY4$2$p3__mEhA-a61i4NU^^!iz|C~qz}Kizvq;B@s;B>m`x+eZ43ULI-juJ4 zwk?HU!Iv2)LWQrPlZX|<1QV$3%*m=t76-X{pOIg5{`J$<2h!5(NwG&JCO3d*s-q})2LjZ2yc42 z&J0wQmF|j^W3a88y5|CQ;Y!{Br9U%D^nat73h-vDNSd3nJVA!Aanj1Qc(P&5iu~^) zW;*j>evN1+A{sA$sA-h(S8sx#cV*^)x5x9Xc*zZl*Lv8bn*H?-!OkXkkY-_Z1!zB?VBuCuj1Sl3{qa>ZQ41 zmS$151xkCS>W@1(T`8KzrTlUOm_xlu=YzNP+8df`iHuWHV1{EAT&v&kL3u6@Qa4L4W)lOe z+w|zq36-_h$TPsVgsrs?9z9b|$BB-q^axcDH|HBB$;N6s${`1yYITU75m;)5i?B`i zR;r(WPI>fR(?MU^&OJ_;mdRI!A@TAQ71m$Wl3f$q%yMfiOF$}ZJ=2)NL2;8fYJn%) zCThb)){ErAM4Bw23*Uv@rWjFp+na4H4tKp(LZ2yk&iho-9B|G&ajm(-eEbi}=+}Wv z3Y-ce3jdIr?6-|cx`EilAq9miM-tU7+o~g+ikvXK_adLScpL~YXsTKe#w?@+D24GJu8Pj6Au(FDP&K` zcFeUi6tZ-x5^p04Z*IoLQu@>34xamgkgmM$Xxp5_;e)SaG|@ zZU{_-{#lqRbNg_70?V*TrM<$Kkz!VN+-g=gVTwTQ+Ze#-sJD?2tnLJw8u3@^g%SZq z@{P-Iy4LV7G!^z+)Tbx|e-w??k0SZ5`^LU->jxrlB?Fq1fq4&I$bpHpbp_eX3!qUS zX;JQ4WD-4RYFfAO28ZUV8&;foPZhQk%Bs%Xat@&qI;sF5ou+jMzlZ0P9}7CXS-NFG zD18ybO_1stkQ@~z^_Z$KFzp}O#|npC{87*WFKug>SgX`);{2Ra%c20hbtvkF?cDR9f0*4zV3NGUn;FR=K-bIXO zEpfWl!%SMOv}hj9ZkT-!_ll%v^$HKkBS zIJYDLp{&)S?%W^P{e-c2O80jWcc#+5-=55SBI7(TACFS4n|^iV^9(EuPesNPSmqa{%p6Lg{eDsE;SVj|j_bGYcLAv7=G zjr}#Sy2m8+-bJ0-1qTkY7Rln128xwe(0)6x60<~@(H+|Y ztxVMe3-WfpJaOnsL1ZhSkNi>yz8hqsoS!99|5mNPR#&@T6|HL#>g(I88*kL%BHf zg}h|p?Yv*RNYycZN4NAdj89XyV_*FIC%bZw3_}!EY#*5hc5fN0w~eshB|aRM&k!US z$}Vky7{b%Y+^WSvddjRL3`!DLAXyb{j@gK-hv&0m9pvefkw0Cj@{gmh?@CD0Qgfj#$aRfVnQXJX7B^48UYJKx* zM|0F}z`60C}vN&Vm6AI6f-khiAo z0s%BR?a=KRcGOjO4VjPGlc=%LL}@IkngoYDfp_iK3z{`dRd;%3^x;65Tn{F6u9aS+ z5gnTvH}O7}zzF0owNT3-kmmssi1u=9JmPJncC<5c6R4n^=h|o;z0xH#n~NBje!zFV zQR=9TSWN}eyWnyrsy22|Cm@v=qWvA6yvDYV7gU1Vw3EziFtY@}r(=mgyH>TpsEka_ zd^hN9mTK2C5i`zl`a__Yp#%R`v#r{OL^-vst8o)!*_oS207cDY>JEur3OE z6?;WTCw7oOgIOXmTkUAGX>g6;gv$q-IyDA)p6ZbiQ4yExB+t$p;8h#bNsks%@TP{Q zo7y7%oSRnS2`QACW-uFcq|iP(02V?V`R?In0o>sa{#Vj&8DFD)$_!&9exKd|b}e78 zrg92eZ*We#wd5__aV34PT{(5PXp|s~2`tL~Ce;V~D{GwiL6}1%PGBUr%7Te#YDh}1 zUMl}PY$KIlZee7J!p;wl?}gvg%TdzaFXTajAQ*-vns`!@Q~|T*J!ZMuT*$M51GwH& z1I8WK{2_2U_6F$424L=y?!ZI8MB<={w+7RaX)8d^O{xmi@iq318XWUk=;g@mt**{-0u z`;M2nUPwF{0}l+6wgf(OnY->Xql@6`i&}X8d*vw1TVmF#ixgJzd}rs|RmsEmnRrvs zD!K_QiR5h-PU6U&&IshQ&jq6qI7>nMm%G-CCy3ZP0_Qi5G>ipUwW_!j6?QCh^m-%H z#T`TG$(5m8Cw8r~oLD4r0Z|HWAOd)lL}VE%i^gO=4n!}bM4(ep#z1y`)Dah6y50&w z34$Sd%H9ZYbVbSA{GNi_%+{P`npQ-8ETn6kD;fD)j+;J=&oW*v_S=qX5HTwW=FE#= zMKx=c7jELbGmRs^&Fzmg@{!sT#1j%U*SdZjY9cd(;W+PtcOc%wn)%mtl>gyN8Rah8 z)Yz;5wufkhgL1ryUcz=G6!{A1Y4K;QtIWDsYtB-KT8?%$FcKwq4B6}# z+IM2!0yTTAz|tny{Lt8e;ea4kR7ResSz!E$djXlROJFcu=#PiB?D>=a#MEby_Gw-M4AW60- z@ef4L06XbZa*N}yt6QJ=gY>`=tb}=M`GX!IC^aWl9>M@xE#16MsnptD8D}PDnv5cv zvZKFd`#%sx$PDj;c@We+Rbm(4?|z4d8HVS(}*$mX@_{Gn0FH` zWe&18jPPUnWi4c0jpH?46umJ$K#E{H1PZHi$Yos?n9Dd_!Y?GGS!?#pKJdPdK-D8@NlW*NXO)Zy- z%x!**^yHI79L00#T>V<3_{vFPVJczG+^|~&j6Ot;0uCLu0?mZUC8Ga}VAPG;u)c@r z0z6wW2w_Z2m0{t*QzAg~QOrci)|Ux##FD)kVf7qSETG1A#MebPwZ?SxKd`-^ML7Oo z0Br?4!}N7YjMpCWt4!}DQadMZp}0}t1=X;T32~YPa!qrlJ){){bz?my!9nl$@<$2+ zB-CWq!}?M+r0IBvvvHypR9b)O4&w9n{&l{6Zc?^j(nMtdp)-0&9L4ptDTKj*igzUz zpv5)ax_XOglSi)*+QLsDC?Rai>{I2~O)?2;L)vCpX4V+Wq6iO(Z1)01RPHYKoGUlx zlv1j5)I=HOsGpbrA}tbM_b6Ph6=+&oV>?7^dM-$nOajrHFhclH%^i&q6z|69yVZx6 z+g#14J-XWX6A=su-r?4}EffPNhzOA(?J;@Z!7(r@xNa*n2kve9o~jrsaZkfM@JG@+ z4hbo>mFAJWODOdf9Wa@UQ>YAa5q1K7BPt1>xP-xy<+iN=;D51-;K$Vbfxn3Hk7-*t z!;}iWGV?M7t~n2Ln{j`I%7*o#V$PA?@r{T;j96Cx8*gna(nE}Qg1x-+p$l%`2jRH# zlq%L_k~X|%3Z@E*YN`rma~M}8+O$iLpFWOGU1ODv1&^w%Ao3qM9fiuuIk#{?fci9c zq?_aA7H2V4U(p3!Rhh0U06h-EW0y89-PP|LY&K_xnw8vZ$Sh9qlw1RBD7(z*slC?p z&Qi3pasMnk`)>0f+9 zL|L1q5TfdBI-^9*O)x$H>xh6%>X`JzZbhW9-c?@zoiZ9>H+29ra3O2*mZ{tI5$IvG z9n20mNaU5b$vb}PA*Vg17%ao1NArzLmF|ZQ&qJhD-q!H`5~*t&+CpK~MQDM&ZG<&Z z1kEU&era(ZeR$s;*~78SnzU};{u){!zQnm_3g z^NEe}(j)s^Aj! zBuT04pcxA!ktIy4AK_T00;-|8wUPVN_J`Q11pZRkr^uM9pGhAL zb4QHU0O_6(5O?VuIjg9{>{_aO$lZ* zbyW#r0C-4o*x#cZEt{Aa3?r;~Zl>Y9TM;>M0G?B^aepEtodSE~y^Q;b1VOWV*->q^ zlL~QuqGQGCnvE{B^<^j{M{rOq604=ufCOl$=o(QhRuFE1tgL5Ux7J%?Qj)?`UY^*! zRnVUf@)4%(dk3dST&c>n3}RsAh}kim*RW5rzgfqWm?AD$UX{GxhHkS@q+vv3Sj81`M8+B6P(`!Xp%E?4A*D!w1NqXaZ#jt= zgoq&KNX&)6GCL$-pvf|2lE}Gb)KRnAuA1UhRE0hDG?UguT~W0+M#({`76<5=nY~S7 zGNgwzRhcQV;tIJ4-xnFq6M42<d;^GFi{u3LS!cG4iYJmcrt~8;R+6({gU}Tuh~7JJZNW z9<}mTl`36{U z>MyQVYbBIgNHq~|i}D7!kWQRJLhCe&E;PMp{2o~=^n$6zh!{p)U9@K>HA>Utaxs{i zaGL)dGf0@oXbBekAx=XfvX`ilqn~tFh8G%D+9FKR*a}uZa5i zDcVK|nYcW$(=tp|>0;z91{5l_6Pl^5zrQ95@ct)-r_syykB(&(dC|Z+2r+_4-N$(&AMuU^U;$ z74iwxJjJ&YWA#;a-fHzC#jF{!V@1!00$(G4B)+J@~oJap{bUja(A%ddU!80 zoD#S03Gvf-28`;aCR)l^H&TG;iTJTVsJ#e%Te2wsy8{tWz$s73@*PcyezmCx-Qkye z+!*nOie~QR>j0NM5IorgEJq^`4qu8fNX;AqtYDnnRfe^ps6W`+pdW7>`)(4fE!xFk zM5=+b9spkEnv!V#5f6kdK`M>K^exoXnm5`Iu=&d099g@h z#X*mp78;m!gcFE>Pm&tsBAp%Q407 zyLrB0cc|G*Bn@7WVhA6zFa6E=x>gh1a_}xq2DfBvX9W{zrleYr!1(q#9r2uKPoyj(?-Go7iZ)pZPMz?7bsk z=o@VkeQY6gD&HB!HS>OGl@QT=YDG9b7xXKOLTZzVh<`t6GAze~2_f*V=+!z3fLn|z zC9LQwVf)N#HYian$uJxUj>GT$Acw;jAM)Kc@OXpsMI3|;gGQ#GovdcRXaQ&<8ZQx0 zKnDgt^l_~)|S8s^Oc?D^pvhAbHMiOayhP;Mxub8XG5vZ7s z&OBG=G$l~H+(vyc<9m1)kY`csLtiA&9oQ4M^CqKyMaPxqI{}iqmeA_MPb2U-0c$FqgKC@_}71V%2=!mxPg zMY(Z3l+RdGN_Yg-uI<`P+hlaM6Hb=M{wx}Wk25uOsCklB4GD0OgWhv&U$ONv7^GcK7+$>lud_uWUSnGu39dA zFFDR&q@4`tz~%K_2l`@h=<$wJiFP);j;U9;_Rn_P+!FJ>V{fvvUV}4s6-mFHY@9F3 z9H*jUK&B>R;BbA69EcyXMAc8^>k5ug_DSd`ytw^QTQvcuO>?O!Ns(7skaS{rF&io- ze<467oKgONa+@jBF0AmUzLaM*l^6HPA0uL03QmPTzG29R_i@kYPIh^9RLTxJk#LKdwiucXypWIy+949@=fV@UyUK zBsU3=1-fnaTfuO6W&D5q4dwa~{Z{aWbStAf`P|W)oc9P#;!%eQf@=8dh>Ue)*s?=e zBggl*wGV!yuXT;ul&j8GIW>u3RT05myJHpb?T4~qBepJ0qzPF|h zB4=D#%}{oVHu`O%H*6oYAIVkof*!ny(e%<)@8@_RX%mz!=0fFb2{ZFd{fyTHCNjaa zxLADdO(^G?lpDoG5?E)6zN2qc)(tH)kVRXobAy@S{RiXYSa4tQ1= zBk8J;bfGQOPad))OwSR$W|)cQ(IP^miXw3}&EeoZA{E#-t9_|)e>s{{RK@8)FWacuEl1?=>QMJFQ96^hB$M%08Hhw1@Kh?Qb7k;F zgcLCq+f?)h2HvY-Xvn-}BpzL`6HZO$Hus6_f)G*Fkm6S)bQE?%y-mL3DUk^x5co&h zZ$%5vz1oX+J2L<>uVIHjOe84&A6=CbC8;JJ8?3yo;X{prV2Cjsc9y;jHSKg{l^7VA zQsG)>*k{7`*&n}S&B^8XIymPl$uC5LP{dLRsNYf*`DN)$NA+-eH7qW?u>EQ04}T_w zj9&|c9F5WIz=7}|^=NBiH_kbl==PEP?|{6$HeIyN??B75?QHipp2%Zb;Azb|8O*JIjx3XNtB-l3WpG4U1mUrvV`m^Gp!icVx}f?lTmiZ zXBs%OLEX$!0)EBxHscSco@g3(T%e5GhHrDG8 zH7I)1ICMPRt-6{J)_N~ESs|{%p!deTM7^kJXfpd-yvy;j3=kO95#=PUQA6B2#SCA3 zA{65esxi)UYc~vw$c+!B5ek8ka<;}do))!}06y|;!!I7YAZw8Oz@&9=qiX>&%<=CC zet_IIa*Z`jqB#Iamc_7N-gLm=!*WgP!#QQTKj^B&g&pD|(X|>_m}Mr_J0do!6JiB9 zoo@faO%oA*?YNBqcH&B^Rw2Sbi9`tlK zaXf;}nbfPTmj-lnqvmbFuaDI01>Q>wTh^5;0;y=fbMW=CH+pr0t^hqaFb>6Wmr3AR zFu3VY+7e8X4cGWCey48Pb~bqU3wCh&Zhf2;57hpXYJ55qzum|P+BTcm1JZUu(^?vo z%k0#<%r6!VP_UH3?gfFWgs5XL?K3UqUsJAZNG1yNfz5nY~D`$W%xfaj5fd zB=>+c4;!M6hwzG0Viv3DJHsD%qLbN!70WqoD<1uZ<*a4L+wjO*r2}5_5}7JWitj{6 zPcHJRCUuYkwQB5;kP2haQam|*y!LCZ?y>70*RQ~Zlv*-)0&N>;x*v@u;{^IM76M{E z(6KV5{nWgB?ILeGsu6xqx@;hbD5eEX{bC;4>GO0ZnHoKydUZ;wmxTKo84?9{KTPbb z_*PDd`U{)wJBjp z3Pd$tW$#UfHhM1N;$io3yJ82oCgV;p%p0;+-H)U=-Z zT~YBiYdor@IL#Iq0TEu{HUTL(p7F;4^4%HfTVH5C#E!Ve?W7MeNQQi_+vil7bC1&yo94L^*&T~b__XtP47frcw(Em9%sU zn#IFpVhuW>R36216z7>5j4zF2R17a;cxsiVK~gVcp(!B5XWaQBy|agoL@?W2XOdW? z{e=jRX8;`!VhkfIcF^OQ80nXq_-cpwk|?a`+5i-OaH8OLQS`^|Ql;3H%on>ak>wnl zJSxZoW_0_$!=vK!RM+^g`4}KUGKs`#GcW>xN*Zct3VubWLA9{|6s$JbsL(ou>bYC1 zexov96Nfo2a9{^Q>|6;zH_)>~%S7P`lDDKU8UyHp`dNCfay8vdtS&@~LUgThoHe$oIGP3Lv`*YnklT(d@3YNe zyKA>W6w3aj6#GHlAMGHpwxcYrDBGQ7RGwl2ULDzM0gn>8SlJ1)^IvCym6-qxvO+|E zMfI^s1dyu^`>g|cJvQck7OUlf&Zz(jC0Oo7KYM$#Bt-xEn{?>6eCw40&J z=Rr;qsp;Kg{9mNg(nFS$Q#upz^haMJ<<>qI|(|(74tdbKi0@@erZ;8Ah1~6uH*Gm$dF5` zlh>%#I+3o+J&3w= =hhI;u>Tugr#=j%#x=A@|{6qOOQW2Zp_B_}}>9aRrXma*B_ z+LG)h(=d(;bs2&(!I=-yqH=u>^+nVbDBTwAUjAKMnfMcDQQbj@*7mx#x93D!UsMp9 zPUyi;<%%{)%4O%Rp(2cO4ebT`(k}4V{vtLKIS-CuJH>rXiO1blXkwOH64qf?lH*-* z#m2}FZd2(IVS?2909a0~r}kJ=-PB0DubQh;g6cN;?=;!eU{=|Z7EJQnUMF?vii)0SQZRc0_7KP!6i}<}Tx4msiBe?E+Eca4!LwMN zny`&j5c@;?OR3H*iCHY{57!L9ZA@|xo#nr#G>A(7d5d7?o)}S7fy<2KmHN;hR3;qL z;Mi(PDwO{W6Tk+k=j?R;Iu75>jR5ZNTikD7fOj& znfTe97))~M-dTG*ZZvNlcBPTo7v4fP>Ru0`N~4yjWhTkgGtI<%o3Uo>xwF*Agb0I| z&@Ush-0WdnSUaaexyUBd&BN>8vrf~OCu=ejhawhV2%2Wvt*ne8n(g8pVi4ODi6PJ}I`oKxT$FS}esI*0YiP;lEZ!NhP_9|{Qumls z!O{H=n1=8M*^MGu4zB-JEpr807aH&P7OUWFlMM@Yjc^2nx9#{BBqq^HJ+GM0O{Rjx znUxSGhtKhk2Hu;L62wFKrGGHNS%J|Vd}0yG95qij0x9J$K{cqqI>P+czX4GG*{m0L z1Fv;t-wVdIp_4w_O>qS%kI{!}m0!={>=qMV8fCtte8RR-Nf5hCB<2DKtz7kSDvr%% z0;Ig*Q;vJf)jjxYey%*crOf5|scPI^vDnsJK`_X%D>2%$xB~{7Ci516zAtY4Vcp5maH)p<`c=t379jr!ln?|{)GC;|oyrcDUiA*C+y~-M~ zxNm+D?L@K)=*aUI1fFHsRK1sHZSwG&24Dtq(KI!ppF0{ErXCGB`+`u; zuy(?LX51joQ{}*pZLk&Fh5p8fWL0nzQAHWh;Bqg~Gaw8YN%*^!vS8Wh#g zzsBcvnEcjXbE&LXe{00M#(zCZ-r0qY*mw$IRpTkHK6U&^c#n>gq^8d_+Gc?wo3!0| zGnVA2hjePHrI+e9UoU>yhM9{yy-S@@Taul)QeHv|_O7R0plLIk+WAc=b4MqTKp>54 z*cti|m?XS0F!@@oA~+rvD;&)Y+6wVoBDXHrc#%`oG7hF`nZ;6;_yok+yVPeT|xho zjmJkYxLcv!@gWy(hV_=S39s3qFd!wQJ;KXIM^I8!F2@4^STPoCcj$Lnq&w!%6To7j z1{*9vxo*Kl+gNiOwQUH_e05nct}#1`&LtXS>GJXg!FOexHI}>goT&K9_z@B-rmMYD z-Vp^g6tFRLnLVfoWUz^oC&KX)rBxa03P)8`zY|8~XkF zTt!Ym_%0$KAVzIRjm=KjONfdK8Ram7_)TwaanO%`CXshI`314GSr9e0J~uQjaP5$L zoY02;@U;lj}fT6*BzUL{MvH1CGS!Q8r0NJB*jb;?0V(($za}|YgXz{p#diN zXc2NR=dI@SLK;8lbivIHDZzSpsmTBJ(>i;h$Jnm zliciNIwI(e(nEJLVY?XH-e;xVtf)5aIjk*w1;Z##!cRrk+>~!p0v{JXSO@rcZ%3&Z z{u;DEx8;7uw>b-BSOytQw9~TBEmZ5bNgi4ZVfRRKxshe1@rG~}@PM8#I3H ztzq{(X+t(r!@Jx1(>H3rnTpvBm_X6K8ky<9ehC`tQ9dVcW8jCKCf|zZc1Y>JakWr| z4j~Ig*w2#E1cjVHkzf@O7NC998f-JM=+Fx?5-o*Pb5)V0Tx0?r@!Wk-CL5>9ClHu4?HHyox;nbYMypIMa=0D1=}x*5fmMc>A(f-64k|>S zGiQ$lSlFPfTWnhg13g=9P&(8L`{TMrqBsK}M%#4&p*jx83g5Nfu%#17ny;XjXa zPyXY^EZ9Ed+(qGVBUz5Kv)4qUFhmTb!i1m-NAQ5$gcr9z$W&~7zyvhs)?hS1Kde~` zm3Xde9T8;THo*_5b5hc#i;n|<9l>8r5eEigI(n6i`MA6~4s(hEfg%jk|BVqcsmY6w zc|&a(i)Wal>*L`kPA^`EmKyT0OCU`_P(@!Y2zTX!qqZcm?p1@g!*3sz0Tk!Za~+mo zVEQ~3MDf%?TPoxfq2=-*SB%(`-`wLD!2-1^r%%ERBz^OloYWeHXhFm-e?G>gq4KMgkjy&Z|P#xnvT>= znpyYl9;H5Hr<$akJU!r#6?F^XxrPrlajZ$JVDHAvvqT!iA2duXp~a;`fhc)qX86Q> z4iW-dn(y{g?|qcVIV8wHv9KZNZLOV)_8VN)c()+zizS>LwcjwikO@x3P)xmuQIBGI zdPh<2SMvJ7Lc_WFg<~|)*b0&%L^}X$VKq(5fW+>7rO7~~nYgPLMsMY8pjbZVxhC8w zOx$IU8wIq9UMYc{h-ar?$Ke-=@2UrD6cCogDac|Rw8VRdwcYz5OEgozHk#g5pIXLh z4eUu^ur%Xbd6;LL^+eOHA{HcBl#LZ8CbH{W>o~gO;2vbGV=>O8{mON}>2d;^@R0fJ zCCLwtWrukPm9Zzez)nIyKm{a)b@O<;lUSFj&9ZZkydP8>!D4lu4S8sd2A7(kh%LVs zu&8VkPrf;yqyo~3VBm}`29rY_DC*F45qhFa{R`M8zeaN$N5<+62ZW#FqaN}Qr6||i zNP+HYFkizm@?9P`+u|ncmqm0`-o#1rFQ|k8{+3>?Yt5p{sRJeqpb2z3O*H?CLSnbT zktRY8xp%4^^x`S?tIY(yg)^?1_G0Xtnjm*bdsT_XTZ*i2ZKeas`@Tds{jnmc8GCbq z)C_f7$Cc-MOJ>snCF(zCng$HN4zlHVLNyaq!fcIj7EQkkWtgiLU3O%c=PohYnGf^E zC^r21KRfFAQ<`FL>R14c@jn|Fc4JLsR*W>CohSn_kxq z-4d8Ve*!2(mkF;_Z<2}# zcA0U180bq}LiR4}arz7JvQ=Hn0Bt@i^APhyv89dS)EB(?_-P6Cf9DU!P<#{vC)r$rHN) zb9qpvY0^T0J!EjS5gdqMta@HqcQ-it3OP~*+i{J11v0(1wBk4?#nc>yP->iu26fC9 zYC1b zoJOJ^>eUi!Ke(gVgIKBctGpnKWGykL&p?=``2s!E0uFgj5n9y(g)0l)jgW3ZBv-H! z@{X-U1Y=YP7%_qFsmH(~@q%f(rgEc-ssVUb9`r2QIMduNsto!h-=L0L*TUfNQs4|w0y!Y$ z*{A!{dCpC!v(uvR$I9#s*W{V}esboGwrMjrop5 z?Fdts8vupRA7&P85PErdi-l%xl1cn3JZE^b`tw8sFAZqU<&DW&+=bgtA5jE+-0%fuj2I9q?Wq>3Xmcce zK#Jrd>L6T!C6dH*yw#5h?zWY|*kS8hdDS5J@_YPr7+X?nQ|77WV`y;HXi8Vie!anU zmnSFub6p`X=hSrIca7IuiLh`~PO~8d)9hnlzj#Ru?%|7%@vKx#g+5#u4vOnPZk#!c zp-O`1h#h>eVsbViEDo7-9_c}>PR&ItQjGAy>f`x1jrVXI>4j6K6bFCs!3z4$g?;SH zGN{|T<9BWg#zkoM$o_I(Js`roD;LK900tvnI5 zU`#(`@pFJ~!HuQ-pzWvHcy|G1x3SH#$gBjq)wGV!qYr}TX4&2=LQhzJ$-$M$SExeq zZcLO%j4L3waim;VT9~v)izjb`+#A;HNbap81SX0o8JKv^3t0oUTsOJK?naKwwq{Lg;KZy%8`D<%wl5o6qKi8`RbQ3$| z9a;4Y`9~m|vGzuO`1Gwh44mb2nnFbzqN;uh8XCHp1>gv#Es>`6Kj8*$J6g$dHnwAu zTA?8NKH^ke2;iuj%gK2ZAM7I|YOutMw#1D=U`dZ1!x!KGNh2+u?7Z1H?r(f(LM-Iw zEuOA4l>ilme`NTx#SFm9q`C}JupsO;77$~zG&c4|ZLVTgbB%RI{SgekIxH2;WxVu1 zVTZ78=~zgu$4K;aLq(;I8bR2HSekfFCEWMwpsHL!{6t>B<`&~pbgN2mY2KaN#i6b!Q1P_p3}3#5FFS#`$dk3!YOQ1<_13}I zHQ;w|oQ`>U;8TA)c?-wmG$-=TXKjE1SAHmSnImU6OvU(zLq*cFTfR&%ov{6_ zyyg69Y2~V@MSpJ^!?(1A6B}?59KsZG?;~@o7j(Q(IcEu>2Q9skiM8FHsYDG?l^$de zfod}4>IXNS#AG&ByMuVB&+qo6UGS(*tC{4MY9kBHePq3Fp-T;Rz}?VqVh)wSV^hPeoGpeKV=>yI zH$wjx`e^Y@EfIN_slNZC2Ug>Z=4X(XF6&s~xA$shimro?P{6xkDnKLR4L+(4#quo zwNs#6CG{RAwqU<(KRV6STM>DzxsCFD)L@(K_{kcjjk39Wiz7~F0B5Shv1HnniUS*@ zqMBZ!S3@r?#3q#ktoQ0Pidwc55uNAK0&_Eaiak3qgtSlcaJ z2Ggjq3M~H0W5ZwFoT4t4XUi$(WbD0Y-6B)X2|Y`+muBd;hDt$gxsJX#2ZL3~`nBl5 zQlzNAT^efC>$5>NSmOXLu^@FrzAP%aW(qKJ9OdLn|DBm;8vBx_xCnYL-dSjByX2xq zL<&U@gN__8mxp+~a{eVq7d2E;sh1o+>w$hDg5=`&)dWGKuHyz_-Z(pR)WscTqGGS$ zZ_DLLjZE+3x~%P7S3jC6CPQ;ms^BD%B;r<>R6OtTz{`%DHA?x# zZ=y$*JVt(7X;x`08oH(jiR}1-5{v9$ z^}0>k8ouxsc?@T^oHnRAs}1d?2^UKFY5GDF`)V>&ziUktfaE42PcPGyzmJXrt+<;7 z6j6g6b~V3TN)60Wl#_9`CI-@utn76q?DG@k;&q9c$^h`2L~|Lx8?NbFGio#3l9n|8 zV2IAOR&@dJ);KgRu(Aw$trGYejb-difsqb@N421gNg0Up#ZEn)XfHC~%geE4S z?|8OL`BOOr))b|W{i3>ilUy0?HMIbeXrsCl%KNuqVokhIW6=F(6wbTipUP>NcBCrD zP*BW*X^$2Rj<^}_?M;N}TSjKhm$M1P7pHcW$sY<6P;Q}ILxYahA`0&u*`{en4H;NF zcGDK4;18?7Eh0rs@ar%2t{tg&r!@XG2ul_=h4xZSFu0YK__w@~&-k|`Nh0!~8p4}V zo5yV_R{Y?cOH$^f0B@QNk>#NIjUQ}j6wd)}uc>M8Qi-44(0ayF{L=9Vrp_oU3-G-G z&4;W_2^R{HSifGL=024aPsSviBO;N_-3ed2je*e!h>G^3Qk+9bt0{)2Y_s!uo6G@F z2@<%uD|KK-!6i4PuZ0;dvCi5k08uV0^PL+n73=Roz zU4PP2(AR>>XfU5k*5$Us+4m?F7Dq1w={L^82Cr2ChLxpQcO1fBvnQI`$RbVKUm2f{WjqhJ2R>BKZBX$&og&Cp40toarcmjg6 zLYrfhMrQlwQn~E6@oIn8tcGzFZ!sDp-g@Wq>`2z6nw-czKn4=IfZL{(@>!Wo zgB>0<4#+O06X!7>r?cw&i@x>&^(W*(K^X=xc8sDdJ03}e_8lda-&4dVN(5ah=Kd66 zX#ygTP&0XWn}&j6iC@jOaNn<_a0j)HQ!mjOWtxL`L#2l5Gli;y%&+BTN6p8~{S=ew z1{fVEkqHf$2>EfKpdz3O)$*R z5qu?T2GdE|y~_Jca#>VAmyhr&R!(iE0 zBIwd)$u{HVF;|ca3^$GY$NnVKF~S zQ~%y0XyE(^U@LoQJf?#|DYEpIVty-S>QMVp=!ZunUIT+?^|Mpp#2QG!@>gtnI7=-a`OE5E>q z59Lt|8gWGj&Y%n8qq(tB&bw0nRUMBZ7Sd#AU!?aOJ?zhIwGiHr`15E0Ag;dQx7=IP zd&oiO3}SX^=5|sGcC|E&82<_bh({`kJU$zZ;sWmu2XDACg+g_}8KYi3kzjc?|_$HSHmE zm}5VTMoa!%CS#Rz(U~Sg?&|}%T;u*f5$JK?YOff8psfZ*+GMX!k2RWU`tn{5m?Kg# zA&TQBmH0-);vg){-MLSCAe0_P$LUYe94IS)ruq!gpuReZTTq92Qkw@dM$hi z;3^6XxXj3EnK4Vjuo!ZB`T%-Y9^fZ4+XO!0K}eH}OI&oF#>~^AtFc74oMqYx#V=Lb zWS7|+{&g(qK+}nIUynp>Kqk;aMqrD``#5j-AVgkm9(J9hS9#vnJ^LX+`4ar+x*;Ci zBvxToCGo%`i^t$QCY_1e?u6BXey^kA@jd&vw`qOOONN2gVJ>)9_3!22U7`ZzqDR&M ztdvurp+AGI2I+Wmkwqfm2Vyf~@(+F|S&O4(eEh4UOU)21aeWko7JBn`r;U3cWH$Uv z=pss%6!}*?&-8_bs>vJ^alS4S)p#{f!io&+OXXN{;(4M6uHdmIZqSCYWh_o7Cs+QA zUf9u!zJZD}yg?6dap!KF;{w)Sh_v5N#pk<(Wp5uC{>WV$k&H9V%$KuN2CIq7*GY0BL{oj7JumP4nPmV4WS)Tp-Z}&)ewfJ2>o45D%rTA@^UydNB583 zE^+#92tNsPoMHy6<&Qh&xqLwnZUvp0m?mrSZT3v`%CPefh!N3uApot&!NLdEy8c?y zd;$}rEyCDz#LvM2Vz_Tz+amL$@6n@d)V;5XK%VGDtzkB^MM_Ki0G~*|r6@%kBgvAS2x7{3B z*fl)x^I!pi%2W+b?HBoki2cM-JhSNJP16U>WM7P~QcteYT-ZnOy(78fl_@!E zuANw3huc{b^-9i$b-L9!?Wh3%AK2qGo4om$%;CS;@?zi)F8lz@{!|z zaT!B){M`W3Rz{8jMpp=yyZsJ^MO|U4^z_4fCZvVQA>m?jpcwi%$_0qr(Cs<40$8MM zgbdmFx*9gVD3Ve&)&|ypS&94}(FIU|ZnF82FmOlxSg+Itax8_yofZ|4L(JCD z4uOlf>xlkEL4U<|x)vq>IawHU_^UX)n#d!2Q`wSfvd~ebIwikr1%%j9b;a2$cm_P_ zN>f)|eDuSoh6h*e;?CaEH{ohbLO)RhSKQQl?5Vl7p8NDu`Z*2_NU^=$0nxu*pcshAs+dd7xnQ^r@yaImvax~ z0?UM#y{G$S<;Cf<_m*(_|6cH=xdKcppg(>4$)7~SU4{y?M(3tex>dQ}o7~X4Ce`G> z5;xC$xsj5_KZ(Xng$xBpSQ>IF2mMBmp=6cznoqVgDj!}2q6ab%&5`qYE`s5rqiQxaG z=*HKnMTH%_B^;-35>?kF`TC45n&w;CriIeEL0_DVbL7mS2%v?d_O0qlLFSem7wYBe ziX!I`-Lm?z9FRfZ2ANZxV7nc>Oy9=d?5)%{&kFjpTOE}LQIxUJ8$*M;Ea_0kPkr6C^v&Q&Eeu5zn0!KQC@2i1BpOkKLgFtF&^}QSpqEoOqwA08jgco3JJh1OH zm{M#q4mTtIW|&pHL*=~w&QKvEQNBmE%sqY{39o zd`t<9MILRR2}Ql>!!;PrxJATzhROSi%hUGn_W%3;{m=jLfBg^r2ULhl#j?)XObVd8 zB5w$#Ni-kxOF4=hC)B;kmT(uvwQ)xwy7J3Ramsi4IAou< zqw(S%dJlM`Z*x~>%u$Smy0t}7Nu_l^ECxe^g}z|sApsRODiF=I-|}~CCzD-B9iGQz zOt^i^u3`t##8c511)SFf7V1*Y0*?JzTOIZHq81Lv41|=G#YjYE=t%bqHG2*k;(2-l z%Zq{sbD5}|F2*fMqyRIQ729>hz(&c;n_T+tm+07O8yPu5%RhN(1*Iae&@KFyU?q$3 zN7WA@SgKITz<||OXQ4b0k#j!nD&K~;h-!~9pOm4iK5Qc^l|hl!9YsVfhFVi123$*> zimkd@{SgQ0p>k1X$RonIWbl&Wv|W2fLv@DQBZ&2*;Jvtsk! z+bUlvX&DZ{1_&PLBGjll*8B9so##aQXD8eiN^QPG&~aNXsH)b=RIt34xt(b40Xp9$b8iMluC(@ti~=y6m)~DoPnM6 zoTPb2%$u(&D=f&N6dIl0KgQ+mIRbZdmZPfx7`l48CmPy_uH`!ke-f4CnW!b`?*;W( ztJZRuJwK0_Gha=mh|ERip8-4}D>UkeS#g_^OhIsivOAxdx8TnlgC*A&-41 z(pR4~-&T=9Y&_dUqY5GG%NB$vWq1F9RWNQ+VFRZyl zaF-6u^Y{ssdUGBo8?pX%_*X{;nGTK(kC_QTby7(m6^rogW6q0$lt6IXN0c)1w=TG) z(I3X2;{?>yu6hg0o3J}VGzKr|Ve1%GYNkUY63C>%x!607RBe#f7R37l0tlV3aCzCM zA|SFjJsrm0WSR=NVz`$ixW}qTb0!7;h2I{!q1OAS0wdk~PqSWa7399df8E;}X}LvG zQO<|lyAnk(XDRrN4D%`a&lLI4Cgq%BG}s%OrXIt~zWjO7=mp6Wgsjliblf#AP@p$c zceIl15=dpmZoYLSp4{spT<472;7vT?aXD|CrnZlNK02-GpG5tsQiCG?qz`Ylc6@^< zwlYpdpX)RF03PLsm>7m1BTu!C3X${LteR=C0Al!?WYPU8emBCC$xjjLVEN|rYq5!~e z{*Y@jWu5mTU2#if1liy{?oG^d|F5J6M5w+%f)cjw7%&!Rj=WL$75B-8U6(Hwd8}9K z`FLoP^n5gGM&qcg$cDMwOROdVfd!kfzZoQ&C2^DF;DJZ8M9j_}*Wd>xuLHu$IodE0 zuuOvlPDO9}u%n>%yf5pBz6kb8G4rVb!u$xE)EyuJQQafju0QIlmVU0jb~WW%Rnx5e z@k5U~iga#SMB+S_Yq<8%!EUgf$LmprnF2w{r+sVi| zdrqZWMx;Uf3#dhqg0Ve{dnwk{Qi3qOO(<%h&56NvHt zH`*LfWx$@Mj`@f$Z+7rt;Bq*S4u?84UM^8msh~soGsv^hR~V?JrlT7Z8uJqLo)ioj zF<;(Pe02L;x!vKpq+z}f_7RqTtGsRi16={^iz^T)p0n64MZi(MS zV~#p+ogoi@IZaezfc)$W-8;yGa|4-p{?lykLspHmFg_PcUu`j^%7Im|1w}sl9f_?n82nbZ17$UB6Na zV7SdLdyc@oBNY#gq5$mJ!?keCjMHP>nV6It0{vKXY8t$AFv=fvMSKNocJfHhV8&aY zG~4$<5`?cYXg1cbw5ZKi9gNxvQCKis>#Z3{5( zXy*7Qa19@@Rb?r%wUb<0ymzL$idE+VT8TLLXf~vZf(&T~=-}JC`MQQT-Pqa>lIDPB zfj0frc6t}4Dz(*5dC`{2UKEb(s;`hbH(U$E5D9&Depct$;XMIChm|=J1#LyHM)L^e1d06OHgpr=&?nc13oix3@7KY6&64IY5BX6bpWaMnK&4 zNx;jw@i6STc3v2VI6=}$z&(J52b#LarOh+N_L@eI<{rgeecfAmN{*9QAOH>^!H1$* zvZSp=9v+0h{npWaW8?$%Qi;8YPNt|o8cz(ZHCwZ>i)fywP!FUIQ_$#Px0L8!wZT(A zarb<7h1dzZ!bv;s&Z(u+ENr;*rCcA7lq&$1hW|*zcaTZw0L5Y*Wg;Ew>!zq&Nq02c zFPrl)3_c)sdRcBTgRDPCI{c;pA{w;Zg6hGB(z#W(R7LC_y&c^AUYnqRu!DQRXEVaJ z2HtLILWc05_M#!{5Uch`a%pW1%d7Sy(3;t05^K&so{HoL%(+zyNwE9sqkjDlJ~7V z1f#z(W9RVQsO`HcxxT)>X;(=C(#`+7dnD9zj~D zD{{&_V>H6BVG4H|`TlvdG#HClhHjB4MU(=Q#X3pHE^`e44V6hp@4Uxdb7QXL&#C}4 zSh`M#a@FN%HKuEfyAsEPmDFJ3Z>UI(x#Pdh6$Ed94c$U6ndi_vX;#u6FCVmZS1O;!~)X)Lg{gG*^qp zb>DijIG7ev`@O&vjEzhJferOj)J2_lC(kTc?KD+cn9In&Dyhg*%X zj&I`mAX*giLg(-bh(Y}xm3cfV!BWt2=85)iNP_;hQT`_AyM16X`BTs_F3LA7m9`HI7dD&nE zJAlW)?w~D&&r4c%J3_9-!1kInxU3p8EcIz4U=`6pO;LcxJY~GM6t&fv$1sqUb9??% zx=WP%bn%6#uRBzH@E|`$fT-#8#$5~$yOv!AG{`db5kz{?$@H!xu1uKk{O)#3)XsE-CGmnCZ48SYq9Cvl@lvl-Sg?0j}r+zD35YZ*`cLyOx8*l?DQhM7o8NGu7(o!tTpeI zezDCXQ`Mx&K$bdasgoR4vI5ObyG=5j-fJeFm%xC}qjGo&{yK6677h?@3roR_3(I%W zX9&q(rqUz2%>cCEc+fFxbkPtrIC1({DR%cW#?kc|b!LmQOaO42pGd01`eTZIdo}KE{G+YA$eTkjr<31Zw&_=TLJ9t;+i)|F5C{amLu&pO6F!t(zlR?3y?ixg@n^ zKY+d>vGlTXHD@Yt!7O{2DvH2DO1?}UpiB*;xw9evm$@dhpkr~+B|E(Vc&H&SbHx!9 z*aZ8XvY6kHF~@>MB0Ld=nJ{{^wv0AgJE(~*&^w{~^WHwlxVk3hQ z7V5?dc)v`bFHL!vdCJ<1B=R=*}?gw_oUH z8ek>taYAqh6MFU@%__>r5=`$R#p=LQE0HbHkP? zl`}Opya}S)lE}k@G#;dTWxQ`S<_Ck_jTM4!rBMObT~#B!b;#z8I%*#8NdQfXCW`-# z?n7jQncB?>yfrf8A$VieSZ!Yv+!daAMYu2R#nrA1+&vD#q@^xgl@A0% z(JqV3leQ-=8rgI5Z|71da~%uXQ4b(!nyEL$(w_%6_@CrCS3|)!dgG+BphUMCI^=Vk zDg2{N)t&zth}PA@o2}!}ns@$Er$ZsX24Phl3B~)unU0x{*G$3-Xq$kceF(Rajn|D+ zv37UlUBEa95?E));bC$j*g>q{eb_R_3lKPVZHiA@F=d{VuHoyZn#pA|!9Vpm)rU0J z9(Av5Z1(D|YV;wWQ_=cO2_BqJ%c=df>@a8%cA2_?x_#W%FQTBLtyyQTF4uld-uTNq zH3lfzcBeW9zAGq7iE_13lhh#Kax+f!b^m-8d1cAvCZ@_DFBmYhB;qLqi_ zkbNe{I{3d4)VOL-Y1od-e{G4iJp!9)6j`I&m`YILUearu0o|nK*M@@7sm)VCw?T?! zSO@XC%qGiWFb952oaL>!$#=>3qU60ALk)K(B^F|h6C2`8E04|nQ$}E>;smlM?N&&7 znMv-y7>wraSY)P#vOb3B%3^&gj>eOnTs*=vFeAledw8;V1>W;`C=|*nJj1z)7iwC! z_R%Ur&>1ma7bIO|T~%hZ2m z2Xxd`nGcOi4ORZEUKBLsTk8V+OT++2YCb^8?n1Ol!^1z=doy`d9umST_h(z^cSZ@Z zgPynuIXr4~9EjN{lWtgSOS>Z-cKfzSC*L~2qx1s!A-eoIRabnjEa{)P%&iGre@_$M z!*t@Hv~be=asAv{TwBO5(=-P;NV`}QZH-fPETa%gX!JRP(3rdEy&ZOj9I$h#ZMx|6C+jZAdg;Wg-lpRJ0> zH2`oAOm|cG6b~(aKIi!D3y5{ISYvGM(7pY1~ z$d4N1-$kpHD^0CLRl94(T{P%@k(H3@D3q%YD1*6WsKu12^AVQOqS6eZ?FX`xDk6{O z#fadBSyCqYfx2=SFEO@4Tw#!-wU14zj`If$}Q=2K+r-^@IYih;o=%spY zt88(DQ)!^Z(9%Gne|ea-h7uEgb}~_mH@2wJ6+7gR9*7 zFHOLupb}JWp@X3%wy>w4Z~yKhx+bELMcXM-izucyiXoNO;8XEoG&bmgWdEb*164Qn zT&7yq!6||kXEz#ky`~Z|xg}g3EMQ!5wPU_)(h}@(jox7A%gbuUgYgFe=g}4l$z+Kh z-&l?ku_V#s^p-stnV&SdwF)?fS_I=9`FcaY@m1jsCvsF?d2QpTC356$E+I=b8F56E zmX$cm=n4B6d*@+K;`R3`c&$eJ7bW#}zlC)=yz9sUrF(-!?^LHjy>jtB)i^U5_dq?e zrS2edVsXwP{w9rup#^94J9 z0AOl=c58ac=L*Znxa7BV%uW}x{YC*w))0gBQKw>~LJ<1ch*Ec~!)i@ezSYDO5+qzq z-%U*4I0~Sj@Y{rjN(E}R7SI?qKlInINg4;H$tq=^Ph@}IG8MKTX@!|h$}_fj%L|s#kEBg( zAZ4qI%XnH(O0sqIG(B6N>wi=0^52=wNmtWaQ#~e-YxsX!?k1eyoskj{r9C15xWZ!8 z6D>1EX_7&DesOt2K^qT7H@)XXl_w+=6&mbzGSST~qbSyAZK`9K0@+1QQ{^U1Gkxl| zPcWg3Kxt$S2o@|CX*R-Tk8G705eQ=QjVkC>zn0Etaifk203M4nPqZfL%X1t(BIFN{ zG-4qpmVG8!n~L2~`nqz90#+kOexHd@$skDH6tzG+Y7^6?j8n|i3l+>LUu}Bs&vHy3fFW_~KG>Hu4jRvbQuN@Y&LOtq8R~5PM4nwtq`0BSpqt=YO~=^vm)j zMIVl)&F|hqyEHWYRZ?9TjFWd576oo4m{$=Wqy{eV$)X$c;RYBGds9S&LHpiTG3hF$ zMF)s(qT(?ZgH<{;2XyC2uIaqp$ByyCKqkIscS?P)7^4nDYt-NG`jD-*gIkNb1ZK|j z{TGlFh^2{>7wfg}IXZ((n{$P++r7YMb<7{NWuHDVGdERdknvoI-}`HfoZjIwh3!&* z-jY}f(=+H==Q6Ed#I*4)NiKQ6zjDsvaW>;Z!6#`ygntKmzZzG*tHKPi5M>VKf#C#7 zY`aHFVGt!mB&+lDyoSh0v|6W}k1a(!`)*gNJt{#Cjiu7*frD?~mpFOL*$qzvyp6oT zTl$+9xWgKAB4&#zz&xHUcylM2n1yE@)fCkiC(l!{rp&(_v#6;g*hC^LYKX$j<=g@> z`LZo8$QgYKuqYW$OogIqe@UQMKu2dZwc$_8aiOQjru~+RJN&4zHXqi)j&R5%jZ^9A zGxv>@fDm+R$IUXr=!qX?B4u_pw*4P>=?IUyC#BRK7`rK+L~)@99QE~p{~^e{O3W*w z9?=uRLCh*u(E!yB*MC`bIoDJ&rfp6CLh7C znIz?DRJ6r`qZSCL?5ASoG}p2f4;Up*HX(r17SKZl`={mar00sJV3T1<=!S!fMbw}d zHre?*nx}^WGuZsYe?M|2Ig<_WSXrWU6n6qaVO63Mu`n%Hqw)he2C})*3aMSlRIJFW zoM?oV6c5*;c{4)C9QHv+*x6Z|7J3S>cp^XRxT7M7^zC8bklC{Iu|yeC;71hi;%$!jfy_N_VPto ztoeGXkhVAw0Dwonh{>tE&fjfL4hYzanyG^z`OBt1#NP|uz!9w9xWc+x0z0Cn_}TOu z)F{gWk2Nq%ez>mb%*VCDApsr^_>=-#AYqOZUVicB} z$n$gs=+gbeTB&^mH{fvS%4a5t#FQ?D|B&(Q_sZ$Lsp+IGy7?n!Zo6L2O^V3se`+U_ zAcAO7M`kRW7y$a6@d8keB*%5{(f|WtPZPR%u4X1W8zjB7;xSo|!?u;GW~<)sMA9le z?eX}9I#L^oP#g%%* zH7x|aMJ^iMdD;d#2gWrKe9tSe_Iel%$2f3T>bNA^+}d{WOoDIfZFi4MP{$`_=_#m= zVB;2Vd~E6v9A!B#nWDj+5ce5@fA4YJXR^DuD9{y0cQJSo^6_Qub2Zb%IOjx_XxwfG z=!}yi+5tYHULgN2B=`!#DOTBL``}~5YpPt#xlLhwP!Of+Nc{%x66N!Ri(r}Smt8R_ zN`;S@J8FqX0cC{WiHpzZM4=0YKb)-uU~wnZOcu4J9hE2^NYw^ZU<7s(WkjB)Kgmry z0fVI(O77$JFHX40-ixZE34_g!J5)iX)=*IKKc&*G$a>W)I5({Sf^K3E-&dPyCrrJv zVUtubGOil0S#p#lA@BvVQyq)QTs04)hbum@SjX02c-n2uCU zru5tr8TF2DWVej`O^Bq2_uK1@@i6`kDDG+SrZOQH7w8Xz{fXlW^pW*2hlN0ttyi6` zBc$crFz~{t(B(yU8Diw-u2LZ_jQ$|Llcb{|W`e89{GzvVY>+@7^$Z~bh5TS~%WC{+ ze~_rTjx>No{dER4LF?5ta8ck!if{5T#?fa5qk&ahGp^{IB_jVk;oSP&g;hjWht64y zAy%#9x}C8lCTMAxLKoz!3RQIH9T6{n;A?H}E%=ooA=YYNi~LMGqE1o&Bi~<+7!|S{ zDxr?<1mr~}6j#n3D&C8laxRxt z_9t;tDwb+#46!qKMQ|O9{{EBehbcn*qb<1^S*XAUX+A z9np$*A6dOWe^hC;%-CnLHZ55l(fY@5NnO$u+JccAL_j6Xor@3Rp*?f|Id&bG{h&0l zEkSsm8os2zl7?=#(uh0kE|E1`ZDJbAp91=9T}z%h6;njURBMTch|rQ$A-!XqM9SPx zMhkQBtajzU40G!=x|V>W2B=U!;pF*0x=hsF{~ye%GjUo27tI0i7tLJUAXn?uExjLb zdmAKxia?JWM7zxp7n+DUO!v}@Q9bHqyp{8at+HrijI`4HTvey)O+B)Q_@;l6?l{Vn zwGs;96Vz4bXpTjU5rHtbxxk2vOry~^1Nt5O!y1{8xMZPI`fqssxouMY8*NfG> z|3dH_E7O8@^B89~jyf_U!TO{m@+u4g$U@Uw>r7K`*fOP&oIfiRaEd37ElVP7Bqfg$ zaA7SRio0rL>#UFOFCrlFqA`gG(3C>-#%EcK`5z*PDH^JH5_D?9&J!Ie_8&zrx)0aC zOGc3k;ZLpIm{)6&wSIYBzk?xBcyFYxiG+|^hjxyZXaQ5?fzVFnXS&!7ayT@9X$~A% zv8Y(X>1=~la9zVEKF;!+%cR4GnJfnqD1r1(l_jNc%eAyM8-SVQyMI`xz@WUE&K@@? zk1{_M#KD9+AouHwYHj}Vy;urknn_9|E9eWM+F>j3geJ~Dc4Jm*BnF2q(I@_sqb8{s z_>DN};Y$I`HnW`$hs@y6OjXr!XQjz)!rRb+OEQBL=bAYY76~d!h+L>Wm31x@Ra)Q? zwL@5(^YkJrA2UhNL%kf2s4`NBAx?ZN zWB{IDe6kV;ug1I6w!{@^g{255gHwQ*p~4zH(eh_^2m?{o1&6{2tFX%{*?rMjKH=Rh z5=p;z+Zvw!3o}D}&t*KE;>c<;_Loh#=#W(j1|;4BX7`-LrR7&prkEG;DSNQMv`#ah zK%S@j&ha2~d|3`fh77%V`rG8fK;-GzY@}^6NJOBg0HT&q_Eu56k>QMC)gpFw0k;?h_wG4jffR-s6#~C66xV zWa*HCw|$u^fJDL_04F{g!scn`sW9bYM51qtbG4n*C+#qa5;_m<6hj)676RMa%vDFM z@nk^o>G{|L)`gFV!%%EZ__f-?R32CeCbWG{jYDJxbiTPPm&7ZyT)+`CPf1y_a!uyB zK#2hyE%KUSxCfPv$!Lj3 zH;CP^ z2R>D~CfOFHou+uLT=709(Q_thV+TDl!~C46`@v<0-=R0QfoRVGTz z>AyMhPzB)s$hCZy*tW)-ia#9rv55_+Wr_^~`Xs8@qS~`F&eamt1uj(WfghnonJ}to zACAdkj8Yrr%c--eR3;s$4J~2E5|LT;r|CpViX^SjMcqrf)#}q7oL=m7{O1o7C9A`%;m%_rzG@aS)>-tzDymAOu)ba?DQkvt z9+U^Pwj+lymaTp z<5Zx{C7_Rk;BB@VbjIVt4pSN2c)aiQrBK?JRU!-toAb;lLZprDqWA8z>LETSa$nvu z6~8YqOX4`a&ZuZwq3)C$urO9G^>^b;K-+ZgvGOs~PC&W_Nk_Cxp2mOd+2f01b7D1L z8M$SsxOA!hDmEa-PVLW)d#;IaQeNiHmB%9Tx{J0#FKWs6Uo-**-Hq9yvJi>Aw+6dQ zj~NU|n!qM6PfeJiRMHP7`Q_WU*)8tZK8?}sOk)SvhMrAcXUs4O!9#4 zouzsG<;(8>ysV$r!!^|D0L+jsxO8_t+8N%a%)u!&gK*_Oa=^4Dg5pIkJ|2@dnc(fc zMreO4sO$^1wKNNTw9R;DMv=pE`mat5y*<(u9c^%*p*2B+2`m&SG{5qV*g%z92ai+| z-RDF%;yjTIHS}(SZ9R5wZj0Wc2scqGxi9gG@B!*0s>09Tb=XjH*81Eak^CK_%n33E z1%Z@hjymK-?2-rYH_(Cx=qZxh`U7%r9hhHxSDgwsKTK>mIm)6e4lo8if=^qmxK+oG z+EvjP5>xstwX&avKJ(~R-}Y>iz^O@5uKqmVGJ5GpnS}H>?}YN2@Ki>i(LZ`l#W9l< zlH-^el5!lS3_7aD{K%2hbE>*4+So?zPbBuA{)E|u)6 z3QbT4uv<-*VARzTAlpZ<_74JS{-$qTk(SH}`=Lt{0q^mv97Xueio!B~${E9r&d5d0 zN=DBC607{+Ig+<(uSZqf>x`(zi13U2sqwTvW#(p!2r9$R)t}fbA0`Dk?Nl3Q;-2g@ zs_IOP3;h?(ib$!gW!E&cm`75WUQe>5RgtH>S8ucvGjjoJFveeX#pS!~k?bjEV&s$V zt`8sSsX_N!pO{lnkhd9)*vB1U09eEToaL<{`1D5|O|H*1@*L4qiMuM;tBbSy!&Lc} z2?o6lU?e+beZ|YM`z0cWS~X58>e|%s_VtJvWu`$yP{26k4dFF4Yz?MdE-S%0fX$i@ zI^App>qKR^LGOu=8qak4Vio(Ud!cfqLO=?Jic2Swhs>_(YTWz}dOH~~E-miXHNchW zh=B2U!w$^{{4px=*a_4fq`?I-zCL|ut4?$WEqD7qK*lznloED`e3lX9QFk4YZdf@G z&@p~#yQ0X`_DJ*#q&)lMP>Sf^j9aShona~oi44zlH~q*>btb70_Ke1A*oef-^LH5p zv#Sy7_?jqOxqz)f%gb;`J{oXunW$U$!fncqKDKZd0zDEs`h*W~K9Ndr>^iG6v!O8? zkF=~tgFG79PJNHX1QNjiKsO7MY}7;0LrsD=P_y!WHZtQ$g{y{_1+KuKsz?h9pAx&o znwd~()$KqF)~X}Wh{od%a|`Bxfg5oEToxO?i69uNCtkEQzl^+)ppdwG8Q6 zX_SGwqg^ZVjAV~s5gyUrH0jm+G}E{FBHAP4<>*xHX7?HtF`kjxSPopnnS`*@#k`YY@ zT+reWMh@UUbpzJ12mb<6g4MbP!r}HErUCc|JDME8H%<7V#yhg3eR@>s%@ybDin18- z{q~(Gfvcme?(~z4auM76yJ`_2(eMMc)d7>o`$-M%3|nu~bpF7POw_aq;q8#&>_>ye zFY{2hpiA{~L3}b{d>-3t2;Y_Z zLI|K6b~XD2z}I9+H0BisHeH&En2Td$0b;aFyUE_JJxg<% zx6!xcMLmm=FH-4DF|#pM5O6SLm2Z2q%H=mgLUlLtz*Ol^KSZa$@`zg;og=vm9Wy;H zOT6WKK2KL2S~s=ovg_@J$ph}ZhEHB^o@s6vS}5z0ri|&QF;FmikQqeD?)`VBo$DO7 z(~Mc~M;afb3$cpoI1_P0-6aIR90QK8*0Bapz}(BWWqqQlvf`aW!LFr9$rRm5tz=E0 zZQwVCgdk$XFiC{Gj+Qa~V~61}VWbKJvM|tf_JhaT@K_H@$OZ)aoQZ|{<71hh8vecm zCx5ypTm#2wG|F_DEX=#0<*e~{5G=UDun|E9BABgpX5Y4R2w{`Kn5^U;bWJ5;kCBE~ zFWm3nlr+=NXm0&Dz8JJL#Q1@w+l&WW>Pv;!4B{h8o8r=uSd#3VE#W0bq90_s-8T<#!vm^sk9M2lE@Cu*(L<49BqJ>bOVbmkfM{UQkZ4vzUnnO}?Jvq9NOravVHXr0S zd{_%|f7}xb?2b*#*39jXRO6TnB}B{~dXop^7di0z zOmUYunWHtZW6@jWNt75XCcvtfNS@!-Ho&YtNvwuXZ0{`-{r?-RA|s!$bnasDPR9|B zL}XN*rW{k?v&F)>QoG8Nk)PRSsuw{}6q>x{(0?T0k-vJjA$n-#LN}PuW-%xzC^f#u z=5PKax=Wo3wbP#I()ry0wODrK@sTvde&R9y)azZ+#H*JBBPNhWJjN15D%Dn6Iyw&| zS0XiBt15)uV;MllLl@r^bgoUUQv-8#-rTB@C!t+=K$onkhwVq40|ez|wR-;qafBBa zC${BTE?8Zl4WnRr?>tzwNHAy983$@VHN=Z!4|*Zn8j4ED1 z=dm$Z><;{iltFxTM|dRUVNNk_`XtVuBeb#pz^cGfd&1>JcumDwO4b zcA#s@ZWgGbF-e4LVxx}EVYdJONqf`eMz$nNGiwzsQj*f??j)UqbASz)+&x}p=dapD z2>==a5@Q13Ks5#dJu(YJSL*oFw3~Q4yCQ_|=w1W|d ziq`izD2lxD+@`csX9ADg4`Q?(fk7PK$zfNbnP`v-%447$Nsx1$G3Xn)IF=YODOyK3 zY~h4!kSm8erKd@}EXGFxGIPn;lY;^BOtrvvekbMxd)XM<-JbsVK2phFaA__<+1eVE z;20AD6a&^f&Vu>5viJ}=oIF8bqte$@VedIk7v766kjf(N9TCqtegM=^n=l?=v? z!^PGf{PTgWFPX9ghnVkYL?WI6O>Ov3nE-~j7f!umq3f&-tYVlnDSss9pPFoN3Wh{; zCzDJRA@C|SAXTcioeots)KiZ%C`mJdhzXIaOeEc>)YR00r)~kK`Q^_@rn#z2r{JW- z3qUy=k}1!T*qVsW8Q~c~SV1}2O`bdqhE-{_qjQ`0VOIeeGlYBWkQ^r()rF#ZMaLC}f2JpKzJEKJ>C#LkG9Iqt~Z(M(&PZrJ&pKDAt zV`>)r$iCu{I;a7%PnRW8nv~ss5ruA~J z`4FO}D`}M{@Oj0yR9iYp|Dz?wV(3@WlFgRCG_?Q~XDN>GLTb@s`(8LHQVlyHK~g8y zJ%mvHuJRy*wivJ9_>kIp5$X3p)5JJ>hM$=l*!j8Dgo(1ms!jsAwoqc+q3_Jw**BAT z?Ty+=_!WA8d}{S<;=4_m z3{oGM!~nxX%xNX1nBhj@o)aN4`4Ir1*^tVOni&$_%>elRW)iz~)3K$wYxD1dxqnX- zol_*K*jv;MJ`9CSmaM5DZaWXNRkzql>s zM$-Qb6GSlEZ#^(13qzH5yJo({HYRNP)~N;{{8HEj#6;{|LgiEkn(SY$4$gn$Dq>!I z7lG(g>ISH@3$(y4lqz1_nb;l=+Zt0t7c7D)pdn5XUImaS^aP46n1e%10<#*7RQ1wB zNnSD<3Jz_WI*O-+Z;!KkgGcIk(Gb*xI$jG>XsFx3HDp3v`^z677%8BU-LB%x&@J`% zNNdrO5Pomh!|#=5xzfHf-a;>6LS0(2ifuxvg;n~c35tHXBvvbYU|tdxZN8293C(S@ zO%J5-*P2YE=tCchZg(Oq5PwS7WZ0-O5=<>12^NZeWs*E&ee#B}uO6Ffdk)e&bFugbyq(bHC>DjELsv&Xm@zktv5{#H^i zadk`X+pJ*ZwHArWcvpa~65m%{&X5GZYSEwtsj0Z2X^C*^aOQ;L3`Txvo~aq}2gjWV z$?Oo&PlpBVbZ$tjX5|7DNCHm*;6X5orm#p3KO`uO&f3zki2?LajUnr;klGfIHLo^3@uL(URKwIzDngxf2jrkNt0yR zq4H5JO_d>y?DEPG52pNa7V%QPv>vr|?(7{=7CM0U8!2bl8HkkNb8+eZOLawd?UJb5 ze!~PyLonbZlY$B+{D5Iq7xS!h%~tv*3FEMs%8Q-pU($z?Nu%{+x3cc?45P z2<-jWZmxKy)W{oYLdTLO zt;%rH;M?m-L^;%R8+(H5Ro~Cz-Wqv`km~%r`k1(w=yZWhTGOVm4+8z@#Vj(hXKs}q zSZy4hkn)4bSQrm8^5oTZKIz#Ig}nG)vTmae-1L-D9Z58~e*2C#`IpD!T~&wJ6?| z={Q#qNpswQyemito55x}Kt$B20RA7CXCmz}<3JnTfq3^Fw;7%?uSV-7pBGH6Lb>~sCc{!^4H^Ms> zv1t~Y>uza=u@-Iuja3Q}qXN3h#J~iD#-5~VWETB2){!trH|`=3Q{92U!YwwdgvAvC(6j3&{mcN3dOCYF?H@mG|kC3{NyP?+!f5jS)KgunB_dsz8Go|Fu+UdA$aKzO=DfefG zOi8|k-gY#ES^8p0qt z*N3Tjx=n;IU4k3z*d02few2ZU)NDV@ao(FjY^HPmMTmGbl7E6q0DO-$22mBaomtyF zONkT4*`sn;VmA$qb~bumH43zcEnTGZoOwq&FFQZyk9&C`3Ymak_se%Oc)Vw`>sFX_ z3gmdHOj-d#JEB7#UyPPY=dkMGHa$HvN6`yK&#ZByCw27!(z;feZTMVBIk9NhJeGWl z>-3y4IWR!38T7TrXdWT2WXFDody|%^<200-m=rr%HCQ*-aJ=MJ8|W5&`Ke8DX>}+& zjhEz)3P6MeMKqbF?JduDC7%3JLLjiA*`ko1uWO{&^xiVrCqU6fi}CP<3GdIUAW21iz$-Jv5){o2!*?Z9;iFffS47peUd# z2$hjsJi2j(b+5{dOQ(v-$U8;3@GJMmx$Mhnm-rj482UI35UPY^6S&*53zP~d)FLW_ zxx%8<0WVl>(aKLDlG6e1sA|B2)t&CDkdGKB38bnlA}W#OS+_`-dN51}oDUNpeBziA zEHJEAL?|(qJf>&b(d-j)XEuOvNpXb?2vxA?vW~2e#!3^;f|^?-NQ*3M(NSpqBf}_Vq2Z`F7bNYVNuM9UT8go6fa znWZ?;WKsfTDk7PT7u4}~pdbAC@CtFE#a#FWCBwqfzb#QK*WMM5Nyz+Kl0wZC#dSIR z1Cts%>+^6_AxxWKms}l)J5mSdTAe7$fNClT%rTh`d+X_U;@q~#reBL8U$#3P;TP{M z60x`NN9ah+qHxO6ch3&A8LY!mgDPxHA-_t+qbR8{H9LM|s5#NvdWbp+*# zA8G>+?_0(Ac(W-GM$$$8S5&&?!{b6D$Tgrl{9?VrUhG%5C8CfzA<7HYZ1V18hH4&8yMiq z3QzMfd;Go6{}1E`k#a^`=uxU4v{{2+d0%}OLg+sFZlWp9KhSjIhgB`!)Al_ksEfWD z&&a1mK`7`(S~$!z;2sj;>Ai$}vSR9mOhon<>%)9Lp=XA$Bh(+ z#9JL<1|a#fRxnc5?l=9Cq-xfaw3kjW_t}m=);3Wq*&PS<5AhtdFL&_5F9N$xWb}XU zX%ffw@!?^BGqvYq9B9LXH)s#>vZw-p1ud3C5>r-zbN89Xiky`wG*=q>G@|S51~M9o zuxtO6z-g+H3cZfdCCEu^qg_&gDz*yY_aSapuNA$9LKSvnAnk=}IG#5B2oVj!ys2t( z`|?TzlG(Q3bmhe5SS}}Ay7Gq(K{=SYVn;w9!nvV5V+la%H=?od#MED)5`G{UO5>vG z8$BeLw;#m6+|pg1Ap#(_YG((ZcGEwcN_Al z$tnLE9m+KQdd+mv6;uD3DnAJ|^RD!v2o2@M=7^jcTW>odb~bRJVvR`$hj*@%ifGh{ zfJQLlf}#n2`~d7UUGUR=^mTD(mc?qJnmNp2E=}vdPssu`mVnjeRzWP3?!UEqFs53}vmaPx}4Mm1CzW3LYa?2M7l zUbFL5*yIgRRf(<50U6=IAje>f7Fc8GW9oE5w~AbV`}m6zZB4T^id~d<SV~^5D5jKRp`HAoNHP!&nxl7zC>$Wnzz@Ir=V>qE>W2Yc%P1x9( z+G|PLh;~Ie!XqlvKK_yRpUk)>IeYk7>_g~0azTEY`JlDN$WU-Iwk@I8I{`|#sx!z{ zlK7QtNiEfLSIkl=&ffg-o|un2;iq=GQEiOW1({ND$9H2;^unm0;uECbz?a$l!OFY7 zrxOmMLKZSLv@um0h!5oJBSp=zX|Xz@tWQ2;fuZ>Y#M=z zHY(fK@I#lu3{%{pr}-#ntU)dNBidsHQ-B!&w~DYa3Eyh|h6aIqsKAko&-*JO&ORk7mFW zFj*}0wOMBH3=FfcHqd@0)|L~eR?9(KKN@}9s-y>nhM{FGHWU!1poK43(+_Hc+~9h$g+10LT>LPHbpZD0zN1`k&) z$_H9y(rv%s7CY(;ryvw*xz2=^7-HfVSXHXVtdDI{*`2Ti#z~4-C9fZAxfymfhUTwS z0jGFB66q3QLO!99>nW^`{ENwbY6UN8fsOxqWK!p6_(*|^?_?5I-mh5g*L29;p|^uO zU4<2+aeZ%N86^aCjR-SRi~*x{KC<_eO12p~c@BP0Ln#?J@73gRm@Z77?#a}-MTT|o z_BKW=2}_7o!kap5+M<8rxOb?Dv-R3;ZU`jE?af-xTb z4eT9-j=vwZa37ePBq@9FN9hZac$-UyX>;r64& zV7PyAE1#yAl5iP=3FiDUS}nxR%MFn@N>^dS#$+4LNH3)gqT;mSnM8g_PMc+Ebvo!W zF9g0e=#uY9OZ$@K1_WU!f=@iP}G$F~WAp{r0X4&UcDhfKPKKVf`FU`{M2J|PnX+(ffn{3RdG$;mn^tQP~ zC`dZA>B6z4XhxkfhAWLszWtVBC|hkDQ~8((jQZM^fZvt2ThVs0E(xG0q*fPp>ZYnJiyff9+HB!JiUMn~=r}nf);f0S6daUwnv7@3tW* zdhG?o2OFe!d@|Z^xtjC{PViJ#ncA^%Yo1N*N7r;!8l>_*(Tz8oUfiQ8bAK2i=EIh9 zlW&HRK8s`HTOleWY%a1BL@5?aH&I$C5*?%j%zK|K>^Qy=wR%C-w?b|TXv;1(bjAA> zVWPr`gQy+$mZ&VZgee>G2AO$m?;6P{TCN|0ORPR-N88s+A1{Ar($*$ZaRP*~R#rY_ zHQ3mZe(H+xr1H7LKlBF1a8tMyO$2%*SIUkQR;xz`FJO)-d{uj6CajQpLpWqIIzO{G_^nJ zC?Y!HtLe~~GAGQNIFEBtTS^1bs2q}L4mqt(#4;GkSAj_61B=%nzSWiWC4UeH=|ccS z7C^(_Du-g!7Im{ny5^d8?Xbg`F zF@u%AIfHUJ3CutFNXa+{i>k4sxtaTZnuyBtz-044O!c0xc-ZqyL!ruHFOQ7EsCp(U z$3h6Hp5`7hYvk~f2XfqzGzT>zTL3L~l!eNY(%15IYfXw=myWVi-EGIDYJ&~~|^ zoI~$JQ_fG3YD8!&RacO(HHI10*z{8Wmq|FJeAq>lHbBBD3;}BJt5Pjqs?>Yr#s(~Mhdp81z zYAL{dC>GnNbR}c05MrbU0_0wtB;^9+6Oca@%_M`sVW#ofpy70*sZpP61wq<#_VK4Q%sy51Bumub z7w4wkeA|WyI0S2#oK765KvDtwOuB#%BA+cuCUt8vYpXl=9{B#)yPC>kU&b1qaJ^Q8T40Hf(pmE#>Ky(&|({gI3Jqn zCggDcg)Oa^U{ZT_fnXP8b1hv!Wso(o^d=Jn`K}APNkh6~pcul3t+5woMgrCnHHJe59USs^l-guC#) z!&#$eh_5r!ljoGJTADC(r9%nYD8v6j^F3a3Xa48}zy@4ZrJ3CtwhC_M zouXXM@^(w(Urnd~yer)O2QdUxFE&s4Rs>JUWlztg!HZO#j)xqpFY9s~qMJ6&b_8wf z1%2t!Q9jdTsB>h~dqIPA5v^>CE&TkC2O$tuT)pmpbX!zBX_e446SVzEFi9`@!)88$ z*_UkUnK^19KhDCKWXfS4nJsvCvDqgOTL5gJ$CS`V*n)Kh%KotUa#QRMW#}ruc5L@F zD~CaIbe-L3c%>={oY4ZkSo5u)3!E_2Sv7J@(zI8nGT~BzgVrmg*97siN-0Dx=~(W0 z?7=5rC#uMPqw$JGE!!84FF#Yq^?{QvNJ?lgpNKPthRct}wO_=w`S0y8_^;b&>9 zvaimuG4LyY!10JXRycW@ACz?QV-Fy%&wI*x__-iVYQVwC81XBjk}@DUHZ;9On z|Hr@lw|~*U&~p+05#EWiaNG_5IYRSEu@s125EoKe1^Tc^G{6W=MCU<&sqCpP$#3!M zEjf>TSPpR!fj7*Ocv5QC(ix5t=7q0cJ{LaN+xGTOH6;E8U0y^I1_F{gkUP%~%*s9Q(Vf z*G$jAA$*o6tc8aJ#lhLffRf76*2xx^nnWT`9@6nUkBRv!afL(YvlZHw6wXuwHhgm0;V zt1TLhJ`EGElX#Q#>8TXJW#~%&!0}?V|D2K*xP#ewkvwzNB#%t#w_!4#xF zD7@i&-&?1uDyU0ku;m2mJU00|@qT%%oSal-@57ij{BwZe8bp4lGvK^gnUh2SKkFjQ zV0>CpdN=K@#wrvxnyV|D51Sc<^_|(Q-T9gIvBNcoK~fH99s^)UN_8)(vQcQoD*qfc z8E4T9XlpucG|FX0XH`!=SK{~xKvz;R_7+%}r=XNy7Kk08eulT}s(NemtxRVTvq`)6Rgt%@p9(@5{JY7!{LiwyI=khCsu;eXv} zCR3$xLI^SJ3#ID*js6D3Qr?y#4jybbMy{j#_Rt>MbQzhza`FrI%-|`Vh z@`iE%ow>+Dz(TiH%Q8c`ffy?9vsB3YC}0uN z2m@+xZ^>S}zEoCJE7#{mMZreyC( zWjTI8rmOhE)afv+F3J3_Vi#&;FGjSAVHP$Zti_tZNf&FPJeHC3g3=5ReW7tr!{^b| z%#VuBzDw28nx7_Wv%P;N+ZQ#m;@aA;<^gYj4o$FZ_YA=}i@r}8ehiaq7}O!Jn>3Ug zNYhpjjRM{PT(AC3%Q%JP&JaT?OxD+_6dA&1fS$qG`C-k<6FN+AU2Ibb<`~#mdbBEm z5uWL<7xKyacouS=PT5K#CVux(^U7=)<1G|YR=(2IC)GDaP@;$F9?=Hh*HhINs;YrE z#^vSfn+}O)aow8VVq4ahdKq5W`rHfWt95VsB*vV&2{O(+!-Lz!CKO9ESAOr=m!^8Gjp#w2*ag% z4qZt&eA&9{s`;6*O}=&wv6_Q-N$naep$S2)i#H?G)w;viRH11Lq<>#I+O4Q{Xc+I}ZnkH@yI6WUk49viB?$Rj|EhOziA2F&Pr@D$0(-gXfn?wXC{Xy5U z6oS0pO&9h;3ZDc((Kj4`?g)P;YyLYO-LgT6T5ZK_M=8gQ?eY78kgKw)!G4mZE=QJf zP%2w8&SVjOb@{?wm0!M5_16X=@7yLz-m;7)3{+-i#uxF&kkhWb*{NWRvKFUBI)41l zC>4{1VheVmtv+JYdJ=1H4F8GFjpH7BK;fT25U~VbEeYRipN7;l}D~Q*i7Zy zqU#v1PtwEe=MYSdd9>L-Sz7!N;QSUc^F>u*HRxuDQtcZ4(|( zV4M6sx#(f4*t#c~gb6GM2j4dQ5c~AE9EJFIhxFph z@=cnce~Vx@G>RH;!D@f<^<^Ao>v0%N4N^%CwV`^gKp%#*gzvh#bw$FF&e+5YkdW{jLcrR1r$t1WmkXOcB`eD{YF$8P~>Ik2PJ-|p(#uQvBs0(d1patT7LxY2>T*mz! zDPKD|kEX4GGowX+1CcN0E8&J7FVKEZEF0CAFCfyn0W)=r#sH5$tTlKuIz#}X!CG(2 z=}s_*SYY|wDpFO0jO?$R!Zo1{NX`E61>=xKCXuuo9Pf6ib^|jc!PTFU-Ks8q#Z{A} zm^6_i8r2+X+~UPfZT*JnS5#`fT-iyc5M03@&mh$9qXZOu_Dv=Mr(61VXBcWAVgV$0 z2)eqqr8M4Qod1%jVk8Q|d$Dx1?YgMCKVl*t3xw}^Ei({8-B>9m5gMmPL^TUSdh@^U z)+of*Yx+-2;K6^s0yeIf_i|7EL~&t*DsITAchjnru*M?THFiGp3W`e<&@Wj6wVma` z8mrDT0}<0CS68VFpCU^ACQR3+wd~W~SFc9w?$7Aj5d?ge&~QQ=39Ljw$#ztn#57fB z3ix|gPO`1R5+;de4TEn|JT084C1m3oke{i9EEv%qqFk0)*597jMEzfVdXok3F4jvk zBs$>r)6hqW#K&ldM$cic*}3ij*Vz}=S_&vXQT7r}LP$q_yfMB#u|srD_zMdNdOV9g zc^(2{5;z>(En1$o{EWoZAoRkqGl~nB8CcOtUf2>H{7v>J(G)s~K&quG~q z&f`NABNy2Lm$Vi)3EgMbhx*ObkhGg5(58mTZ9YtSfY71oGEIPrl{fxwD}y*~D(g*VIhuX>%K*U7w}B!bYM$HHi$>oXsrM-md;6QKb4)5_U&WlYPN`6lxk7OXv;|8oNsV zUgiK+koL#_!`6(GnA?;$b?@vM2&_h=9zC*YF$iYiLHES*stGIA3F(}4{fUdm=egrV zg|?$Gng$pFq3-A%dbvUFzT452{HUhPLDOYf$?A5wOL#ka6Ib$8L2y&N2{PGX?%`-{ z)*rVtjR@|@XWp~gI2L0OargKS6A)9?Rlaqw+4qn45^45CH?vHsQzk~aSrw-HHV9ha z%Ht01gzxS6L>%iVcAJaxk}}(xP?oC)skcOQBH8@5BMvOm%TPzLw!$UC1PPP~xRK19 zI+V5cFAc-!b;%V_tTb?dr}wNNLYmkD#<;`H#*vm8ROq;jauEzJgy#qw-jU}0mslsA z>E?L(C#&c9_GV!z%nzy-oNM;ZwRVSozDPDK7SJGbmhQiBr;2C}Axd_kr{D;aSQ-S_J(z76ab8qMxXR9=4>}-GaR;HpZr)kcKgmx?3>V zMyrO0>5HDTlcrlNZyDNa5P@uccn4@jXGxK(!OagD@k=xs*P;IB30{2iy0G=r%{G4ml{2&PnkLgWuC}UT(09a z#eXuHV$dgL=^vv;-Q2OrWhI+ZnuF#lo^Z+HvaI3$h{F69?PXjX5ob;Hlq0eK?+57+ z9W}&cDxF~mPI5vH4F5=60gq3i)0UM)_DtAO$uF}#F!L1o`dRv>i?0mi z0d>u(7(xaZNB-s|*9aFh3Lt+-Sfi{$)g`LEsnhpF!AZJF=7?%9Diz{FVHDQv74gUk z$RUi2FBE@0H)@tU=C8aYXTwPZ#gCJL&D=K9CJFC5=b`FR^v@W#$3}kd0Orz($Q0W^ zYeGKBq>t4hE2EU*igl(L<<=_Z{0v#U>kF2KYG*AO@n@P^i{!1WCM};zt_=kax+PA! z4c$_9Cjv8oW9gL+0q;>N9QP#QBCR2j`$<+EMj7Gbz7s&wzN0}DH=}poNhJf7REZ?} zt&eOMjs&>6^R7BmXx+~7&#&}$2tJ5oJ-w;v-=EOrv!MrXQr5waI|Yi&keUP|y+>Qi z(^wx;b!GH!Qnp`YV@Y}JDoKDRDqkJk`|MAyLK5S`bvZ50D1XUX0E0W!bK^A7u+iTw zob0M$ph3iR(G`um)lrRd7pF+~SdR&m2%SR^A*#<`Hq`qVkA-eSvcTZ|>H;}_YKMcM z)d=U9w?zX#&!H{}*^Oz#bQI2A5SV}`KUJVe44CNGvdXeKE6v|2U&Vm3- zW)GUes2Q(hS}|3!k{X*=W|(2q+8N#|P{ld& zyMsiFX2^;PmN=R6XI*$3Ep0I?N5LcA=FarQyUjVCcqs_P!VO7FHSB{%`yJpJL%t}A$;VUR6#?Y&b94|p6TKPj zhq_2DVs>aRT#zu>F-$a+DsRi^l5!NdfCghF86K_nN@hw}!vyRChdFtDd)vwHN#BS} zjVb4Yoxh5l>q%EUeLx6QLrQ76Alg|Zf`l$;2-7jMv&p#;THr}Kq$vc1+}+P@43tj> zi8`=;N`!SL3~{vtIG3$yWXclpa?d~=umLY3pBplCY%e>bTs6K^pceFH*T5&38c&c2 zIfjp232!yIRg1Pm@Xg2+gBffi@wl>L5{-#mwZvK+h=-nSGr0$qwJW;H1Wy1zD4y+8 zHB>DR;Du4DUXGKVEJZBr6?^B}(;b)+Fjm&dbqD@1@3>?8;E4mE)GSr|rU2tn0llh0 zQt329&f0B$dl%{!9XI zq{q6DrLLH9%YXrD3U@<>*okaL@VtZ>RQ9rY5{7wCV99r8z%vn+20re)AD~>>2zSy1 z8A3?%grPb((F=hgpNg+Vf zfelCTe{`BF_h?ZgOGOxk)GY}}F`mM&nabpATpnJ@C~kVo20_<6lkL3HE6mMu>hA)a z#B7jR$)HdEI9tvMZRvgG$ock%fCxLQ`BygYhfjqtqn-$QUCgWuph3+Nv}o|?v$kaP znYXNxb3}=QcuqdVRAEYYMTk~d7Kk%arDBU&Df z$aUUW&0B#jxENG7Dl9_xw9?1l&*Vuhhu(==ASiAV>-Cz*Bdy=g&=E=cy(7`gsmn!*!i(Hi1*XqGCk8_jje(#YFe98I^$%gWnzckezj?wfJC(fJ+q#SyKKBgT^(N@faq_2HZekJgH1O(^x)r5R928f4Fwk zuS)GbLSY#r#bE>ecN>KR(T96{0Lg$8Tl$KBe4w?v?q^nJ9?`1oeG!IksD1f2k&{i` zgole8Kx!)CZ|FG#n5LZ})`3BMF#3?1D|KfBzCV4p6Wk}1Ra2_*dn_EZi&!K0y0QB6 zO&2ZCC!89n&hzZXf=X{-n~x0<{hfm~7GE^w6jP%;Rx?DCz`%78=3U6tl9C4_^+)rW z5NZNdB{Wc@KD+a-kmRuAoOVPo`i6S>M+xotJk!6b_DK`-lW1=kV$(<{&ZyM>9hOf- ztB8q$00)+rKRji$ADO@J$7g_VCmWvd@*fgJ`jG~(S zW#MQ5T+7!-mAnP_ai1$0?xd@5NQF_-@kdULYo8j?cP=XE!7gM_X!$Ubwi*#W0sqh_ zD6EeyRacs9JmC?OX4;WT0K-|-l=1#N)-)N%_c}lz5n0eY2f$fT$fcJ{c_Uy8GbKW| zeX)_KY|?h1LJ-z91P?GmP_|7}EZf=7aA8yrU}mjlR9pyvK4dQx4@5T`_93rKfm4-i zsX*{5wMMQtwc%qw2?s4k`%q@Ib6&$GU$<29mkOia5ZLjf0VLGPg3JTU_D+vh zWAq)%c}Gc2in�x|le0wqq^3twA(p5$8XJM28!0f(qO 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

0VwS4I=qBN8 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�Gkz$}4+K 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%0DZ^)zlvgm7x6g}Y3mQN7t$_?IrDBu zB@M)d7kuf>0Q6#n@rT>+LZ^=1CGLG~F{T}%w<4E5uik<5jpg}4ggCxeLO6O^mv8!8 zS$f5?M(!Jca3`UT6Y*TNbv9uqzsk!!Pj!fNw2vPfwF)sf{!EWL?Z8%BY0-#s*u0e!{kB2-c61M-yliWt$6v_iF+Z!9v5{XiOJ()&B^-0 zNSl*#f+(`@-8fpBXf9Itni`D}SmIZw>TLO|uhZx{E_VykF8@akkkbi=?qv0h&1E57 z<`H%zYm-cq5faRU_dQW49bi@si#3~o(m98}+?8uMZ^}|B)&}q4qGxSXB*Q@6&l^{f z@w%(-#B?wb9Odl5sKxZ^JY&vcNx(Q4*3Ix*njeVZMC1{(N`k1-&yhT^iydlD3(U#8 z<3Vz+D*DXEjLhNDB3Q&2-bjR4-a&z-Jz;3{QAZlM`s?)y*MxXn6+DFPXLa}QG{eKx zWvc}af1(cs%*;L%ImAUM!)yb=z^mZ}cL0nqH@4;PDg=sDDmEtKdzEHflaas`ds}}( zJu3v|&I>NOKS{F4s{CJv_#@R+SA5@9!vr}A&Lsos% zS}{>@Ev@0-%vKtZm378-%VK|&j1aYde3xGacI*Zf1c26d>_?Kox zxSlqc*ZjO3{rBcJiUe6#IFxX zEtFrqrYZpw+W`aH-~ntX-q-7*w&{i|dtLOUFJl9b;c{`Z0~g$SmcFU~_svbkb#YSF zzN*amgDpAF#o)AgPdK5660JhafqfuW^BU$Vg5)?PEd;_>F8FR55qE%71v%akelw6ZF#R>S*!ei)_JbNcgIALcBmCv@b1v{?~($r1O-{aTB*vnE`X>HG{j!N$90N9l13WM30(XWfW;+U@ zx@Rzaq=)iMGdPp0_%g} zst1RWy0mh?4iC;CA0zKJSybql3aw6oiex1%B5=YChspG)EArj9M4d}8r-vwS1BLL8 zzUXF>DG|Cr8>5nxRJQ1cEv%icvJ{->U-+#RGQ##L+{$FOhQlLZ%M z!>Lu!Xj~D^xG}7ZP%M@e&U)e`T;Upb$_8wfMEpHAan@DC{)N=+_L#ITw#Ow{=ASA( zE$N^6#JUUz4OL!`guRgx6~TILW-W?)44hR^6o5E^UODO1y&jeY@*RE2<*_H{!EGiI#8zdUs9lw#_`GM zRXtOOxVwnoVkC>X5@W32&iLO}={|g<~3uMQ&gjt52E>iF|gOEO?1?*qla+ zRi`aLW4)UReP0uiQAa66>FLW}TqpeHm``E8ef4Y@`@s;O>cI@K3#KbnXf-Yr&n`Ko zkLb~bcm>Tw;h*g+CNJG7|%yJTf8dElX}FdDQ?3+q&a&&%%al`xObap4Bu(6}KPbhdv7c2(MPAIa0ZJ$fgb?pZm`4bN8cJArgzSF`Y_@N$4*c zr(m=(4>0PeQwfZSddR5iF>P{zn?mXleR5}Y-=UVSG~2;ZNnbx0zD}<;c;Qx%lIFuT zo*ZU+vb&l?`mn;*%dqid{L?OpUKYP>YWF2k1g=uPRpD14kTIbr+Fs?t=mx0HZ7gEU zMfW^c7rEg1XU2+Jo#yx{y0GJT&|6T<&C3D~j+q!9H+T}U?y5=1Hbk!k-zuefOIOp> z>cveggmEry;7)FMBzAabgJbV$?7Oo*&u`-l=Bt&?TkSPrQ}NaRv!}7^Rl#@HNZC5w zVe}honoEWFvq`l#j2xYHChtO~ERX-Wz7j7O^nqNt!WUVG@(;~p#`8vxhgYkG{T8>- zCTv-pL9^^&BwI=axxo`fU7X+oIY)1#O#`lKY|N^6-p7s}Up(}8(Duagp{&IrwfPL9 z%==mgEB1eufU1{phH?kZ?BMS_EAB5D&&+FXCC ztmu|BeFXn6==~;Ti>t#LH^N2N>wQf%6H8}B1dAj?HIuGt zCgR!1Nxth(O*i2%j?*mduFc!g0G)jYK7kMIo2ACX3U872%We?L&{UL8$=_BNqFB+7 zI$s_|hlcrxiEu&gjng1BVLB2w3IlL#FCel=^alAl4T2-yd>hVNfqV>m;|B=yM;w^_ zeK&UHVVXB`qjiaW!9jOwm%1*V3}!(qP8G}56-uO59<@H=YyC;z0~Q_>5dB$FRwgu? z9JUE{F%$>)gY?V_0f3{h`*-q&xxa=L##S}Yqseqm|0*=uKx!!HR<5dHx2?jzaVFZt z4@|7ktE$1#41+^*RP+n_XlSP@MP3lH$S6qNNOC&f1?`#kv2ltSDyKm;K1&5HY{n%* z2NVlHDRhn|N0XV_Bsu{-bvC&m2)3U=V>^Qr44>ASf|~j!aw^&08Wzn!f-n ztDNxA#0-lq8*r&g*#tR0B_3XyO#Rk z`Cgi&&z)hvV|wbI6(DHGlfHPwKD%d4Gj86&M+yse8a|zKjf}(wT@?d5IMnK4*_cB& zYS_tBrk-bMU>}Sb!4gtI$08aSag0MuaSUgg5Gq9tqieCQ$9n`TM;OQ@UHUnX(ICwu z^R6N88)Ty4IAdxXYGS;lq9H`pSkBLQyO0s55$PYcE?>=g0Jd~!tYH=Yq+`@kHTO)t zd<@@xt01VlQ=Jwvrh~Lg9;!{;SpzfKM6Z#}>d#DM;U3IpUar;#z|X}}X;o2S7993~ zb)&Vbn(WvV)8Ql3L%}vKmcHL87KKBEffIl!!y3$Z$f4!i*_qws4-n7StNrK z$~}jL8Up@Q57iV7z3j)%^BAe2-#}`xT#?uly8oyCE4ykztF_3)7`>vfRL&P#sz}xG z%_KZJ5-ppLl+(1x(`}+jPpTipt>e<>O71=vqi76jSvhn3DjGsHBw0&~O)n*qTRi>u^~8r9ixpJ&9;v^OFyY-P)K7iwe! zmCh7{)7fVh?u53I+O2)fRhxs#Bz&EjsdHmyYJa1s_1GrVfe!#;5vRRu$|vy*`1&JQ z3C(oP{$?PYy`(Bn+K2ls|9lsWdcZ>=UJS~ig*P@?0e1CYbyQ=D!rPw!5jmS z7T@0?a-!#_ZG%l(566!`vE$k^#e-2?p9T}YKNBbA;ROg`>4n{J<-(o%x#ayg`K24( z>@0Vj+=xP*ls7Vjc>X3DXt{z!he?oatAT5lZztIJ5?L|mZ2z0{Cf{E^)2+CLy`yFQ zbHLrB;6S)SF~l5SyInyL5A$&@WzxpUbKH*8f;@`*rsBS@2A#dz&?3s5f-^tzDZ{UU zZKE*5#;VL2-C{q7Z)12Aiqn*~MK==;8!K(?BJ^#Fy42)a2Q%~jNL9JH?OJC3(fNQT zQU3cbHuJ*|f;_a!BNmBtZoYE%=X58scO0lAUv2}D6*+s+hsK634eh>729!fo)+E}p z)ek1)JvD%RA-mWT5L<6S{c&c=Z;s>FIbDc6v2!aP3vHF{D`m{qvM=WyXj9IHX3g1Q zgr&Kv4$-l=Iu?ia*O{P$o~mlf-x!8PW~q)44oJMAwlqfVm9SDsG{E5DheJ+8KK_zn z^Kb8;^%_0)e^ywVcuM}S!UifaH~<{H=T8?#Dj*>yewqx+?QnTZVqhM_XOfLJA(K-v z20vA`#7l_yudR)z;VRQD#5^9gF<2{i&9D4QpExT#k5ai0O5M2Oo@Hv@`0s`1=#2qD zXur+BHeJ;IHrL39|4H(O0bac^NSRL#HP}r1*v`U@GG*`=1+o5a1Xp$N!08U7&_w;J zNWnTSDwzcdl19OWi4CRb$U|Fc6VreqyOmi3cYrj1!Oz@iP9PHr_O~Duwk}hVQV4*^ zY<2<$dbBWxiB=GAe2a<#pmVj*@tzc^*A&Aib?nwI(A%@8d+_fOHUwLF6N?H@y(8% zgsnlCj-@;Nf$X;l*P#nl_-ITWGR|<6{A$&wZv^8z)$Q2YWNHtv{T|Lyh15=($v&nj zq-wxYw#13GX%@QQL26kdvfZW8X*cR*xBW@Dw#TQWTc(o5U@D?u+KpAMRf>=FeTZJd z_>En|Y(h26du;+=~L!!9}OSNRZ=YcIQv2xSsb=eNH(3kRYpc7@O2QD7UmQ z&Bmf4`lZr@>Qo8hE5RE9Idh8{yva%7T(`Ln`lE_#GSjsqiNYfGoJh?H1-iVNOA%Zm z10*3lae$_)H&#gI#b(0Ba-1jYVZ||VW}%t`SZ($pQ?S6TT;LvEIuH_yX(E4-PPKEh zWmkG}h&^bY5^nm%8UvIi@D+6g+hP^9+h}@gZH&b6q5-~WRN+^^@pLy?fWJA)XY+Zg zvf3>iU5p{T;MKF6USb}^b6D4gIMO`KRzRv7QO&g~%klS4Q_)U%T6s}Ny)#l=(8H%H z`P|4QSbsMw5F@9JEV4qhRf_sLvmdhXT6-^;wJ5~Ml{g7ufg}S24|4&(I~%c z3P1=JYG^eC^sV~vq6EBsB(;E4lps{{uso#OnY6 From 1b9800bfc26fde1cbf2da1a1f525cde362eb921e Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 8 Jul 2022 17:21:07 -0400 Subject: [PATCH 014/195] improve speed test code --- models_evaluation/speed_test_evaluation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models_evaluation/speed_test_evaluation.py b/models_evaluation/speed_test_evaluation.py index 1a1322ec..7c2e337b 100644 --- a/models_evaluation/speed_test_evaluation.py +++ b/models_evaluation/speed_test_evaluation.py @@ -1,15 +1,15 @@ import os.path -import pickle from statistics import mean from deepparse import download_from_public_repository +from deepparse.dataset_container import PickleDatasetContainer from deepparse.parser import AddressParser from models_evaluation.timer.timer import Timer download_from_public_repository("speed_test_dataset", "./data", "p") -addresses = pickle.load(open("./data/speed_test_dataset.p", "rb")) -addresses, tags = zip(*addresses) +address_container = PickleDatasetContainer("./data/speed_test_dataset.p") +addresses, tags = zip(*address_container) speed_test_directory = "results/speed_test_results" os.makedirs(speed_test_directory, exist_ok=True) From 38e61153a18b8cfdb6e1a4edff05ea0ef6ca360a Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 12 Jul 2022 10:28:15 -0400 Subject: [PATCH 015/195] add num_workers test fasttext under windows os condition --- deepparse/parser/address_parser.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/deepparse/parser/address_parser.py b/deepparse/parser/address_parser.py index caf05bbc..f598bc90 100644 --- a/deepparse/parser/address_parser.py +++ b/deepparse/parser/address_parser.py @@ -2,6 +2,7 @@ import contextlib import os +import platform import re import warnings from typing import List, Union, Dict, Tuple @@ -590,18 +591,26 @@ def retrain( name_of_the_retrain_parser="MyParserName") """ - if name_of_the_retrain_parser is not None: - if len(name_of_the_retrain_parser.split(".")) > 1: - raise ValueError( - "The name_of_the_retrain_parser should NOT include a file extension or a dot-like" "filename style." - ) if "fasttext-light" in self.model_type: raise ValueError("It's not possible to retrain a fasttext-light due to pymagnitude problem.") + if platform.system().lower() == "windows" and "fasttext" in self.model_type and num_workers > 0: + raise ValueError( + "On Windows system, we cannot retrain FastText like models with parallelism workers since " + "FastText objects are not pickleable with the parallelism process use by Windows. " + "Thus, you need to set num_workers to 0 since 1 also means 'parallelism'." + ) + if not dataset_container.is_a_train_container(): raise ValueError("The dataset container is not a train container.") + if name_of_the_retrain_parser is not None: + if len(name_of_the_retrain_parser.split(".")) > 1: + raise ValueError( + "The name_of_the_retrain_parser should NOT include a file extension or a dot-like" "filename style." + ) + model_factory_dict = {"prediction_layer_len": 9} # We set the default output dim size if prediction_tags is not None: From 9154a1df9afb57a90addd4127e12dbe1afac8fd5 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 12 Jul 2022 10:42:51 -0400 Subject: [PATCH 016/195] add tests case for num_workers test in parser --- .../parser/test_address_parser_retrain_api.py | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/tests/parser/test_address_parser_retrain_api.py b/tests/parser/test_address_parser_retrain_api.py index 1b33256c..8ba202ca 100644 --- a/tests/parser/test_address_parser_retrain_api.py +++ b/tests/parser/test_address_parser_retrain_api.py @@ -24,7 +24,6 @@ def setUpClass(cls): cls.a_train_ratio = 0.8 cls.a_batch_size = BATCH_SIZE cls.a_epoch_number = 1 - cls.a_number_of_workers = 1 cls.a_learning_rate = 0.01 cls.a_callbacks_list = [] cls.a_seed = 42 @@ -58,14 +57,19 @@ def tearDown(self) -> None: self.temp_dir_obj.cleanup() def address_parser_retrain_call( - self, prediction_tags=None, seq2seq_params=None, layers_to_freeze=None, name_of_the_retrain_parser=None + self, + prediction_tags=None, + seq2seq_params=None, + layers_to_freeze=None, + name_of_the_retrain_parser=None, + num_workers=1, ): self.address_parser.retrain( self.mocked_data_container, self.a_train_ratio, self.a_batch_size, self.a_epoch_number, - num_workers=self.a_number_of_workers, + num_workers=num_workers, learning_rate=self.a_learning_rate, callbacks=self.a_callbacks_list, seed=self.a_seed, @@ -370,6 +374,36 @@ def test_givenAFasttextMagnitudeModel_whenRetrain_thenRaiseError( with self.assertRaises(ValueError): self.address_parser_retrain_call() + @patch("deepparse.parser.address_parser.platform") + @patch("deepparse.parser.address_parser.DataTransform") + @patch("deepparse.parser.address_parser.FastTextSeq2SeqModel") + @patch("deepparse.parser.address_parser.fasttext_data_padding") + @patch("deepparse.parser.address_parser.FastTextVectorizer") + @patch("deepparse.parser.address_parser.FastTextEmbeddingsModel") + @patch("deepparse.parser.address_parser.download_fasttext_embeddings") + def test_givenAFastTextLikeModelOnWindowsOS_whenRetrainWithNumWorkersGT0_thenReRaiseError( + self, + download_weights_mock, + embeddings_model_mock, + vectorizer_model_mock, + data_padding_mock, + model_mock, + data_transform_mock, + platform_mock, + ): + # OS equal Windows + platform_mock.system().__eq__.return_value = True + + self.address_parser = AddressParser( + model_type=self.a_fastest_model_type, + device=self.a_device, + verbose=self.verbose, + ) + + num_workers_gt_0 = 1 + with self.assertRaises(ValueError): + self.address_parser_retrain_call(num_workers=num_workers_gt_0) + @patch("deepparse.parser.address_parser.torch.save") @patch("deepparse.parser.address_parser.DataLoader") @patch("deepparse.parser.address_parser.Experiment") @@ -1144,13 +1178,16 @@ def test_givenNotTrainingDataContainer_thenRaiseValueError( verbose=self.verbose, ) mocked_data_container = ADataContainer(is_training_container=False) + + a_number_of_workers = 1 + with self.assertRaises(ValueError): self.address_parser.retrain( mocked_data_container, self.a_train_ratio, self.a_batch_size, self.a_epoch_number, - num_workers=self.a_number_of_workers, + num_workers=a_number_of_workers, learning_rate=self.a_learning_rate, callbacks=self.a_callbacks_list, seed=self.a_seed, From a6b28fde0533285ae5dbb588705efee39ca612d8 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 12 Jul 2022 10:43:16 -0400 Subject: [PATCH 017/195] simplified tests case windows --- tests/embeddings_models/test_fasttext_embeddings_model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/embeddings_models/test_fasttext_embeddings_model.py b/tests/embeddings_models/test_fasttext_embeddings_model.py index e512c582..edb786b4 100644 --- a/tests/embeddings_models/test_fasttext_embeddings_model.py +++ b/tests/embeddings_models/test_fasttext_embeddings_model.py @@ -21,7 +21,7 @@ def setUp(self): model.get_dimension = shape_mock self.model = model - @skipIf(platform.system() == "Windows", "Integration test not on Windows env.") + @skipIf(platform.system().lower() == "windows", "Integration test not on Windows env.") def test_whenInstantiatedWithPath_thenShouldLoadFasttextModel(self): with patch( "deepparse.embeddings_models.fasttext_embeddings_model.load_fasttext_embeddings", @@ -31,7 +31,7 @@ def test_whenInstantiatedWithPath_thenShouldLoadFasttextModel(self): loader.assert_called_with(self.a_path) - @skipIf(platform.system() == "Windows", "Integration test not on Windows env.") + @skipIf(platform.system().lower() == "Windows", "Integration test not on windows env.") def test_whenCalledToEmbed_thenShouldCallLoadedModel(self): with patch( "deepparse.embeddings_models.fasttext_embeddings_model.load_fasttext_embeddings", @@ -79,7 +79,7 @@ def test_whenInstantiatedOnMacOS_thenShouldLoadFasttextModel(self, platform_mock loader.assert_called_with(self.a_path) - @skipIf(not platform.system() == "Windows", "Integration test on Windows env.") + @skipIf(not platform.system().lower() == "windows", "Integration test on Windows env.") def test_givenADimOf9Windows_whenAskDimProperty_thenReturnProperDim(self): with patch( "deepparse.embeddings_models.fasttext_embeddings_model.load_facebook_vectors", @@ -91,7 +91,7 @@ def test_givenADimOf9Windows_whenAskDimProperty_thenReturnProperDim(self): expected = self.dim self.assertEqual(actual, expected) - @skipIf(platform.system() == "Windows", "Integration test not on Windows env.") + @skipIf(platform.system().lower() == "windows", "Integration test not on Windows env.") def test_givenADimOf9Linux_whenAskDimProperty_thenReturnProperDim(self): with patch( "deepparse.embeddings_models.fasttext_embeddings_model.load_fasttext_embeddings", From de55e638aec843dc59e4d41f8a8f2488cb6898c0 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 12 Jul 2022 10:46:41 -0400 Subject: [PATCH 018/195] update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b643fd9..92258c96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -233,4 +233,5 @@ to `cache_dir`. `saving_dir` is now deprecated and will be remove in version 0.8 ## dev -- Refactored function `download_from_url` to `download_from_public_repository`. \ No newline at end of file +- Refactored function `download_from_url` to `download_from_public_repository`. +- Add error management when retrain a FastText like model on Windows with a number of workers (`num_workers`) greater than 0. \ No newline at end of file From 6b24e0c2781541143a51fa2a7bb1dfa1b465358f Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 12 Jul 2022 11:53:45 -0400 Subject: [PATCH 019/195] fix windows os failing test due to num workers gt 0 --- tests/parser/test_address_parser_retrain_api.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/parser/test_address_parser_retrain_api.py b/tests/parser/test_address_parser_retrain_api.py index 8ba202ca..3ebafcb4 100644 --- a/tests/parser/test_address_parser_retrain_api.py +++ b/tests/parser/test_address_parser_retrain_api.py @@ -1,6 +1,7 @@ # Since we use a patch as model mock we skip the unused argument error # pylint: disable=unused-argument, too-many-arguments, too-many-public-methods, protected-access, too-many-lines import os +import platform import unittest from tempfile import TemporaryDirectory from unittest import skipIf @@ -62,8 +63,16 @@ def address_parser_retrain_call( seq2seq_params=None, layers_to_freeze=None, name_of_the_retrain_parser=None, - num_workers=1, + num_workers=None, ): + if num_workers is None: + # AddressParser default num_workers settings is 1 + # But, we change it to 0 for Windows OS to allow test to pass since it fail (volontairy) + # at greater than 0 due to parallelism pickle error + if platform.system().lower() == "windows": + num_workers = 0 # Default setting is 1, but We set it to zero to allow Windows tests to pass + else: + num_workers = 1 self.address_parser.retrain( self.mocked_data_container, self.a_train_ratio, @@ -1179,7 +1188,7 @@ def test_givenNotTrainingDataContainer_thenRaiseValueError( ) mocked_data_container = ADataContainer(is_training_container=False) - a_number_of_workers = 1 + a_number_of_workers = 0 # We set it to 0 to allow Windows test to also pass with self.assertRaises(ValueError): self.address_parser.retrain( From b1748e91571853b348bfab8d5849ba85ea2fc65e Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 12 Jul 2022 12:13:52 -0400 Subject: [PATCH 020/195] fix missing lower cassing windows os name --- tests/embeddings_models/test_fasttext_embeddings_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/embeddings_models/test_fasttext_embeddings_model.py b/tests/embeddings_models/test_fasttext_embeddings_model.py index edb786b4..0551df51 100644 --- a/tests/embeddings_models/test_fasttext_embeddings_model.py +++ b/tests/embeddings_models/test_fasttext_embeddings_model.py @@ -31,7 +31,7 @@ def test_whenInstantiatedWithPath_thenShouldLoadFasttextModel(self): loader.assert_called_with(self.a_path) - @skipIf(platform.system().lower() == "Windows", "Integration test not on windows env.") + @skipIf(platform.system().lower() == "windows", "Integration test not on windows env.") def test_whenCalledToEmbed_thenShouldCallLoadedModel(self): with patch( "deepparse.embeddings_models.fasttext_embeddings_model.load_fasttext_embeddings", From c0417e2b10ab7d15e148e2432dffff3616c66da7 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 13 Jul 2022 11:31:05 -0400 Subject: [PATCH 021/195] add missing downlaod_from_url deprecated message and redirect to new refactored function --- deepparse/tools.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/deepparse/tools.py b/deepparse/tools.py index df2943c1..3008dc6e 100644 --- a/deepparse/tools.py +++ b/deepparse/tools.py @@ -82,7 +82,15 @@ def latest_version(model: str, cache_path: str, verbose: bool) -> bool: return is_latest_version -def download_from_public_repository(file_name: str, saving_dir: str, file_extension: str): +def download_from_url(file_name: str, saving_dir: str, file_extension: str) -> None: + warnings.warn( + "download_from_url is deprecated; use download_from_public_repository to download files from " + "our public repository. The function will be removed in the next major release." + ) + download_from_public_repository(file_name=file_name, saving_dir=saving_dir, file_extension=file_extension) + + +def download_from_public_repository(file_name: str, saving_dir: str, file_extension: str) -> None: """ Simple function to download the content of a file from Deepparse public repository. The repository URL string is ̀`'https://graal.ift.ulaval.ca/public/deepparse/{}.{}'`` @@ -122,7 +130,7 @@ def handle_poutyne_version() -> str: return version -def valid_poutyne_version(min_major: int = 1, min_minor: int = 2): +def valid_poutyne_version(min_major: int = 1, min_minor: int = 2) -> bool: """ Validate Poutyne version is greater than min_major.min_minor for using a str checkpoint. Some version before does not support all the features we need. By default, min_major.min_minor equal version 1.2 which is the From 3f92b051c97178cced44dee95dd2b573a8582591 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 13 Jul 2022 11:31:21 -0400 Subject: [PATCH 022/195] add major release todo list to track function to remove --- major_release_todo.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 major_release_todo.md diff --git a/major_release_todo.md b/major_release_todo.md new file mode 100644 index 00000000..30b2d39f --- /dev/null +++ b/major_release_todo.md @@ -0,0 +1 @@ +- Remove deprecated `download_from_url` function \ No newline at end of file From ad6c8f05f044513a1b500d1209dc962bcb2494af Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 13 Jul 2022 11:31:30 -0400 Subject: [PATCH 023/195] update changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92258c96..7722168c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -234,4 +234,6 @@ to `cache_dir`. `saving_dir` is now deprecated and will be remove in version 0.8 ## dev - Refactored function `download_from_url` to `download_from_public_repository`. -- Add error management when retrain a FastText like model on Windows with a number of workers (`num_workers`) greater than 0. \ No newline at end of file +- Add error management when retrain a FastText like model on Windows with a number of workers (`num_workers`) greater than 0. +- Improve dev tooling +- Improve CI \ No newline at end of file From 9a4f6a84a5af283194d0ae72f29de8ed6acaf3b1 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 13 Jul 2022 11:33:10 -0400 Subject: [PATCH 024/195] add pragma no cover to skip codecovv --- deepparse/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepparse/tools.py b/deepparse/tools.py index 3008dc6e..cab23d27 100644 --- a/deepparse/tools.py +++ b/deepparse/tools.py @@ -82,7 +82,7 @@ def latest_version(model: str, cache_path: str, verbose: bool) -> bool: return is_latest_version -def download_from_url(file_name: str, saving_dir: str, file_extension: str) -> None: +def download_from_url(file_name: str, saving_dir: str, file_extension: str) -> None: # pragma: no cover warnings.warn( "download_from_url is deprecated; use download_from_public_repository to download files from " "our public repository. The function will be removed in the next major release." From ca51cebe3802d8a4f4a8fc3b29374a37d80b6db9 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 15 Jul 2022 20:07:55 -0400 Subject: [PATCH 025/195] improve variable naming --- deepparse/cli/test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deepparse/cli/test.py b/deepparse/cli/test.py index 5b67f989..39185f2a 100644 --- a/deepparse/cli/test.py +++ b/deepparse/cli/test.py @@ -82,7 +82,7 @@ def main(args=None) -> None: batch_size = parsed_args.batch_size num_workers = parsed_args.num_workers seed = parsed_args.seed - parser_args = { + test_arguments = { "batch_size": batch_size, "num_workers": num_workers, "seed": seed, @@ -98,7 +98,7 @@ def main(args=None) -> None: text_to_log = f"Testing results on dataset file {test_dataset_path} using the parser {str(address_parser)}." logging.info(text_to_log) - results = address_parser.test(test_dataset_container=testing_data, **parser_args) + results = address_parser.test(test_dataset_container=testing_data, **test_arguments) pd.DataFrame(results, index=[0]).to_csv(results_export_path, index=False, sep="\t") if parsed_args.log: From 99b1d1c32ca55da575fe0fd2f59be98dc0ebdbfb Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 15 Jul 2022 20:08:25 -0400 Subject: [PATCH 026/195] refactor position of non protected method --- deepparse/dataset_container/dataset_container.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deepparse/dataset_container/dataset_container.py b/deepparse/dataset_container/dataset_container.py index 500b666a..3f69c438 100644 --- a/deepparse/dataset_container/dataset_container.py +++ b/deepparse/dataset_container/dataset_container.py @@ -58,6 +58,9 @@ def __getitem__(self, idx: Union[int, slice]): result = self.data[idx] return result + def is_a_train_container(self) -> bool: + return self.is_training_container + def validate_dataset(self) -> None: if not self._data_is_a_list(): raise TypeError("The dataset is not a list.") @@ -121,9 +124,6 @@ def _data_tags_is_same_len_then_address(self) -> bool: """ return all(len(data[0].split(" ")) == len(data[1]) for data in self.data) - def is_a_train_container(self) -> bool: - return self.is_training_container - def _data_tags_not_the_same_len_diff(self) -> str: diff_indexes = [index for index, data in enumerate(self.data) if len(data[0].split(" ")) != len(data[1])] report = "" From 79b5513c76d7c9cb2c4a7d482e60c0e0c769d103 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 26 Jul 2022 11:46:31 -0400 Subject: [PATCH 027/195] bump pylint and add django for codacy --- .pylintrc | 514 ++++++++++++++++++++------------------- settings.py | 8 + styling_requirements.txt | 3 +- 3 files changed, 275 insertions(+), 250 deletions(-) create mode 100644 settings.py diff --git a/.pylintrc b/.pylintrc index 86812da4..786a8dcf 100644 --- a/.pylintrc +++ b/.pylintrc @@ -2,36 +2,61 @@ # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist=torch +# run arbitrary code. +extension-pkg-allow-list= -# Add files or directories to the blacklist. They should be base names, not -# paths. +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Files or directories to be skipped. They should be base names, not paths. ignore=CVS -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. +# Add files or directories matching the regex patterns to the ignore-list. The +# regex matches against paths and can be in Posix or Windows format. +ignore-paths= + +# Files or directories matching the regex patterns are skipped. The regex +# matches against base names, not paths. ignore-patterns= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= -# Use multiple processes to speed up Pylint. -jobs=4 +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 -# List of plugins (as comma separated values of python modules names) to load, +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, # usually to register additional checkers. -load-plugins= +load-plugins=pylint_django +django-settings-module=settings # Pickle collected data for later comparisons. persistent=yes -# Specify a configuration file. -#rcfile= +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.7 # When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages +# user-friendly hints instead of false-positive error messages. suggestion-mode=yes # Allow loading of arbitrary C extensions. Extensions are imported into the @@ -42,107 +67,28 @@ unsafe-load-any-extension=no [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. confidence= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to +# file where it should appear only once). You can also use "--disable=all" to # disable everything first and then reenable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable=print-statement, - parameter-unpacking, - unpacking-in-except, - old-raise-syntax, - backtick, - long-suffix, - old-ne-operator, - old-octal-literal, - import-star-module-level, - non-ascii-bytes-literal, - raw-checker-failed, +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, bad-inline-option, locally-disabled, - locally-enabled, file-ignored, suppressed-message, useless-suppression, deprecated-pragma, - apply-builtin, - basestring-builtin, - buffer-builtin, - cmp-builtin, - coerce-builtin, - execfile-builtin, - file-builtin, - long-builtin, - raw_input-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - no-absolute-import, - old-division, - dict-iter-method, - dict-view-method, - next-method-called, - metaclass-assignment, - indexing-exception, - raising-string, - reload-builtin, - oct-method, - hex-method, - nonzero-method, - cmp-method, - input-builtin, - round-builtin, - intern-builtin, - unichr-builtin, - map-builtin-not-iterating, - zip-builtin-not-iterating, - range-builtin-not-iterating, - filter-builtin-not-iterating, - using-cmp-argument, - eq-without-hash, - div-method, - idiv-method, - rdiv-method, - exception-message-attribute, - invalid-str-codec, - sys-max-int, - bad-python3-import, - deprecated-string-function, - deprecated-str-translate-call, - deprecated-itertools-function, - deprecated-types-field, - deprecated-module, - next-method-defined, - dict-items-not-iterating, - dict-keys-not-iterating, - dict-values-not-iterating, - invalid-name, - no-self-use, + use-symbolic-message-instead, missing-docstring, - len-as-condition, - attribute-defined-outside-init, - too-many-instance-attributes, - duplicate-code, - too-few-public-methods, - unnecessary-pass, - cyclic-import, - arguments-differ, - isinstance-second-argument-not-valid-type, - consider-using-from-import, - consider-using-with, - inconsistent-return-statements + invalid-name # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -153,23 +99,23 @@ enable=c-extension-no-member [REPORTS] -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details +# used to format the message information. See doc for all details. #msg-template= # Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio).You can also give a reporter class, eg +# and msvs (visual studio). You can also give a reporter class, e.g. # mypackage.mymodule.MyReporterClass. output-format=text -# Tells whether to display a full report or only the messages +# Tells whether to display a full report or only the messages. reports=no # Activate the evaluation score. @@ -185,101 +131,50 @@ max-nested-blocks=5 # inconsistent-return-statements if a never returning function is called then # it will be considered as an explicit return statement and no message will be # printed. -never-returning-functions=optparse.Values,sys.exit +never-returning-functions=sys.exit,argparse.parse_error [SIMILARITIES] -# Ignore comments when computing similarities. +# Comments are removed from the similarity computation ignore-comments=yes -# Ignore docstrings when computing similarities. +# Docstrings are removed from the similarity computation ignore-docstrings=yes -# Ignore imports when computing similarities. +# Imports are removed from the similarity computation ignore-imports=no +# Signatures are removed from the similarity computation +ignore-signatures=no + # Minimum lines number of a similarity. min-similarity-lines=4 -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=120 - -# Maximum number of lines in a module -max-module-lines=1000 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - [VARIABLES] # List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. +# you should avoid defining new builtins when possible. additional-builtins= # Tells whether unused global variables should be treated as a violation. allow-global-unused-variables=yes +# List of names allowed to shadow builtins +allowed-redefined-builtins= + # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_, _cb -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ # Argument names that match this expression will be ignored. Default to name -# with leading underscore +# with leading underscore. ignored-argument-names=_.*|^ignored_|^unused_ # Tells whether we should check for unused import in __init__ files. @@ -287,26 +182,61 @@ init-import=no # List of qualified module names which can have objects that can redefine # builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the 'python-enchant' package. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear and the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no [BASIC] -# Naming style matching correct argument names +# Naming style matching correct argument names. argument-naming-style=snake_case # Regular expression matching correct argument names. Overrides argument- -# naming-style +# naming-style. #argument-rgx= -# Naming style matching correct attribute names +# Naming style matching correct attribute names. attr-naming-style=snake_case # Regular expression matching correct attribute names. Overrides attr-naming- -# style +# style. #attr-rgx= -# Bad variable names which should always be refused, separated by a comma +# Bad variable names which should always be refused, separated by a comma. bad-names=foo, bar, baz, @@ -314,38 +244,50 @@ bad-names=foo, tutu, tata -# Naming style matching correct class attribute names +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. class-attribute-naming-style=any # Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style +# attribute-naming-style. #class-attribute-rgx= -# Naming style matching correct class names +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. +#class-const-rgx= + +# Naming style matching correct class names. class-naming-style=PascalCase -# Regular expression matching correct class names. Overrides class-naming-style +# Regular expression matching correct class names. Overrides class-naming- +# style. #class-rgx= -# Naming style matching correct constant names +# Naming style matching correct constant names. const-naming-style=UPPER_CASE # Regular expression matching correct constant names. Overrides const-naming- -# style +# style. #const-rgx= # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=-1 -# Naming style matching correct function names +# Naming style matching correct function names. function-naming-style=snake_case # Regular expression matching correct function names. Overrides function- -# naming-style +# naming-style. #function-rgx= -# Good variable names which should always be accepted, separated by a comma +# Good variable names which should always be accepted, separated by a comma. good-names=i, j, k, @@ -353,28 +295,32 @@ good-names=i, Run, _ -# Include a hint for the correct naming format with invalid-name +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. include-naming-hint=no -# Naming style matching correct inline iteration names +# Naming style matching correct inline iteration names. inlinevar-naming-style=any # Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style +# inlinevar-naming-style. #inlinevar-rgx= -# Naming style matching correct method names +# Naming style matching correct method names. method-naming-style=snake_case # Regular expression matching correct method names. Overrides method-naming- -# style +# style. #method-rgx= -# Naming style matching correct module names +# Naming style matching correct module names. module-naming-style=snake_case # Regular expression matching correct module names. Overrides module-naming- -# style +# style. #module-rgx= # Colon-delimited sets of names that determine each other's naming style when @@ -387,22 +333,45 @@ no-docstring-rgx=^_ # List of decorators that produce properties, such as abc.abstractproperty. Add # to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. property-classes=abc.abstractproperty -# Naming style matching correct variable names +# Naming style matching correct variable names. variable-naming-style=snake_case # Regular expression matching correct variable names. Overrides variable- -# naming-style +# naming-style. #variable-rgx= -[MISCELLANEOUS] +[FORMAT] -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no [TYPECHECK] @@ -415,12 +384,16 @@ contextmanager-decorators=contextlib.contextmanager # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. -generated-members=torch.*,numpy.* +generated-members=REQUEST,acl_users,aq_parent,"[a-zA-Z]+_set{1,2}",save,delete # Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). +# class is considered mixin if its name matches the mixin-class-rgx option. ignore-mixin-members=yes +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + # This flag controls whether pylint should warn about no-member and similar # checks whenever an opaque object is returned when inferring. The inference # can return multiple potential results while evaluating a Python object, but @@ -436,7 +409,7 @@ ignored-classes=optparse.Values,thread._local,_thread._local # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It +# and thus existing member attributes cannot be deduced by static analysis). It # supports qualified module names, as well as Unix pattern matching. ignored-modules= @@ -452,41 +425,112 @@ missing-member-hint-distance=1 # showing a hint for a missing member. missing-member-max-choices=1 +# Regex pattern to define which classes are considered mixins ignore-mixin- +# members is set to 'yes' +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + [LOGGING] +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + # Logging modules to check that the string format arguments are in logging -# function parameter format +# function parameter format. logging-modules=logging +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + [DESIGN] -# Maximum number of arguments for function / method +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. max-args=5 # Maximum number of attributes for a class (see R0902). max-attributes=7 -# Maximum number of boolean expressions in a if statement +# Maximum number of boolean expressions in an if statement (see R0916). max-bool-expr=5 -# Maximum number of branch for function / method body +# Maximum number of branch for function / method body. max-branches=12 -# Maximum number of locals for function / method body +# Maximum number of locals for function / method body. max-locals=15 # Maximum number of parents for a class (see R0901). -max-parents=7 +max-parents=13 # Maximum number of public methods for a class (see R0904). max-public-methods=20 -# Maximum number of return / yield for function / method body +# Maximum number of return / yield for function / method body. max-returns=6 -# Maximum number of statements in function / method body +# Maximum number of statements in function / method body. max-statements=50 # Minimum number of public methods for a class (see R0903). @@ -495,10 +539,14 @@ min-public-methods=2 [CLASSES] +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__, __new__, - setUp + setUp, + __post_init__ # List of member names, which should be excluded from the protected access # warning. @@ -512,44 +560,12 @@ exclude-protected=_asdict, valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant +valid-metaclass-classmethod-first-arg=cls [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/settings.py b/settings.py new file mode 100644 index 00000000..6874851c --- /dev/null +++ b/settings.py @@ -0,0 +1,8 @@ +# Django settings for django_frontend project. + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' diff --git a/styling_requirements.txt b/styling_requirements.txt index 112f1a3a..55cef994 100644 --- a/styling_requirements.txt +++ b/styling_requirements.txt @@ -1,2 +1,3 @@ black==22.3.0 -pylint==2.12.1 +pylint==2.14.5 +pylint-django[with_django] From 3b4b821dd0b5ce5156d8b3ff0b16d7c4af37472f Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 26 Jul 2022 11:46:48 -0400 Subject: [PATCH 028/195] fix deepparse tools pylint --- deepparse/fasttext_tools.py | 7 ++++--- deepparse/weights_init.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/deepparse/fasttext_tools.py b/deepparse/fasttext_tools.py index 65152f65..7fad8d59 100644 --- a/deepparse/fasttext_tools.py +++ b/deepparse/fasttext_tools.py @@ -17,7 +17,7 @@ def download_fasttext_magnitude_embeddings(cache_dir: str, verbose: bool = True, if saving_dir is not None: # pragma: no cover # Deprecated argument handling warnings.warn( - "Argument saving_dir is deprecated. Use cache_dir instead. The argument will be removed " "in release 0.8.", + "Argument saving_dir is deprecated. Use cache_dir instead. The argument will be removed in release 0.8.", DeprecationWarning, ) cache_dir = saving_dir @@ -86,7 +86,7 @@ def download_fasttext_embeddings(cache_dir: str, verbose: bool = True, saving_di if saving_dir is not None: # pragma: no cover # Deprecated argument handling warnings.warn( - "Argument saving_dir is deprecated. Use cache_dir instead. The argument will be removed " "in release 0.8.", + "Argument saving_dir is deprecated. Use cache_dir instead. The argument will be removed in release 0.8.", DeprecationWarning, ) cache_dir = saving_dir @@ -132,7 +132,8 @@ def download_gz_model(gz_file_name: str, saving_path: str, verbose: bool = True) def _download_file(url: str, write_file_name: str, chunk_size: int = 2**13, verbose: bool = True) -> None: if verbose: print(f"Downloading {url}") - response = urlopen(url) + + response = urlopen(url) # pylint: disable=consider-using-with if hasattr(response, "getheader"): file_size = int(response.getheader("Content-Length").strip()) else: # pragma: no cover diff --git a/deepparse/weights_init.py b/deepparse/weights_init.py index 0ab5a8cc..551f5d86 100644 --- a/deepparse/weights_init.py +++ b/deepparse/weights_init.py @@ -1,5 +1,5 @@ -import torch.nn as nn -import torch.nn.init as init +from torch import nn +from torch.nn import init def weights_init(m): From d20f51e48752dcb044ebc0043ad7af36e10dae15 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 26 Jul 2022 11:52:53 -0400 Subject: [PATCH 029/195] fix network pylint --- deepparse/network/bpemb_seq2seq.py | 5 +++++ deepparse/network/decoder.py | 8 ++++++-- deepparse/network/embedding_network.py | 6 +++++- deepparse/network/encoder.py | 2 +- deepparse/network/fasttext_seq2seq.py | 4 ++++ deepparse/network/seq2seq.py | 6 +++++- 6 files changed, 26 insertions(+), 5 deletions(-) diff --git a/deepparse/network/bpemb_seq2seq.py b/deepparse/network/bpemb_seq2seq.py index 011977fa..2ca26c81 100644 --- a/deepparse/network/bpemb_seq2seq.py +++ b/deepparse/network/bpemb_seq2seq.py @@ -1,4 +1,9 @@ # pylint: disable=too-many-arguments + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + from typing import List, Union import torch diff --git a/deepparse/network/decoder.py b/deepparse/network/decoder.py index 29d12723..9405be13 100644 --- a/deepparse/network/decoder.py +++ b/deepparse/network/decoder.py @@ -1,9 +1,13 @@ # temporary fix for _forward_unimplemented for torch 1.6 https://github.com/pytorch/pytorch/issues/42305 -# pylint: disable=W0223, too-many-arguments +# pylint: disable=W0223, too-many-arguments, too-many-instance-attributes + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member from typing import Tuple import torch -import torch.nn as nn +from torch import nn from ..weights_init import weights_init diff --git a/deepparse/network/embedding_network.py b/deepparse/network/embedding_network.py index 80d65df9..13da67c8 100644 --- a/deepparse/network/embedding_network.py +++ b/deepparse/network/embedding_network.py @@ -4,10 +4,14 @@ # temporary fix for _forward_unimplemented for PyTorch 1.6 https://github.com/pytorch/pytorch/issues/42305 # pylint: disable=W0223 +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + from typing import Tuple, List import torch -import torch.nn as nn +from torch import nn from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence diff --git a/deepparse/network/encoder.py b/deepparse/network/encoder.py index 05af855b..4ee7f643 100644 --- a/deepparse/network/encoder.py +++ b/deepparse/network/encoder.py @@ -4,7 +4,7 @@ from typing import Tuple import torch -import torch.nn as nn +from torch import nn from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence from ..weights_init import weights_init diff --git a/deepparse/network/fasttext_seq2seq.py b/deepparse/network/fasttext_seq2seq.py index 73bad529..598032ca 100644 --- a/deepparse/network/fasttext_seq2seq.py +++ b/deepparse/network/fasttext_seq2seq.py @@ -1,4 +1,8 @@ # pylint: disable=too-many-arguments + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member from typing import Union import torch diff --git a/deepparse/network/seq2seq.py b/deepparse/network/seq2seq.py index 79376e66..3b5443ee 100644 --- a/deepparse/network/seq2seq.py +++ b/deepparse/network/seq2seq.py @@ -1,4 +1,8 @@ # pylint: disable=too-many-arguments + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member import os import random import warnings @@ -7,7 +11,7 @@ from typing import Tuple, Union import torch -import torch.nn as nn +from torch import nn from .decoder import Decoder from .encoder import Encoder From 1aef16968bacea1d8056defb866f17136f3611e0 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 26 Jul 2022 11:58:56 -0400 Subject: [PATCH 030/195] fix vectorizer modules --- .pylintrc | 3 ++- deepparse/vectorizer/bpemb_vectorizer.py | 6 +++++- deepparse/vectorizer/vectorizer.py | 2 -- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.pylintrc b/.pylintrc index 786a8dcf..8d16a28e 100644 --- a/.pylintrc +++ b/.pylintrc @@ -88,7 +88,8 @@ disable=raw-checker-failed, deprecated-pragma, use-symbolic-message-instead, missing-docstring, - invalid-name + invalid-name, + too-few-public-methods # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/deepparse/vectorizer/bpemb_vectorizer.py b/deepparse/vectorizer/bpemb_vectorizer.py index 98851a11..edd7f356 100644 --- a/deepparse/vectorizer/bpemb_vectorizer.py +++ b/deepparse/vectorizer/bpemb_vectorizer.py @@ -17,6 +17,10 @@ def __init__(self, embeddings_model: EmbeddingsModel) -> None: super().__init__(embeddings_model) self.padding_value = 0 + self._max_length = 0 + + def _reset_max_length(self): + self._max_length = 0 def __call__(self, addresses: List[str]) -> List[Tuple]: """ @@ -30,7 +34,7 @@ def __call__(self, addresses: List[str]) -> List[Tuple]: """ validate_data_to_parse(addresses) - self._max_length = 0 + self._reset_max_length() batch = [self._vectorize_sequence(address) for address in addresses] self._decomposed_sequence_padding(batch) return batch diff --git a/deepparse/vectorizer/vectorizer.py b/deepparse/vectorizer/vectorizer.py index 749add49..99926d8b 100644 --- a/deepparse/vectorizer/vectorizer.py +++ b/deepparse/vectorizer/vectorizer.py @@ -28,5 +28,3 @@ def __call__(self, addresses: List[str]) -> List: Return: The addresses elements (components) embeddings vector. """ - - pass From cd7909ecb3c427797581f951a1bdce6ab2d6ea8b Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 26 Jul 2022 12:09:56 -0400 Subject: [PATCH 031/195] fix torch member and parser modules --- .pylintrc | 4 +++- deepparse/converter/data_padding.py | 4 ++++ deepparse/parser/address_parser.py | 13 +++++++++++-- deepparse/parser/tools.py | 4 ++++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.pylintrc b/.pylintrc index 8d16a28e..7cbecb76 100644 --- a/.pylintrc +++ b/.pylintrc @@ -89,7 +89,9 @@ disable=raw-checker-failed, use-symbolic-message-instead, missing-docstring, invalid-name, - too-few-public-methods + too-few-public-methods, + attribute-defined-outside-init, + too-many-instance-attributes # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/deepparse/converter/data_padding.py b/deepparse/converter/data_padding.py index 3bfd7d18..c8960c29 100644 --- a/deepparse/converter/data_padding.py +++ b/deepparse/converter/data_padding.py @@ -1,6 +1,10 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + from typing import List, Tuple import numpy as np diff --git a/deepparse/parser/address_parser.py b/deepparse/parser/address_parser.py index f598bc90..1037a1ec 100644 --- a/deepparse/parser/address_parser.py +++ b/deepparse/parser/address_parser.py @@ -1,5 +1,13 @@ # pylint: disable=too-many-lines +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + +# Pylint raise error for an inconsistent-return-statements for the retrain function +# It must be due to the complex try, except else case. +# pylint: disable=inconsistent-return-statements + import contextlib import os import platform @@ -393,7 +401,8 @@ def retrain( layers_to_freeze: Union[str, None] = None, name_of_the_retrain_parser: Union[None, str] = None, ) -> List[Dict]: - # pylint: disable=too-many-arguments, line-too-long, too-many-locals, too-many-branches, too-many-statements + # pylint: disable=too-many-arguments, too-many-locals, too-many-branches, too-many-statements + """ Method to retrain the address parser model using a dataset with the same tags. We train using `experiment `_ from `poutyne `_ @@ -608,7 +617,7 @@ def retrain( if name_of_the_retrain_parser is not None: if len(name_of_the_retrain_parser.split(".")) > 1: raise ValueError( - "The name_of_the_retrain_parser should NOT include a file extension or a dot-like" "filename style." + "The name_of_the_retrain_parser should NOT include a file extension or a dot-like filename style." ) model_factory_dict = {"prediction_layer_len": 9} # We set the default output dim size diff --git a/deepparse/parser/tools.py b/deepparse/parser/tools.py index 23848cd9..9c539b53 100644 --- a/deepparse/parser/tools.py +++ b/deepparse/parser/tools.py @@ -1,3 +1,7 @@ +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import math import os from typing import List, Tuple, OrderedDict From b3a018fe64039230624e08ba53d7722503fbc92e Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 26 Jul 2022 12:43:07 -0400 Subject: [PATCH 032/195] refactor arguments init in cli and cycling import --- .pylintrc | 3 +- deepparse/cli/__init__.py | 1 - deepparse/cli/parse.py | 75 ++++-------- deepparse/cli/parser_arguments_adder.py | 107 ++++++++++++++++++ deepparse/cli/retrain.py | 83 ++++---------- deepparse/cli/test.py | 106 +++++------------ deepparse/cli/tools.py | 2 +- .../dataset_container/dataset_container.py | 2 +- 8 files changed, 176 insertions(+), 203 deletions(-) create mode 100644 deepparse/cli/parser_arguments_adder.py diff --git a/.pylintrc b/.pylintrc index 7cbecb76..a8972dfd 100644 --- a/.pylintrc +++ b/.pylintrc @@ -91,7 +91,8 @@ disable=raw-checker-failed, invalid-name, too-few-public-methods, attribute-defined-outside-init, - too-many-instance-attributes + too-many-instance-attributes, + cyclic-import # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/deepparse/cli/__init__.py b/deepparse/cli/__init__.py index 97d7a933..3c4e0ea6 100644 --- a/deepparse/cli/__init__.py +++ b/deepparse/cli/__init__.py @@ -3,4 +3,3 @@ from .parse import * from .retrain import * from .test import * -from .tools import * diff --git a/deepparse/cli/parse.py b/deepparse/cli/parse.py index b72fe068..21f30d39 100644 --- a/deepparse/cli/parse.py +++ b/deepparse/cli/parse.py @@ -3,7 +3,16 @@ import sys from functools import partial -from deepparse.cli.tools import ( +from .parser_arguments_adder import ( + add_device_arg, + add_csv_column_separator_arg, + add_csv_column_name_arg, + add_log_arg, + add_cache_dir_arg, + add_batch_size_arg, + add_path_to_retrained_model_arg, +) +from .tools import ( is_csv_path, is_pickle_path, to_csv, @@ -12,12 +21,11 @@ wrap, is_json_path, to_json, - bool_parse, replace_path_extension, attention_model_type_handling, ) -from deepparse.dataset_container import CSVDatasetContainer, PickleDatasetContainer -from deepparse.parser import AddressParser +from ..dataset_container import CSVDatasetContainer, PickleDatasetContainer +from ..parser import AddressParser def main(args=None) -> None: @@ -151,62 +159,19 @@ def get_parser() -> argparse.ArgumentParser: type=str, ) - parser.add_argument( - "--device", - help=wrap("The device to use. It can be 'cpu' or a GPU device index such as '0' or '1'. By default '0'."), - type=str, - default="0", - ) + add_path_to_retrained_model_arg(parser) - parser.add_argument( - "--batch_size", - help=wrap("The size of the batch (default is 32)."), - type=int, - default=32, - ) + add_device_arg(parser) - parser.add_argument( - "--path_to_retrained_model", - help=wrap("A path to a retrained model to use for parsing."), - type=str, - default=None, - ) + add_batch_size_arg(parser) - parser.add_argument( - "--csv_column_name", - help=wrap( - "The column name to extract address in the CSV. Need to be specified if the provided dataset_path " - "leads to a CSV file." - ), - type=str, - default=None, - ) + add_csv_column_name_arg(parser) - parser.add_argument( - "--csv_column_separator", - help=wrap( - "The column separator for the dataset container will only be used if the dataset is a CSV one." - " By default '\t'." - ), - default="\t", - ) + add_csv_column_separator_arg(parser) - parser.add_argument( - "--log", - help=wrap( - "Either or not to log the parsing process into a '.log' file exported at the same place as the " - "parsed data using the same name as the export file. " - "The bool value can be (not case sensitive) 'true/false', 't/f', 'yes/no', 'y/n' or '0/1'." - ), - type=bool_parse, - default="True", - ) - parser.add_argument( - "--cache_dir", - type=str, - default=None, - help="To change the default cache directory (default to None e.g. default path).", - ) + add_log_arg(parser) + + add_cache_dir_arg(parser) return parser diff --git a/deepparse/cli/parser_arguments_adder.py b/deepparse/cli/parser_arguments_adder.py new file mode 100644 index 00000000..1ce12720 --- /dev/null +++ b/deepparse/cli/parser_arguments_adder.py @@ -0,0 +1,107 @@ +from argparse import ArgumentParser + +from . import wrap, bool_parse + + +def add_base_parsing_model_arg(parser: ArgumentParser) -> None: + parser.add_argument( + "base_parsing_model", + choices=[ + "fasttext", + "fasttext-attention", + "fasttext-light", + "bpemb", + "bpemb-attention", + ], + help=wrap("The base parsing module to use for retraining."), + ) + + +def add_device_arg(parser: ArgumentParser) -> None: + parser.add_argument( + "--device", + help=wrap("The device to use. It can be 'cpu' or a GPU device index such as '0' or '1'. By default '0'."), + type=str, + default="0", + ) + + +def add_csv_column_name_arg(parser: ArgumentParser) -> None: + parser.add_argument( + "--csv_column_name", + help=wrap( + "The column name to extract address in the CSV. Need to be specified if the provided dataset_path " + "leads to a CSV file." + ), + type=str, + default=None, + ) + + +def add_seed_arg(parser: ArgumentParser) -> None: + parser.add_argument( + "--seed", + help=wrap("The seed to use to make the sampling deterministic (default 42)."), + type=int, + default=42, + ) + + +def add_csv_column_separator_arg(parser: ArgumentParser) -> None: + parser.add_argument( + "--csv_column_separator", + help=wrap( + "The column separator for the dataset container will only be used if the dataset is a CSV one." + " By default '\t'." + ), + default="\t", + ) + + +def add_log_arg(parser: ArgumentParser) -> None: + parser.add_argument( + "--log", + help=wrap( + "Either or not to log the parsing process into a '.log' file exported at the same place as the " + "parsed data using the same name as the export file. " + "The bool value can be (not case sensitive) 'true/false', 't/f', 'yes/no', 'y/n' or '0/1'." + ), + type=bool_parse, + default="True", + ) + + +def add_cache_dir_arg(parser: ArgumentParser) -> None: + parser.add_argument( + "--cache_dir", + type=str, + default=None, + help="To change the default cache directory (default to None e.g. default path).", + ) + + +def add_batch_size_arg(parser: ArgumentParser) -> None: + parser.add_argument( + "--batch_size", + help=wrap("The size of the batch (default is 32)."), + type=int, + default=32, + ) + + +def add_path_to_retrained_model_arg(parser: ArgumentParser) -> None: + parser.add_argument( + "--path_to_retrained_model", + help=wrap("A path to a retrained model to use for testing."), + type=str, + default=None, + ) + + +def add_num_workers_arg(parser: ArgumentParser) -> None: + parser.add_argument( + "--num_workers", + help=wrap("The number of workers to use for the data loader (default is 1 worker)."), + type=int, + default=1, + ) diff --git a/deepparse/cli/retrain.py b/deepparse/cli/retrain.py index 2860d0fe..388c68ec 100644 --- a/deepparse/cli/retrain.py +++ b/deepparse/cli/retrain.py @@ -2,15 +2,21 @@ import sys from typing import Dict -from deepparse.cli.tools import ( +from .parser_arguments_adder import ( + add_seed_arg, + add_batch_size_arg, + add_base_parsing_model_arg, + add_num_workers_arg, add_device_arg, add_csv_column_name_arg, add_csv_column_separator_arg, add_cache_dir_arg, +) +from .tools import ( is_csv_path, is_pickle_path, wrap, bool_parse, attention_model_type_handling, ) -from deepparse.dataset_container import CSVDatasetContainer, PickleDatasetContainer -from deepparse.parser import AddressParser +from ..dataset_container import CSVDatasetContainer, PickleDatasetContainer +from ..parser import AddressParser _retrain_parameters = [ "train_ratio", @@ -120,17 +126,8 @@ def get_parser() -> argparse.ArgumentParser: """Return ArgumentParser for the cli.""" parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument( - "base_parsing_model", - choices=[ - "fasttext", - "fasttext-attention", - "fasttext-light", - "bpemb", - "bpemb-attention", - ], - help=wrap("The base parsing module to use for retraining."), - ) + + add_base_parsing_model_arg(parser) parser.add_argument( "train_dataset_path", @@ -148,12 +145,7 @@ def get_parser() -> argparse.ArgumentParser: default=0.8, ) - parser.add_argument( - "--batch_size", - help=wrap("The size of the batch (default is 32)."), - type=int, - default=32, - ) + add_batch_size_arg(parser) parser.add_argument( "--epochs", @@ -162,12 +154,7 @@ def get_parser() -> argparse.ArgumentParser: default=5, ) - parser.add_argument( - "--num_workers", - help=wrap("The number of workers to use for the data loader (default is 1 worker)."), - type=int, - default=1, - ) + add_num_workers_arg(parser) parser.add_argument( "--learning_rate", @@ -176,13 +163,6 @@ def get_parser() -> argparse.ArgumentParser: default=0.01, ) - parser.add_argument( - "--seed", - help=wrap("The seed to use (default 42)."), - type=int, - default=42, - ) - parser.add_argument( "--logging_path", help=wrap( @@ -226,40 +206,15 @@ def get_parser() -> argparse.ArgumentParser: type=str, ) - parser.add_argument( - "--device", - help=wrap("The device to use. It can be 'cpu' or a GPU device index such as '0' or '1'. By default, '0'."), - type=str, - default="0", - ) + add_seed_arg(parser) - parser.add_argument( - "--csv_column_names", - help=wrap( - "The column names to extract address and tags in the CSV. Need to be specified if the provided " - "dataset_path leads to a CSV file. Column names have to be separated by a whitespace. For" - "example, --csv_column_names column1 column2. By default, None." - ), - default=None, - nargs=2, - type=str, - ) + add_device_arg(parser) - parser.add_argument( - "--csv_column_separator", - help=wrap( - "The column separator for the dataset container will only be used if the dataset is a CSV one." - " By default, '\t'." - ), - default="\t", - ) + add_csv_column_name_arg(parser) - parser.add_argument( - "--cache_dir", - type=str, - default=None, - help="To change the default cache directory (default to None e.g. default path).", - ) + add_csv_column_separator_arg(parser) + + add_cache_dir_arg(parser) return parser diff --git a/deepparse/cli/test.py b/deepparse/cli/test.py index 39185f2a..3cda751e 100644 --- a/deepparse/cli/test.py +++ b/deepparse/cli/test.py @@ -4,17 +4,28 @@ import pandas as pd -from deepparse.cli.tools import ( +from .parser_arguments_adder import ( + add_csv_column_name_arg, + add_csv_column_separator_arg, + add_log_arg, + add_cache_dir_arg, + add_seed_arg, + add_device_arg, + add_batch_size_arg, + add_path_to_retrained_model_arg, + add_base_parsing_model_arg, + add_num_workers_arg, +) +from .tools import ( is_csv_path, is_pickle_path, wrap, - bool_parse, attention_model_type_handling, generate_export_path, replace_path_extension, ) -from deepparse.dataset_container import CSVDatasetContainer, PickleDatasetContainer -from deepparse.parser import AddressParser +from ..dataset_container import CSVDatasetContainer, PickleDatasetContainer +from ..parser import AddressParser def main(args=None) -> None: @@ -113,17 +124,8 @@ def get_parser() -> argparse.ArgumentParser: """Return ArgumentParser for the cli.""" parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument( - "base_parsing_model", - choices=[ - "fasttext", - "fasttext-attention", - "fasttext-light", - "bpemb", - "bpemb-attention", - ], - help=wrap("The base parsing module to use for testing."), - ) + + add_base_parsing_model_arg(parser) parser.add_argument( "test_dataset_path", @@ -131,79 +133,23 @@ def get_parser() -> argparse.ArgumentParser: type=str, ) - parser.add_argument( - "--device", - help=wrap("The device to use. It can be 'cpu' or a GPU device index such as '0' or '1'. By default '0'."), - type=str, - default="0", - ) + add_path_to_retrained_model_arg(parser) - parser.add_argument( - "--path_to_retrained_model", - help=wrap("A path to a retrained model to use for testing."), - type=str, - default=None, - ) + add_batch_size_arg(parser) - parser.add_argument( - "--batch_size", - help=wrap("The size of the batch (default is 32)."), - type=int, - default=32, - ) + add_num_workers_arg(parser) - parser.add_argument( - "--num_workers", - help=wrap("The number of workers to use for the data loader (default is 1 worker)."), - type=int, - default=1, - ) + add_seed_arg(parser) - parser.add_argument( - "--seed", - help=wrap("The seed to use to make the sampling deterministic (default 42)."), - type=int, - default=42, - ) + add_device_arg(parser) - parser.add_argument( - "--csv_column_names", - help=wrap( - "The column names to extract address and tags in the CSV. Need to be specified if the provided " - "dataset_path leads to a CSV file. Column names have to be separated by a whitespace. For" - "example, --csv_column_names column1 column2. By default, None." - ), - default=None, - nargs=2, - type=str, - ) + add_csv_column_name_arg(parser) - parser.add_argument( - "--csv_column_separator", - help=wrap( - "The column separator for the dataset container will only be used if the dataset is a CSV one." - " By default '\t'." - ), - default="\t", - ) + add_csv_column_separator_arg(parser) - parser.add_argument( - "--log", - help=wrap( - "Either or not to log the parsing process into a '.log' file exported at the same place as the " - "parsed data using the same name as the export file. " - "The bool value can be (not case sensitive) 'true/false', 't/f', 'yes/no', 'y/n' or '0/1'." - ), - type=bool_parse, - default="True", - ) + add_log_arg(parser) - parser.add_argument( - "--cache_dir", - type=str, - default=None, - help="To change the default cache directory (default to None e.g. default path).", - ) + add_cache_dir_arg(parser) return parser diff --git a/deepparse/cli/tools.py b/deepparse/cli/tools.py index a8986d7e..02ac7708 100644 --- a/deepparse/cli/tools.py +++ b/deepparse/cli/tools.py @@ -7,7 +7,7 @@ import pandas as pd -from deepparse.parser import FormattedParsedAddress +from ..parser import FormattedParsedAddress def is_csv_path(export_file_name: str) -> bool: diff --git a/deepparse/dataset_container/dataset_container.py b/deepparse/dataset_container/dataset_container.py index 3f69c438..1e33b420 100644 --- a/deepparse/dataset_container/dataset_container.py +++ b/deepparse/dataset_container/dataset_container.py @@ -241,7 +241,7 @@ def __init__( if is_training_container: if isinstance(column_names, str): raise ValueError( - "When the dataset is a training container, the column names should be a list of" "column name." + "When the dataset is a training container, the column names should be a list of column name." ) if len(column_names) != 2: raise ValueError("When the dataset is a training container, two column names must be provided.") From d06cb9ca981c768077a1dd13ea9bf1d63ef33f35 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 26 Jul 2022 12:48:46 -0400 Subject: [PATCH 033/195] fix circular import --- deepparse/cli/parser_arguments_adder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepparse/cli/parser_arguments_adder.py b/deepparse/cli/parser_arguments_adder.py index 1ce12720..a8bf3657 100644 --- a/deepparse/cli/parser_arguments_adder.py +++ b/deepparse/cli/parser_arguments_adder.py @@ -1,6 +1,6 @@ from argparse import ArgumentParser -from . import wrap, bool_parse +from .tools import wrap, bool_parse def add_base_parsing_model_arg(parser: ArgumentParser) -> None: From d3c63be069ef21f6f7a95b2723b7f3fa5671a8f9 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 26 Jul 2022 12:52:49 -0400 Subject: [PATCH 034/195] fix last pylint errors --- deepparse/cli/download.py | 11 ++++------- deepparse/cli/parse.py | 11 ++++------- deepparse/cli/parser_arguments_adder.py | 18 +++++++++++------- deepparse/cli/retrain.py | 8 +++++++- deepparse/cli/test.py | 2 ++ deepparse/network/bpemb_seq2seq.py | 2 +- deepparse/network/fasttext_seq2seq.py | 2 +- 7 files changed, 30 insertions(+), 24 deletions(-) diff --git a/deepparse/cli/download.py b/deepparse/cli/download.py index 2f77138e..75f5bc76 100644 --- a/deepparse/cli/download.py +++ b/deepparse/cli/download.py @@ -1,3 +1,5 @@ +# pylint: disable=duplicate-code + import argparse import os import sys @@ -11,6 +13,7 @@ download_fasttext_embeddings, download_weights, ) +from .parser_arguments_adder import choices def main(args=None) -> None: @@ -58,13 +61,7 @@ def get_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser() parser.add_argument( "model_type", - choices=[ - "fasttext", - "fasttext-attention", - "fasttext-light", - "bpemb", - "bpemb-attention", - ], + choices=choices, help="The model type to download.", ) parser.add_argument( diff --git a/deepparse/cli/parse.py b/deepparse/cli/parse.py index 21f30d39..8022ecec 100644 --- a/deepparse/cli/parse.py +++ b/deepparse/cli/parse.py @@ -1,3 +1,5 @@ +# pylint: disable=duplicate-code + import argparse import logging import sys @@ -11,6 +13,7 @@ add_cache_dir_arg, add_batch_size_arg, add_path_to_retrained_model_arg, + choices, ) from .tools import ( is_csv_path, @@ -131,13 +134,7 @@ def get_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) parser.add_argument( "parsing_model", - choices=[ - "fasttext", - "fasttext-attention", - "fasttext-light", - "bpemb", - "bpemb-attention", - ], + choices=choices, help=wrap("The parsing module to use."), ) diff --git a/deepparse/cli/parser_arguments_adder.py b/deepparse/cli/parser_arguments_adder.py index a8bf3657..bd4f65d5 100644 --- a/deepparse/cli/parser_arguments_adder.py +++ b/deepparse/cli/parser_arguments_adder.py @@ -1,18 +1,22 @@ +# pylint: disable=duplicate-code + from argparse import ArgumentParser from .tools import wrap, bool_parse +choices = [ + "fasttext", + "fasttext-attention", + "fasttext-light", + "bpemb", + "bpemb-attention", +] + def add_base_parsing_model_arg(parser: ArgumentParser) -> None: parser.add_argument( "base_parsing_model", - choices=[ - "fasttext", - "fasttext-attention", - "fasttext-light", - "bpemb", - "bpemb-attention", - ], + choices=choices, help=wrap("The base parsing module to use for retraining."), ) diff --git a/deepparse/cli/retrain.py b/deepparse/cli/retrain.py index 388c68ec..6fd59b7b 100644 --- a/deepparse/cli/retrain.py +++ b/deepparse/cli/retrain.py @@ -1,3 +1,5 @@ +# pylint: disable=duplicate-code + import argparse import sys from typing import Dict @@ -6,7 +8,11 @@ add_seed_arg, add_batch_size_arg, add_base_parsing_model_arg, - add_num_workers_arg, add_device_arg, add_csv_column_name_arg, add_csv_column_separator_arg, add_cache_dir_arg, + add_num_workers_arg, + add_device_arg, + add_csv_column_name_arg, + add_csv_column_separator_arg, + add_cache_dir_arg, ) from .tools import ( is_csv_path, diff --git a/deepparse/cli/test.py b/deepparse/cli/test.py index 3cda751e..8a26fd42 100644 --- a/deepparse/cli/test.py +++ b/deepparse/cli/test.py @@ -1,3 +1,5 @@ +# pylint: disable=duplicate-code + import argparse import logging import sys diff --git a/deepparse/network/bpemb_seq2seq.py b/deepparse/network/bpemb_seq2seq.py index 2ca26c81..de0851f3 100644 --- a/deepparse/network/bpemb_seq2seq.py +++ b/deepparse/network/bpemb_seq2seq.py @@ -1,4 +1,4 @@ -# pylint: disable=too-many-arguments +# pylint: disable=too-many-arguments, duplicate-code # Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event # if not the case. diff --git a/deepparse/network/fasttext_seq2seq.py b/deepparse/network/fasttext_seq2seq.py index 598032ca..6030d8c6 100644 --- a/deepparse/network/fasttext_seq2seq.py +++ b/deepparse/network/fasttext_seq2seq.py @@ -1,4 +1,4 @@ -# pylint: disable=too-many-arguments +# pylint: disable=too-many-arguments, duplicate-code # Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event # if not the case. From 50ad57d42f763507bd5f54f223e6fb7f6c0f0f5a Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 26 Jul 2022 12:58:19 -0400 Subject: [PATCH 035/195] fix error in csv column names versus column name --- deepparse/cli/parser_arguments_adder.py | 13 +++++++++++++ deepparse/cli/retrain.py | 4 ++-- deepparse/cli/test.py | 4 ++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/deepparse/cli/parser_arguments_adder.py b/deepparse/cli/parser_arguments_adder.py index bd4f65d5..94d19fa0 100644 --- a/deepparse/cli/parser_arguments_adder.py +++ b/deepparse/cli/parser_arguments_adder.py @@ -42,6 +42,19 @@ def add_csv_column_name_arg(parser: ArgumentParser) -> None: ) +def add_csv_column_names_arg(parser: ArgumentParser) -> None: + parser.add_argument( + "--csv_column_names", + help=wrap( + "The column names to extract address and tags in the CSV. Need to be specified if the provided " + "dataset_path leads to a CSV file. Column names have to be separated by a whitespace. For" + "example, --csv_column_names column1 column2. By default, None." + ), + type=str, + default=None, + ) + + def add_seed_arg(parser: ArgumentParser) -> None: parser.add_argument( "--seed", diff --git a/deepparse/cli/retrain.py b/deepparse/cli/retrain.py index 6fd59b7b..823a608f 100644 --- a/deepparse/cli/retrain.py +++ b/deepparse/cli/retrain.py @@ -10,9 +10,9 @@ add_base_parsing_model_arg, add_num_workers_arg, add_device_arg, - add_csv_column_name_arg, add_csv_column_separator_arg, add_cache_dir_arg, + add_csv_column_names_arg, ) from .tools import ( is_csv_path, @@ -216,7 +216,7 @@ def get_parser() -> argparse.ArgumentParser: add_device_arg(parser) - add_csv_column_name_arg(parser) + add_csv_column_names_arg(parser) add_csv_column_separator_arg(parser) diff --git a/deepparse/cli/test.py b/deepparse/cli/test.py index 8a26fd42..436c6848 100644 --- a/deepparse/cli/test.py +++ b/deepparse/cli/test.py @@ -7,7 +7,6 @@ import pandas as pd from .parser_arguments_adder import ( - add_csv_column_name_arg, add_csv_column_separator_arg, add_log_arg, add_cache_dir_arg, @@ -17,6 +16,7 @@ add_path_to_retrained_model_arg, add_base_parsing_model_arg, add_num_workers_arg, + add_csv_column_names_arg, ) from .tools import ( is_csv_path, @@ -145,7 +145,7 @@ def get_parser() -> argparse.ArgumentParser: add_device_arg(parser) - add_csv_column_name_arg(parser) + add_csv_column_names_arg(parser) add_csv_column_separator_arg(parser) From 69c8f1a27c34504ee639db6b873a298e3c0f575b Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 26 Jul 2022 12:59:39 -0400 Subject: [PATCH 036/195] fix list csv column names missing nargs --- deepparse/cli/parser_arguments_adder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deepparse/cli/parser_arguments_adder.py b/deepparse/cli/parser_arguments_adder.py index 94d19fa0..051bffbb 100644 --- a/deepparse/cli/parser_arguments_adder.py +++ b/deepparse/cli/parser_arguments_adder.py @@ -50,8 +50,9 @@ def add_csv_column_names_arg(parser: ArgumentParser) -> None: "dataset_path leads to a CSV file. Column names have to be separated by a whitespace. For" "example, --csv_column_names column1 column2. By default, None." ), - type=str, default=None, + nargs=2, + type=str, ) From 80bec06c13c10a9d462eaf45bd779661c1aef354 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 26 Jul 2022 13:10:07 -0400 Subject: [PATCH 037/195] remove duplicate detection and fix with statement for temporary directory --- .pylintrc | 3 ++- deepparse/cli/download.py | 2 -- deepparse/cli/parse.py | 2 -- deepparse/cli/parser_arguments_adder.py | 2 -- deepparse/cli/retrain.py | 2 -- deepparse/cli/test.py | 2 -- tests/cli/test_download.py | 3 +++ tests/cli/test_parse.py | 3 +++ tests/cli/test_retrain.py | 3 +++ tests/cli/test_tools.py | 3 +++ tests/dataset_container/test_dataset_container.py | 3 +++ .../integration/test_integration_bpemb_embeddings_model.py | 3 +++ .../integration/test_integration_fasttext_embeddings_model.py | 3 +++ tests/network/integration/base.py | 4 ++++ tests/network/integration/test_integration_decoder.py | 4 ++++ tests/network/integration/test_integration_encoder.py | 4 ++++ tests/parser/base.py | 3 +++ tests/parser/integration/base_predict.py | 3 +++ tests/parser/integration/base_retrain.py | 3 +++ .../test_integration_address_parser_new_params_new_tags.py | 3 +++ tests/parser/test_address_parser.py | 3 +++ tests/parser/test_address_parser_retrain_api.py | 4 ++++ tests/parser/test_tools.py | 4 ++++ tests/test_fasttext_tools.py | 3 +++ tests/test_tools.py | 3 +++ 25 files changed, 64 insertions(+), 11 deletions(-) diff --git a/.pylintrc b/.pylintrc index a8972dfd..330e86d6 100644 --- a/.pylintrc +++ b/.pylintrc @@ -92,7 +92,8 @@ disable=raw-checker-failed, too-few-public-methods, attribute-defined-outside-init, too-many-instance-attributes, - cyclic-import + cyclic-import, + duplicate-code # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/deepparse/cli/download.py b/deepparse/cli/download.py index 75f5bc76..3a9db8fe 100644 --- a/deepparse/cli/download.py +++ b/deepparse/cli/download.py @@ -1,5 +1,3 @@ -# pylint: disable=duplicate-code - import argparse import os import sys diff --git a/deepparse/cli/parse.py b/deepparse/cli/parse.py index 8022ecec..34113e83 100644 --- a/deepparse/cli/parse.py +++ b/deepparse/cli/parse.py @@ -1,5 +1,3 @@ -# pylint: disable=duplicate-code - import argparse import logging import sys diff --git a/deepparse/cli/parser_arguments_adder.py b/deepparse/cli/parser_arguments_adder.py index 051bffbb..c50426f0 100644 --- a/deepparse/cli/parser_arguments_adder.py +++ b/deepparse/cli/parser_arguments_adder.py @@ -1,5 +1,3 @@ -# pylint: disable=duplicate-code - from argparse import ArgumentParser from .tools import wrap, bool_parse diff --git a/deepparse/cli/retrain.py b/deepparse/cli/retrain.py index 823a608f..dde35544 100644 --- a/deepparse/cli/retrain.py +++ b/deepparse/cli/retrain.py @@ -1,5 +1,3 @@ -# pylint: disable=duplicate-code - import argparse import sys from typing import Dict diff --git a/deepparse/cli/test.py b/deepparse/cli/test.py index 436c6848..5f866864 100644 --- a/deepparse/cli/test.py +++ b/deepparse/cli/test.py @@ -1,5 +1,3 @@ -# pylint: disable=duplicate-code - import argparse import logging import sys diff --git a/tests/cli/test_download.py b/tests/cli/test_download.py index 3d35e60e..f19803a3 100644 --- a/tests/cli/test_download.py +++ b/tests/cli/test_download.py @@ -1,6 +1,9 @@ # Since we use a patch as model mock we skip the unused argument error # pylint: disable=unused-argument +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os import unittest from tempfile import TemporaryDirectory diff --git a/tests/cli/test_parse.py b/tests/cli/test_parse.py index f7c0da84..898ba61f 100644 --- a/tests/cli/test_parse.py +++ b/tests/cli/test_parse.py @@ -1,5 +1,8 @@ # pylint: disable=too-many-arguments, too-many-locals +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import logging import os import unittest diff --git a/tests/cli/test_retrain.py b/tests/cli/test_retrain.py index 23823294..7929cc1f 100644 --- a/tests/cli/test_retrain.py +++ b/tests/cli/test_retrain.py @@ -1,5 +1,8 @@ # pylint: disable=too-many-arguments, too-many-locals +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os import unittest from tempfile import TemporaryDirectory diff --git a/tests/cli/test_tools.py b/tests/cli/test_tools.py index d6c5e3ed..1edda80d 100644 --- a/tests/cli/test_tools.py +++ b/tests/cli/test_tools.py @@ -1,5 +1,8 @@ # pylint: disable=no-member, too-many-public-methods +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import argparse import json import os diff --git a/tests/dataset_container/test_dataset_container.py b/tests/dataset_container/test_dataset_container.py index b8473627..82bac989 100644 --- a/tests/dataset_container/test_dataset_container.py +++ b/tests/dataset_container/test_dataset_container.py @@ -1,5 +1,8 @@ # pylint: disable=unbalanced-tuple-unpacking +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os import pickle import unittest diff --git a/tests/embeddings_models/integration/test_integration_bpemb_embeddings_model.py b/tests/embeddings_models/integration/test_integration_bpemb_embeddings_model.py index ba53446c..1aafdae9 100644 --- a/tests/embeddings_models/integration/test_integration_bpemb_embeddings_model.py +++ b/tests/embeddings_models/integration/test_integration_bpemb_embeddings_model.py @@ -1,3 +1,6 @@ +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os import platform from tempfile import TemporaryDirectory diff --git a/tests/embeddings_models/integration/test_integration_fasttext_embeddings_model.py b/tests/embeddings_models/integration/test_integration_fasttext_embeddings_model.py index 483a005b..dd966074 100644 --- a/tests/embeddings_models/integration/test_integration_fasttext_embeddings_model.py +++ b/tests/embeddings_models/integration/test_integration_fasttext_embeddings_model.py @@ -1,3 +1,6 @@ +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os import platform from tempfile import TemporaryDirectory diff --git a/tests/network/integration/base.py b/tests/network/integration/base.py index a2624cde..daff21b8 100644 --- a/tests/network/integration/base.py +++ b/tests/network/integration/base.py @@ -1,5 +1,9 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable + +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os import pickle from tempfile import TemporaryDirectory diff --git a/tests/network/integration/test_integration_decoder.py b/tests/network/integration/test_integration_decoder.py index 4881c6ad..b1f1359b 100644 --- a/tests/network/integration/test_integration_decoder.py +++ b/tests/network/integration/test_integration_decoder.py @@ -1,5 +1,9 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable + +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os import pickle import unittest diff --git a/tests/network/integration/test_integration_encoder.py b/tests/network/integration/test_integration_encoder.py index bf2b67f6..43fdb1a2 100644 --- a/tests/network/integration/test_integration_encoder.py +++ b/tests/network/integration/test_integration_encoder.py @@ -1,5 +1,9 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable + +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os import pickle import unittest diff --git a/tests/parser/base.py b/tests/parser/base.py index 00e70275..2d1631e7 100644 --- a/tests/parser/base.py +++ b/tests/parser/base.py @@ -1,6 +1,9 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable, too-many-public-methods, no-name-in-module +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os from tempfile import TemporaryDirectory from unittest import TestCase diff --git a/tests/parser/integration/base_predict.py b/tests/parser/integration/base_predict.py index 8f703c9c..f92b26ff 100644 --- a/tests/parser/integration/base_predict.py +++ b/tests/parser/integration/base_predict.py @@ -2,6 +2,9 @@ # no-member skip is so because child define the training_container in setup # pylint: disable=not-callable, too-many-public-methods, no-member, too-many-arguments +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os from tempfile import TemporaryDirectory from typing import List diff --git a/tests/parser/integration/base_retrain.py b/tests/parser/integration/base_retrain.py index 229b1dde..31ab11c1 100644 --- a/tests/parser/integration/base_retrain.py +++ b/tests/parser/integration/base_retrain.py @@ -2,6 +2,9 @@ # no-member skip is so because child define the training_container in setup # pylint: disable=not-callable, too-many-public-methods, no-member +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os from tempfile import TemporaryDirectory from unittest import TestCase diff --git a/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py b/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py index 3d37c98f..e666caac 100644 --- a/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py +++ b/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py @@ -2,6 +2,9 @@ # no-member skip is so because child define the training_container in setup # pylint: disable=not-callable, too-many-public-methods, no-member, too-many-arguments +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os from tempfile import TemporaryDirectory from unittest import TestCase, skipIf diff --git a/tests/parser/test_address_parser.py b/tests/parser/test_address_parser.py index 6ae37aaf..c629df56 100644 --- a/tests/parser/test_address_parser.py +++ b/tests/parser/test_address_parser.py @@ -1,6 +1,9 @@ # Since we use a patch as model mock we skip the unused argument error # pylint: disable=unused-argument, no-member, too-many-public-methods, too-many-lines +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os import unittest from tempfile import TemporaryDirectory diff --git a/tests/parser/test_address_parser_retrain_api.py b/tests/parser/test_address_parser_retrain_api.py index 3ebafcb4..ab42d0c8 100644 --- a/tests/parser/test_address_parser_retrain_api.py +++ b/tests/parser/test_address_parser_retrain_api.py @@ -1,5 +1,9 @@ # Since we use a patch as model mock we skip the unused argument error # pylint: disable=unused-argument, too-many-arguments, too-many-public-methods, protected-access, too-many-lines + +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os import platform import unittest diff --git a/tests/parser/test_tools.py b/tests/parser/test_tools.py index 6c2f7f96..b1cb232d 100644 --- a/tests/parser/test_tools.py +++ b/tests/parser/test_tools.py @@ -1,4 +1,8 @@ # pylint: disable=too-many-public-methods + +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os import unittest from tempfile import TemporaryDirectory diff --git a/tests/test_fasttext_tools.py b/tests/test_fasttext_tools.py index 469f3803..4f90a782 100644 --- a/tests/test_fasttext_tools.py +++ b/tests/test_fasttext_tools.py @@ -1,6 +1,9 @@ # Since we use a patch as model mock we skip the unused argument error # pylint: disable=unused-argument +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import gzip import os import unittest diff --git a/tests/test_tools.py b/tests/test_tools.py index 9dbc0b22..e0173fff 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -1,6 +1,9 @@ # Since we use a patch to mock verify last we skip the unused argument error # pylint: disable=unused-argument, too-many-public-methods +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + import os import unittest from tempfile import TemporaryDirectory From 831b668a957262c28a04e34c7bb80d15f35297e6 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 26 Jul 2022 13:24:16 -0400 Subject: [PATCH 038/195] fix oylint on test --- tests/comparer/test_addresses_comparer.py | 4 ++++ tests/converter/test_data_padding.py | 5 +++++ tests/metrics/test_accuracy.py | 5 +++++ tests/metrics/test_nll_loss.py | 5 +++++ tests/network/base.py | 7 +++++++ tests/network/integration/base.py | 4 ++++ tests/network/integration/test_integration_decoder.py | 4 ++++ .../integration/test_integration_embedding_network.py | 5 +++++ tests/network/integration/test_integration_encoder.py | 4 ++++ .../test_integration_seq2seq_bpemb_model_cpu.py | 5 +++++ .../test_integration_seq2seq_bpemb_model_gpu.py | 5 +++++ .../test_integration_seq2seq_fasttext_model_cpu.py | 5 +++++ .../test_integration_seq2seq_fasttext_model_gpu.py | 5 +++++ .../integration/test_integration_seq2seq_model_cpu.py | 5 +++++ .../integration/test_integration_seq2seq_model_gpu.py | 5 +++++ tests/network/test_bpemb_seq2seq_model_cpu.py | 8 ++++++++ tests/network/test_bpemb_seq2seq_model_gpu.py | 8 ++++++++ tests/network/test_decoder.py | 7 +++++++ tests/network/test_fasttext_seq2seq_model_cpu.py | 8 ++++++++ tests/network/test_fasttext_seq2seq_model_gpu.py | 8 ++++++++ tests/network/test_seq2seq.py | 8 ++++++++ tests/parser/base.py | 7 +++++++ .../integration/test_integration_address_parser_gpu.py | 4 ++++ tests/parser/test_address_parser.py | 10 ++++++++++ tests/parser/test_address_parser_retrain_api.py | 4 ++++ tests/parser/test_address_parser_test_api.py | 5 +++++ tests/parser/test_formatted_parsed_address.py | 3 +++ tests/parser/test_tools.py | 5 +++++ tests/parser/test_user_fromatted_parsed_address.py | 3 +++ tests/tools.py | 4 ++++ 30 files changed, 165 insertions(+) diff --git a/tests/comparer/test_addresses_comparer.py b/tests/comparer/test_addresses_comparer.py index 0a847d8d..73f51446 100644 --- a/tests/comparer/test_addresses_comparer.py +++ b/tests/comparer/test_addresses_comparer.py @@ -1,5 +1,9 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable, too-many-public-methods + +# Pylint raise error for the call method mocking +# pylint: disable=unnecessary-dunder-call + import unittest from unittest import TestCase from unittest.mock import MagicMock diff --git a/tests/converter/test_data_padding.py b/tests/converter/test_data_padding.py index 3b9614dc..b2b32595 100644 --- a/tests/converter/test_data_padding.py +++ b/tests/converter/test_data_padding.py @@ -1,5 +1,10 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable, too-many-public-methods + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import unittest from unittest import TestCase diff --git a/tests/metrics/test_accuracy.py b/tests/metrics/test_accuracy.py index 4252bbf1..1fbefd37 100644 --- a/tests/metrics/test_accuracy.py +++ b/tests/metrics/test_accuracy.py @@ -1,5 +1,10 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import unittest from unittest import TestCase diff --git a/tests/metrics/test_nll_loss.py b/tests/metrics/test_nll_loss.py index ca6ec1cc..bfad334f 100644 --- a/tests/metrics/test_nll_loss.py +++ b/tests/metrics/test_nll_loss.py @@ -1,5 +1,10 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import unittest from unittest import TestCase diff --git a/tests/network/base.py b/tests/network/base.py index 9b365167..e8793ede 100644 --- a/tests/network/base.py +++ b/tests/network/base.py @@ -1,6 +1,13 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + +# Pylint raise error for the call method mocking +# pylint: disable=unnecessary-dunder-call + import os from unittest import TestCase from unittest.mock import MagicMock diff --git a/tests/network/integration/base.py b/tests/network/integration/base.py index daff21b8..7d689644 100644 --- a/tests/network/integration/base.py +++ b/tests/network/integration/base.py @@ -4,6 +4,10 @@ # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import os import pickle from tempfile import TemporaryDirectory diff --git a/tests/network/integration/test_integration_decoder.py b/tests/network/integration/test_integration_decoder.py index b1f1359b..7eb16b0b 100644 --- a/tests/network/integration/test_integration_decoder.py +++ b/tests/network/integration/test_integration_decoder.py @@ -4,6 +4,10 @@ # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import os import pickle import unittest diff --git a/tests/network/integration/test_integration_embedding_network.py b/tests/network/integration/test_integration_embedding_network.py index 1549160a..7e78fd52 100644 --- a/tests/network/integration/test_integration_embedding_network.py +++ b/tests/network/integration/test_integration_embedding_network.py @@ -1,4 +1,9 @@ # pylint: disable=line-too-long + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import unittest from unittest import TestCase diff --git a/tests/network/integration/test_integration_encoder.py b/tests/network/integration/test_integration_encoder.py index 43fdb1a2..ab5dec7c 100644 --- a/tests/network/integration/test_integration_encoder.py +++ b/tests/network/integration/test_integration_encoder.py @@ -4,6 +4,10 @@ # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import os import pickle import unittest diff --git a/tests/network/integration/test_integration_seq2seq_bpemb_model_cpu.py b/tests/network/integration/test_integration_seq2seq_bpemb_model_cpu.py index 545396e5..39803a65 100644 --- a/tests/network/integration/test_integration_seq2seq_bpemb_model_cpu.py +++ b/tests/network/integration/test_integration_seq2seq_bpemb_model_cpu.py @@ -1,6 +1,11 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # We also skip protected-access since we test the encoder and decoder step # pylint: disable=not-callable, protected-access + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import os import unittest from unittest import skipIf diff --git a/tests/network/integration/test_integration_seq2seq_bpemb_model_gpu.py b/tests/network/integration/test_integration_seq2seq_bpemb_model_gpu.py index 861d0e38..7385764c 100644 --- a/tests/network/integration/test_integration_seq2seq_bpemb_model_gpu.py +++ b/tests/network/integration/test_integration_seq2seq_bpemb_model_gpu.py @@ -1,6 +1,11 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # We also skip protected-access since we test the encoder and decoder step # pylint: disable=not-callable, protected-access + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import os import unittest from unittest import skipIf diff --git a/tests/network/integration/test_integration_seq2seq_fasttext_model_cpu.py b/tests/network/integration/test_integration_seq2seq_fasttext_model_cpu.py index cbb5f16e..3ba73b8c 100644 --- a/tests/network/integration/test_integration_seq2seq_fasttext_model_cpu.py +++ b/tests/network/integration/test_integration_seq2seq_fasttext_model_cpu.py @@ -1,6 +1,11 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # We also skip protected-access since we test the encoder and decoder step # pylint: disable=not-callable, protected-access + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import os import unittest from unittest import skipIf diff --git a/tests/network/integration/test_integration_seq2seq_fasttext_model_gpu.py b/tests/network/integration/test_integration_seq2seq_fasttext_model_gpu.py index ccb27505..b591cb91 100644 --- a/tests/network/integration/test_integration_seq2seq_fasttext_model_gpu.py +++ b/tests/network/integration/test_integration_seq2seq_fasttext_model_gpu.py @@ -1,6 +1,11 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # We also skip protected-access since we test the encoder and decoder step # pylint: disable=not-callable, protected-access + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import os import unittest from unittest import skipIf diff --git a/tests/network/integration/test_integration_seq2seq_model_cpu.py b/tests/network/integration/test_integration_seq2seq_model_cpu.py index 7c08b024..98bcde7f 100644 --- a/tests/network/integration/test_integration_seq2seq_model_cpu.py +++ b/tests/network/integration/test_integration_seq2seq_model_cpu.py @@ -1,6 +1,11 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # We also skip protected-access since we test the encoder and decoder step # pylint: disable=not-callable, protected-access + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import os import unittest from unittest import skipIf diff --git a/tests/network/integration/test_integration_seq2seq_model_gpu.py b/tests/network/integration/test_integration_seq2seq_model_gpu.py index 143767a2..b1b39168 100644 --- a/tests/network/integration/test_integration_seq2seq_model_gpu.py +++ b/tests/network/integration/test_integration_seq2seq_model_gpu.py @@ -1,6 +1,11 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # We also skip protected-access since we test the encoder and decoder step # pylint: disable=not-callable, protected-access + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import unittest from unittest import skipIf from unittest.mock import patch diff --git a/tests/network/test_bpemb_seq2seq_model_cpu.py b/tests/network/test_bpemb_seq2seq_model_cpu.py index 417477c2..fb110477 100644 --- a/tests/network/test_bpemb_seq2seq_model_cpu.py +++ b/tests/network/test_bpemb_seq2seq_model_cpu.py @@ -3,6 +3,14 @@ # pylint: disable=W0613, protected-access, too-many-arguments, too-many-locals # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + +# Pylint raise error for the call method mocking +# pylint: disable=unnecessary-dunder-call + import unittest from unittest.mock import patch, call, MagicMock diff --git a/tests/network/test_bpemb_seq2seq_model_gpu.py b/tests/network/test_bpemb_seq2seq_model_gpu.py index a625e18d..85dd2c22 100644 --- a/tests/network/test_bpemb_seq2seq_model_gpu.py +++ b/tests/network/test_bpemb_seq2seq_model_gpu.py @@ -2,6 +2,14 @@ # We also skip protected-access since we test the encoder and decoder step # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=unused-argument, protected-access, too-many-arguments, not-callable, too-many-locals + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + +# Pylint raise error for the call method mocking +# pylint: disable=unnecessary-dunder-call + import unittest from unittest import skipIf from unittest.mock import patch, call, MagicMock diff --git a/tests/network/test_decoder.py b/tests/network/test_decoder.py index a15f1046..79427f1c 100644 --- a/tests/network/test_decoder.py +++ b/tests/network/test_decoder.py @@ -1,3 +1,10 @@ +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + +# Pylint raise error for the getitem method mocking +# pylint: disable=unnecessary-dunder-call + import unittest from unittest import TestCase from unittest.mock import patch, MagicMock, call diff --git a/tests/network/test_fasttext_seq2seq_model_cpu.py b/tests/network/test_fasttext_seq2seq_model_cpu.py index bf526281..6c03e393 100644 --- a/tests/network/test_fasttext_seq2seq_model_cpu.py +++ b/tests/network/test_fasttext_seq2seq_model_cpu.py @@ -3,6 +3,14 @@ # pylint: disable=W0613, protected-access, too-many-arguments, too-many-locals # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + +# Pylint raise error for the call method mocking +# pylint: disable=unnecessary-dunder-call + import unittest from unittest.mock import patch, call, MagicMock diff --git a/tests/network/test_fasttext_seq2seq_model_gpu.py b/tests/network/test_fasttext_seq2seq_model_gpu.py index ec30b516..307a3302 100644 --- a/tests/network/test_fasttext_seq2seq_model_gpu.py +++ b/tests/network/test_fasttext_seq2seq_model_gpu.py @@ -2,6 +2,14 @@ # We also skip protected-access since we test the encoder and decoder step # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=unused-argument, protected-access, too-many-arguments, not-callable, too-many-locals + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + +# Pylint raise error for the call method mocking +# pylint: disable=unnecessary-dunder-call + import unittest from unittest import skipIf from unittest.mock import patch, call, MagicMock diff --git a/tests/network/test_seq2seq.py b/tests/network/test_seq2seq.py index 374b345d..92525f60 100644 --- a/tests/network/test_seq2seq.py +++ b/tests/network/test_seq2seq.py @@ -2,6 +2,11 @@ # Since we use patch we skip the unused argument error # We also skip protected-access since we test the _load_weights # pylint: disable=protected-access, unused-argument, not-callable + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import os import unittest from unittest import TestCase @@ -304,6 +309,7 @@ def test_givenSeq2seqModel_whenLoadPreTrainedWeightsNotVerboseCPU_thenWarningsNo @patch("deepparse.network.seq2seq.torch") @patch("deepparse.network.seq2seq.torch.nn.Module.load_state_dict") def test_givenSeq2SeqModelRetrained_whenLoadRetrainedWeights_thenLoadProperly(self, torch_nn_mock, torch_mock): + # pylint: disable=unnecessary-dunder-call all_layers_params_mock = MagicMock() all_layers_params_mock.__getitem__().__len__.return_value = self.decoder_output_size torch_mock.load.return_value = all_layers_params_mock @@ -330,6 +336,8 @@ def test_givenSeq2SeqModelRetrained_whenLoadRetrainedWeights_thenLoadProperly(se def test_givenSeq2SeqModelRetrained_whenLoadRetrainedWeightsNewTagModel_thenLoadProperDict( self, torch_nn_mock, torch_mock ): + # pylint: disable=unnecessary-dunder-call + all_layers_params_mock = MagicMock(spec=dict) all_layers_params_mock.__getitem__().__len__.return_value = self.decoder_output_size torch_mock.load.return_value = all_layers_params_mock diff --git a/tests/parser/base.py b/tests/parser/base.py index 2d1631e7..cc104e89 100644 --- a/tests/parser/base.py +++ b/tests/parser/base.py @@ -4,6 +4,13 @@ # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + +# Pylint raise error for the call method mocking +# pylint: disable=unnecessary-dunder-call + import os from tempfile import TemporaryDirectory from unittest import TestCase diff --git a/tests/parser/integration/test_integration_address_parser_gpu.py b/tests/parser/integration/test_integration_address_parser_gpu.py index 4cdba708..771bad2e 100644 --- a/tests/parser/integration/test_integration_address_parser_gpu.py +++ b/tests/parser/integration/test_integration_address_parser_gpu.py @@ -1,6 +1,10 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + from unittest import skipIf import torch diff --git a/tests/parser/test_address_parser.py b/tests/parser/test_address_parser.py index c629df56..ef524f77 100644 --- a/tests/parser/test_address_parser.py +++ b/tests/parser/test_address_parser.py @@ -4,6 +4,16 @@ # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + +# Pylint raise error for from torch import device +# pylint: disable=no-name-in-module + +# Pylint raise error for the repr method mocking +# pylint: disable=unnecessary-dunder-call + import os import unittest from tempfile import TemporaryDirectory diff --git a/tests/parser/test_address_parser_retrain_api.py b/tests/parser/test_address_parser_retrain_api.py index ab42d0c8..a662c2f1 100644 --- a/tests/parser/test_address_parser_retrain_api.py +++ b/tests/parser/test_address_parser_retrain_api.py @@ -4,6 +4,10 @@ # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import os import platform import unittest diff --git a/tests/parser/test_address_parser_test_api.py b/tests/parser/test_address_parser_test_api.py index 00d39198..dfd68f1e 100644 --- a/tests/parser/test_address_parser_test_api.py +++ b/tests/parser/test_address_parser_test_api.py @@ -1,5 +1,10 @@ # Since we use a patch as model mock we skip the unused argument error # pylint: disable=unused-argument, too-many-arguments + +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import unittest from unittest import skipIf from unittest.mock import patch, call diff --git a/tests/parser/test_formatted_parsed_address.py b/tests/parser/test_formatted_parsed_address.py index 360afba6..226b8f9d 100644 --- a/tests/parser/test_formatted_parsed_address.py +++ b/tests/parser/test_formatted_parsed_address.py @@ -1,5 +1,8 @@ # pylint: disable=no-member, too-many-public-methods +# Pylint raise error for the repr method mocking +# pylint: disable=unnecessary-dunder-call + import io import sys import unittest diff --git a/tests/parser/test_tools.py b/tests/parser/test_tools.py index b1cb232d..ad2ea398 100644 --- a/tests/parser/test_tools.py +++ b/tests/parser/test_tools.py @@ -3,6 +3,11 @@ # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + + import os import unittest from tempfile import TemporaryDirectory diff --git a/tests/parser/test_user_fromatted_parsed_address.py b/tests/parser/test_user_fromatted_parsed_address.py index 35b0784b..1f78ff0b 100644 --- a/tests/parser/test_user_fromatted_parsed_address.py +++ b/tests/parser/test_user_fromatted_parsed_address.py @@ -1,5 +1,8 @@ # pylint: disable=no-member, too-many-public-methods +# Pylint raise error for the call method mocking +# pylint: disable=unnecessary-dunder-call + import io import sys import unittest diff --git a/tests/tools.py b/tests/tools.py index c613555e..52d01ee2 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -1,5 +1,9 @@ # pylint: disable=too-many-arguments, self-assigning-variable +# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event +# if not the case. +# pylint: disable=no-member + import pickle from typing import List From 164ff056ce9f03d310948a6fffc5513e55261ca2 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 26 Jul 2022 13:50:18 -0400 Subject: [PATCH 039/195] push to 0.8.1 --- CHANGELOG.md | 8 ++++++-- README.md | 1 + version.txt | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7722168c..dc922b2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -231,9 +231,13 @@ to `cache_dir`. `saving_dir` is now deprecated and will be remove in version 0.8 - Add hyphen parsing cleaning step (with a bool flag to activate or not) to improve some country address parsing (see [issue 137](https://github.com/GRAAL-Research/deepparse/issues/137)). - Add ListDatasetContainer for Python list dataset. -## dev +## 0.8.1 - Refactored function `download_from_url` to `download_from_public_repository`. - Add error management when retrain a FastText like model on Windows with a number of workers (`num_workers`) greater than 0. - Improve dev tooling -- Improve CI \ No newline at end of file +- Improve CI +- Improve code coverage and pylint +- Add codacy + +## dev \ No newline at end of file diff --git a/README.md b/README.md index 282ffec5..77047bd6 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](http://www.gnu.org/licenses/lgpl-3.0) [![Continuous Integration](https://github.com/GRAAL-Research/deepparse/workflows/Continuous%20Integration/badge.svg)](https://github.com/GRAAL-Research/deepparse/actions?query=workflow%3A%22Continuous+Integration%22+branch%3Amaster) [![codecov](https://codecov.io/gh/GRAAL-Research/deepparse/branch/master/graph/badge.svg)](https://codecov.io/gh/GRAAL-Research/deepparse) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/62464699ff0740d0b8064227c4274b98)](https://www.codacy.com/gh/GRAAL-Research/deepparse/dashboard?utm_source=github.com&utm_medium=referral&utm_content=GRAAL-Research/deepparse&utm_campaign=Badge_Grade) [![Download](https://img.shields.io/badge/Download%20Dataset-blue?style=for-the-badge&logo=download)](https://github.com/GRAAL-Research/deepparse-address-data) diff --git a/version.txt b/version.txt index aec258df..6f4eebdf 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.8 +0.8.1 From b218ef1cc48189ee8ec8b7171bd54df25a565cfa Mon Sep 17 00:00:00 2001 From: davebulaval Date: Tue, 26 Jul 2022 14:53:39 -0400 Subject: [PATCH 040/195] simplification skipif test testing --- tests/cli/test_testing.py | 40 ++++----------------------------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/tests/cli/test_testing.py b/tests/cli/test_testing.py index 6d985b92..5633abb6 100644 --- a/tests/cli/test_testing.py +++ b/tests/cli/test_testing.py @@ -14,6 +14,10 @@ from tests.parser.integration.base_retrain import RetrainTestCase +@skipIf( + not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), + "download of model too long for test in runner", +) class TestingTests(RetrainTestCase, PretrainedWeightsBase): @pytest.fixture(autouse=True) def inject_fixtures(self, caplog): @@ -42,10 +46,6 @@ def setUpClass(cls): cls.a_cache_dir = "a_cache_dir" - @skipIf( - not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), - "download of model too long for test in runner", - ) def test_integration_cpu(self): parser_params = [ self.a_fasttext_model_type, @@ -79,10 +79,6 @@ def test_integration_gpu(self): self.assertTrue(os.path.isfile(os.path.join(self.temp_dir_obj.name, "data", expected_file_path))) - @skipIf( - not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), - "download of model too long for test in runner", - ) def test_integration_logging(self): with self._caplog.at_level(logging.INFO): parser_params = [ @@ -114,10 +110,6 @@ def test_integration_logging(self): actual_second_message = self._caplog.records[1].message self.assertEqual(expected_second_message, actual_second_message) - @skipIf( - not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), - "download of model too long for test in runner", - ) def test_integration_no_logging(self): with self._caplog.at_level(logging.INFO): parser_params = [ @@ -150,10 +142,6 @@ def test_integration_attention_model(self): self.assertTrue(os.path.isfile(os.path.join(self.temp_dir_obj.name, "data", expected_file_path))) - @skipIf( - not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), - "download of model too long for test in runner", - ) def test_integration_csv(self): parser_params = [ self.a_fasttext_model_type, @@ -175,10 +163,6 @@ def test_integration_csv(self): self.assertTrue(os.path.isfile(os.path.join(self.temp_dir_obj.name, "data", expected_file_path))) - @skipIf( - not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), - "download of model too long for test in runner", - ) def test_ifIsCSVFile_noColumnName_raiseValueError(self): with self.assertRaises(ValueError): parser_params = [ @@ -190,10 +174,6 @@ def test_ifIsCSVFile_noColumnName_raiseValueError(self): test.main(parser_params) - @skipIf( - not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), - "download of model too long for test in runner", - ) def test_ifIsNotSupportedFile_raiseValueError(self): with self.assertRaises(ValueError): parser_params = [ @@ -205,10 +185,6 @@ def test_ifIsNotSupportedFile_raiseValueError(self): test.main(parser_params) - @skipIf( - not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), - "download of model too long for test in runner", - ) def test_ifPathToFakeRetrainModel_thenUseFakeRetrainModel(self): with self._caplog.at_level(logging.INFO): # We use the default path to fasttext model as a "retrain model path" @@ -245,10 +221,6 @@ def test_ifPathToFakeRetrainModel_thenUseFakeRetrainModel(self): actual_second_message = self._caplog.records[1].message self.assertEqual(expected_second_message, actual_second_message) - @skipIf( - not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), - "download of model too long for test in runner", - ) def test_ifPathToBPEmbRetrainModel_thenUseBPEmbRetrainModel(self): with self._caplog.at_level(logging.INFO): parser_params = [ @@ -282,10 +254,6 @@ def test_ifPathToBPEmbRetrainModel_thenUseBPEmbRetrainModel(self): actual_second_message = self._caplog.records[3].message self.assertEqual(expected_second_message, actual_second_message) - @skipIf( - not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), - "download of model too long for test in runner", - ) def test_ifCachePath_thenUseNewCachePath(self): with patch("deepparse.cli.test.AddressParser") as address_parser_mock: parser_params = [ From 259b7ac03a86484ba80f1c123b4f75373a3991d8 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 27 Jul 2022 12:03:30 -0400 Subject: [PATCH 041/195] bug fix issue 141 --- CHANGELOG.md | 26 +++++++++++++++++--------- deepparse/parser/tools.py | 12 +++++++++--- tests/parser/test_tools.py | 21 +++++++++++++++++++++ 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc922b2b..29da0040 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -204,7 +204,8 @@ ## 0.7.5 - Bug-fix Poutyne version handling that causes a print error when a version is 1.11 when retraining -- Add the option to create a named retrain parsing model using by default the architecture setting or using the user-given name +- Add the option to create a named retrain parsing model using by default the architecture setting or using the + user-given name - Hot-fix missing raise for DataError validation of address to parse when address is tuple - Bug-fix handling of string column name for CSVDatasetContainer that raised ValueError - Improve parse CLI doc and fix error in doc stating JSON format is supported as input data @@ -212,32 +213,39 @@ - Add minimum version to Gensim 4.0.0. - Add a new CLI function, retrain, to retrain from the command line - Improve doc -- Add `cache_dir` to the BPEmb embedding model and to `AddressParser` to change the embeddings cache directory and models weights cache directory -- Change the `saving_dir` argument of `download_fastext_embeddings` and `download_fasttext_magnitude_embeddings` function -to `cache_dir`. `saving_dir` is now deprecated and will be remove in version 0.8. +- Add `cache_dir` to the BPEmb embedding model and to `AddressParser` to change the embeddings cache directory and + models weights cache directory +- Change the `saving_dir` argument of `download_fastext_embeddings` and `download_fasttext_magnitude_embeddings` + function + to `cache_dir`. `saving_dir` is now deprecated and will be remove in version 0.8. - Add a new CLI function, test, to test from the command line ## 0.7.6 -- Re-release the version 0.7.5 into 0.7.6 due to manipulation error and change in PyPi (now delete does not delete release by yank does). +- Re-release the version 0.7.5 into 0.7.6 due to manipulation error and change in PyPi (now delete does not delete + release by yank does). ## 0.8 -- Improve SEO +- Improve SEO - Add cache_dir arg in all CLI functions - Improve handling of HTTP error in models version verification - Improve doc - Add a note for parsing data cleaning (i.e. lowercase, commas removal, and hyphen replacing). -- Add hyphen parsing cleaning step (with a bool flag to activate or not) to improve some country address parsing (see [issue 137](https://github.com/GRAAL-Research/deepparse/issues/137)). +- Add hyphen parsing cleaning step (with a bool flag to activate or not) to improve some country address parsing ( + see [issue 137](https://github.com/GRAAL-Research/deepparse/issues/137)). - Add ListDatasetContainer for Python list dataset. ## 0.8.1 - Refactored function `download_from_url` to `download_from_public_repository`. -- Add error management when retrain a FastText like model on Windows with a number of workers (`num_workers`) greater than 0. +- Add error management when retrain a FastText like model on Windows with a number of workers (`num_workers`) greater + than 0. - Improve dev tooling - Improve CI - Improve code coverage and pylint - Add codacy -## dev \ No newline at end of file +## dev + +- Bug-fix retrain attention model naming parsing \ No newline at end of file diff --git a/deepparse/parser/tools.py b/deepparse/parser/tools.py index 9c539b53..2cad51d6 100644 --- a/deepparse/parser/tools.py +++ b/deepparse/parser/tools.py @@ -77,12 +77,18 @@ def handle_model_name(model_type: str, attention_mechanism: bool) -> Tuple[str, model_type = model_type.lower() # To handle retrained model using attention mechanism. - if 'attention' in model_type: + if "attention" in model_type: if not attention_mechanism: raise ValueError( f"Model-type {model_type} requires attention mechanism. Set attention_mechanism flag to True." ) - model_type = model_type.replace('attention', '') + # To handle the presence of attention in the model name. + # We handle two cases: modelattention and model_attention. + # To do so, we first remove the attention for both case, and + # second we handle the trailing possible underscore for the + # second case. + model_type = model_type.replace("attention", "") + model_type = model_type.replace("_", "") if model_type in ("lightest", "fasttext-light"): model_type = "fasttext-light" # We change name to 'fasttext-light' since lightest = fasttext-light @@ -106,7 +112,7 @@ def handle_model_name(model_type: str, attention_mechanism: bool) -> Tuple[str, def infer_model_type(checkpoint_weights: OrderedDict, attention_mechanism: bool) -> (str, bool): """ - Function to infer the model type using the weights matrix. + Function to infer the model type using the weights' matrix. We first try to use the "model_type" key added by our retrain process. If this fails, we infer it using our knowledge of the layers' names. For example, BPEmb model uses an embedding network, thus, if ``embedding_network.model.weight_ih_l0`` is present, diff --git a/tests/parser/test_tools.py b/tests/parser/test_tools.py index ad2ea398..9ec90b89 100644 --- a/tests/parser/test_tools.py +++ b/tests/parser/test_tools.py @@ -314,6 +314,27 @@ def test_givenModelTypes_whenHandleThem_then_ReturnProperModelType(self): expected_model_type += "_attention" self.assertEqual(expected_model_type, actual_model_type) + # Retrained models no attention + retrain_model_type = "fasttext" + expected_model_type = "fasttext" + actual_model_type, _ = handle_model_name(retrain_model_type, attention_mechanism=False) + self.assertEqual(expected_model_type, actual_model_type) + + retrain_model_type = "fasttext_attention" + expected_model_type = "fasttext_attention" + actual_model_type, _ = handle_model_name(retrain_model_type, attention_mechanism=True) + self.assertEqual(expected_model_type, actual_model_type) + + retrain_model_type = "bpemb" + expected_model_type = "bpemb" + actual_model_type, _ = handle_model_name(retrain_model_type, attention_mechanism=False) + self.assertEqual(expected_model_type, actual_model_type) + + retrain_model_type = "bpemb_attention" + expected_model_type = "bpemb_attention" + actual_model_type, _ = handle_model_name(retrain_model_type, attention_mechanism=True) + self.assertEqual(expected_model_type, actual_model_type) + def test_givenModelTypes_whenHandleThem_then_ReturnProperFormattedModelType(self): # "Normal" Fasttext setup model_types = ["fasttext", "fastest"] From 889cec3423576d3aed9785d4cb36f310398e5972 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 27 Jul 2022 12:18:08 -0400 Subject: [PATCH 042/195] fix missing csv dataset in test for csv integration test --- tests/cli/test_retrain.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/cli/test_retrain.py b/tests/cli/test_retrain.py index 7929cc1f..afd005bc 100644 --- a/tests/cli/test_retrain.py +++ b/tests/cli/test_retrain.py @@ -152,7 +152,11 @@ def test_integration_attention_model(self): ) def test_integration_csv(self): - parser_params = self.set_up_params(csv_column_names=["Address", "Tags"], csv_column_separator=",") + parser_params = self.set_up_params( + train_dataset_path=self.a_train_csv_dataset_path, + csv_column_names=["Address", "Tags"], + csv_column_separator=",", + ) retrain.main(parser_params) From e73f07d6292d4f7146de051af57b450d386fdada Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 27 Jul 2022 12:36:39 -0400 Subject: [PATCH 043/195] merge improvement for error handling of retrain and test API --- CHANGELOG.md | 3 +- deepparse/parser/address_parser.py | 12 +++++ .../parser/test_address_parser_retrain_api.py | 48 ++++++++++++++++++- tests/parser/test_address_parser_test_api.py | 40 ++++++++++++---- 4 files changed, 92 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29da0040..d87a80cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -248,4 +248,5 @@ ## dev -- Bug-fix retrain attention model naming parsing \ No newline at end of file +- Bug-fix retrain attention model naming parsing +- Improve error handling when not a DatasetContainer is use in retrain and test API \ No newline at end of file diff --git a/deepparse/parser/address_parser.py b/deepparse/parser/address_parser.py index 1037a1ec..3deb77ea 100644 --- a/deepparse/parser/address_parser.py +++ b/deepparse/parser/address_parser.py @@ -611,6 +611,12 @@ def retrain( "Thus, you need to set num_workers to 0 since 1 also means 'parallelism'." ) + if not isinstance(dataset_container, DatasetContainer): + raise ValueError( + "The dataset_container has to be a DatasetContainer. " + "Read the docs at https://deepparse.org/ for more details." + ) + if not dataset_container.is_a_train_container(): raise ValueError("The dataset container is not a train container.") @@ -813,6 +819,12 @@ def test( "doc for more details." ) + if not isinstance(test_dataset_container, DatasetContainer): + raise ValueError( + "The test_dataset_container has to be a DatasetContainer. " + "Read the docs at https://deepparse.org/ for more details." + ) + if not test_dataset_container.is_a_train_container(): raise ValueError("The dataset container is not a train container.") diff --git a/tests/parser/test_address_parser_retrain_api.py b/tests/parser/test_address_parser_retrain_api.py index a662c2f1..7f9c1f64 100644 --- a/tests/parser/test_address_parser_retrain_api.py +++ b/tests/parser/test_address_parser_retrain_api.py @@ -67,6 +67,7 @@ def tearDown(self) -> None: def address_parser_retrain_call( self, + dataset_container=None, prediction_tags=None, seq2seq_params=None, layers_to_freeze=None, @@ -81,8 +82,13 @@ def address_parser_retrain_call( num_workers = 0 # Default setting is 1, but We set it to zero to allow Windows tests to pass else: num_workers = 1 + + if dataset_container is None: + dataset_container = self.mocked_data_container + # To handle by default for most of the tests. + self.address_parser.retrain( - self.mocked_data_container, + dataset_container, self.a_train_ratio, self.a_batch_size, self.a_epoch_number, @@ -1598,6 +1604,46 @@ def test_givenWrongNewNamedModelName_thenRaiseValueError( with self.assertRaises(ValueError): self.address_parser_retrain_call(name_of_the_retrain_parser="a_wrong_named_parser_name.ckpt") + @patch("deepparse.parser.address_parser.os.path.join") + @patch("deepparse.parser.address_parser.torch.save") + @patch("deepparse.parser.address_parser.DataLoader") + @patch("deepparse.parser.address_parser.Experiment") + @patch("deepparse.parser.address_parser.SGD") + @patch("deepparse.parser.address_parser.DataTransform") + @patch("deepparse.parser.address_parser.FastTextSeq2SeqModel") + @patch("deepparse.parser.address_parser.fasttext_data_padding") + @patch("deepparse.parser.address_parser.FastTextVectorizer") + @patch("deepparse.parser.address_parser.FastTextEmbeddingsModel") + @patch("deepparse.parser.address_parser.download_fasttext_embeddings") + def test_givenNotADatasetContainer_whenRetrainCall_thenRaiseValueError( + self, + download_weights_mock, + embeddings_model_mock, + vectorizer_model_mock, + data_padding_mock, + model_mock, + data_transform_mock, + optimizer_mock, + experiment_mock, + data_loader_mock, + torch_save_mock, + os_path_join_mock, + ): + + self.address_parser = AddressParser( + model_type=self.a_fasttext_model_type, + device=self.a_device, + verbose=self.verbose, + ) + + not_a_dataset_container_obj = [] + with self.assertRaises(ValueError): + self.address_parser_retrain_call(dataset_container=not_a_dataset_container_obj) + + not_a_dataset_container_obj = {} + with self.assertRaises(ValueError): + self.address_parser_retrain_call(dataset_container=not_a_dataset_container_obj) + if __name__ == "__main__": unittest.main() diff --git a/tests/parser/test_address_parser_test_api.py b/tests/parser/test_address_parser_test_api.py index dfd68f1e..5e36127a 100644 --- a/tests/parser/test_address_parser_test_api.py +++ b/tests/parser/test_address_parser_test_api.py @@ -40,9 +40,13 @@ def setUpClass(cls): cls.verbose = False - def address_parser_test_call(self): + def address_parser_test_call(self, dataset_container=None): + if dataset_container is None: + dataset_container = self.mocked_data_container + # To handle by default for most of the tests. + self.address_parser.test( - self.mocked_data_container, + dataset_container, self.a_batch_size, num_workers=self.a_number_of_workers, callbacks=self.a_callbacks_list, @@ -314,13 +318,31 @@ def test_givenNotTrainingDataContainer_thenRaiseValueError( ) mocked_data_container = ADataContainer(is_training_container=False) with self.assertRaises(ValueError): - self.address_parser.test( - mocked_data_container, - self.a_batch_size, - num_workers=self.a_number_of_workers, - callbacks=self.a_callbacks_list, - seed=self.a_seed, - ) + self.address_parser_test_call(dataset_container=mocked_data_container) + + @patch("deepparse.parser.address_parser.BPEmbSeq2SeqModel") + @patch("deepparse.parser.address_parser.bpemb_data_padding") + @patch("deepparse.parser.address_parser.BPEmbVectorizer") + @patch("deepparse.parser.address_parser.BPEmbEmbeddingsModel") + def test_givenNotADataContainer_thenRaiseValueError( + self, + embeddings_model_mock, + vectorizer_model_mock, + data_padding_mock, + model_patch, + ): + self.address_parser = AddressParser( + model_type=self.a_bpemb_model_type, + device=self.a_device, + verbose=self.verbose, + ) + not_a_dataset_container = [] + with self.assertRaises(ValueError): + self.address_parser_test_call(dataset_container=not_a_dataset_container) + + not_a_dataset_container = {} + with self.assertRaises(ValueError): + self.address_parser_test_call(dataset_container=not_a_dataset_container) if __name__ == "__main__": From b79b3ee24838b3c70daf5cd289f83513c33f5f01 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Thu, 28 Jul 2022 10:14:49 -0400 Subject: [PATCH 044/195] linting yml file --- .github/workflows/60-days-stale-check.yml | 18 ++++++------ .github/workflows/docs.yml | 2 +- .github/workflows/formatting.yml | 4 +-- .github/workflows/greetings.yml | 12 ++++---- .github/workflows/linting.yml | 4 +-- .github/workflows/python-publish.yml | 34 +++++++++++------------ .github/workflows/tests.yml | 6 ++-- 7 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.github/workflows/60-days-stale-check.yml b/.github/workflows/60-days-stale-check.yml index 455aa715..a7c5b028 100644 --- a/.github/workflows/60-days-stale-check.yml +++ b/.github/workflows/60-days-stale-check.yml @@ -1,7 +1,7 @@ name: 60 Days Stale Check on: schedule: - - cron: '0 0 * * 0' + - cron: "0 0 * * 0" jobs: stale: @@ -10,13 +10,13 @@ jobs: - uses: actions/stale@v3.0.14 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue is stale because it has been open 60 days with no activity.
- `Stale` issues will automatically be closed 30 days after being marked `Stale`
.' - stale-pr-message: 'This PR is stale because it has been open 60 days with no activity.
- `Stale` pull requests will automatically be closed 30 days after being marked `Stale`
.' + stale-issue-message: "This issue is stale because it has been open 60 days with no activity.
+ `Stale` issues will automatically be closed 30 days after being marked `Stale`
." + stale-pr-message: "This PR is stale because it has been open 60 days with no activity.
+ `Stale` pull requests will automatically be closed 30 days after being marked `Stale`
." days-before-stale: 60 # 60 days before marking anything stale days-before-close: 90 - stale-issue-label: 'stale' - stale-pr-label: 'stale' - exempt-pr-labels: 'never-stale' # Exempt 'never-stale' labels from being marked stale - exempt-issue-labels: 'never-stale' # Exempt 'never-stale' labels from being marked stale + stale-issue-label: "stale" + stale-pr-label: "stale" + exempt-pr-labels: "never-stale" # Exempt "never-stale" labels from being marked stale + exempt-issue-labels: "never-stale" # Exempt "never-stale" labels from being marked stale diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c940b091..6a093454 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: [ "3.7", "3.8", "3.9", "3.10" ] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index e81bca29..51fd5763 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -1,13 +1,13 @@ name: Formatting -on: [push, pull_request] +on: [ push, pull_request ] jobs: formatting: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: [ "3.7", "3.8", "3.9", "3.10" ] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml index 67725f54..154cdbe6 100644 --- a/.github/workflows/greetings.yml +++ b/.github/workflows/greetings.yml @@ -1,6 +1,6 @@ name: Greetings -on: [pull_request_target, issues] +on: [ pull_request_target, issues ] jobs: greeting: @@ -9,8 +9,8 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/first-interaction@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - issue-message: "Thank you for you interest in improving Deepparse." - pr-message: "Thank you for you interest in improving Deepparse." + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + issue-message: "Thank you for you interest in improving Deepparse." + pr-message: "Thank you for you interest in improving Deepparse." diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 3ddfab20..42ebc4c2 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -1,13 +1,13 @@ name: Linting -on: [push, pull_request] +on: [ push, pull_request ] jobs: linting: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: [ "3.7", "3.8", "3.9", "3.10" ] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 1713d323..55cd1ae0 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -5,7 +5,7 @@ name: Upload Python Package on: release: - types: [created] + types: [ created ] jobs: deploy: @@ -13,19 +13,19 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.10' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - DEEPPARSE_RELEASE_BUILD=1 python setup.py sdist bdist_wheel - twine upload dist/* \ No newline at end of file + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + DEEPPARSE_RELEASE_BUILD=1 python setup.py sdist bdist_wheel + twine upload dist/* \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d332f603..d4a6449d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,13 +1,13 @@ name: Tests -on: [push, pull_request] +on: [ push, pull_request ] jobs: build: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: [ "3.7", "3.8", "3.9", "3.10" ] steps: - uses: actions/checkout@v2 @@ -28,7 +28,7 @@ jobs: runs-on: windows-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9"] # pip install requirements fail on 3.10 + python-version: [ "3.7", "3.8", "3.9" ] # pip install requirements fail on 3.10 steps: - uses: actions/checkout@v2 From 1dce60b9dd0c76b9143176852e0d225595fe9dd3 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Thu, 28 Jul 2022 10:27:05 -0400 Subject: [PATCH 045/195] improve run all tests script --- run_tests_python_envs.sh | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/run_tests_python_envs.sh b/run_tests_python_envs.sh index a1f8c639..8a0c0b80 100755 --- a/run_tests_python_envs.sh +++ b/run_tests_python_envs.sh @@ -20,9 +20,16 @@ pip install -Ur ../requirements.txt echo "*****Running test in Conda Python version 3.7*****" conda run -n deepparse_pytest_3_7 --live-stream pytest --cov ../deepparse --cov-report html:html_report_3_7 --cov-report xml:export_xml_report_3_7.xml --cov-config=.coveragerc ../tests +if [ $? -eq 0 ]; then + python3_7_tests_res=1 +fi + # close conda env conda deactivate +# Cleanup the conda env +conda env remove -n deepparse_pytest_3_7 + # Create a new Python env 3.8 conda create --name deepparse_pytest_3_8 python=3.8 -y --force conda activate deepparse_pytest_3_8 @@ -35,9 +42,16 @@ pip install -Ur ../requirements.txt echo "*****Running test in Conda Python version 3.8*****" conda run -n deepparse_pytest_3_8 --live-stream pytest --cov ../deepparse --cov-report html:html_report_3_8 --cov-report xml:export_xml_report_3_8.xml --cov-config=.coveragerc ../tests +if [ $? -eq 0 ]; then + python3_8_tests_res=1 +fi + # close conda env conda deactivate +# Cleanup the conda env +conda env remove -n deepparse_pytest_3_8 + # Create a new Python env 3.9 conda create --name deepparse_pytest_3_9 python=3.9 -y --force conda activate deepparse_pytest_3_9 @@ -50,9 +64,16 @@ pip install -Ur ../requirements.txt echo "*****Running test in Conda Python version 3.9*****" conda run -n deepparse_pytest_3_9 --live-stream pytest --cov ../deepparse --cov-report html:html_report_3_9 --cov-report xml:export_xml_report_3_9.xml --cov-config=.coveragerc ../tests +if [ $? -eq 0 ]; then + python3_9_tests_res=1 +fi + # close conda env conda deactivate +# Cleanup the conda env +conda env remove -n deepparse_pytest_3_9 + # Create a new Python env 3.10 conda create --name deepparse_pytest_3_10 python=3.10 -y --force conda activate deepparse_pytest_3_10 @@ -65,5 +86,39 @@ pip install -Ur ../requirements.txt echo "*****Running test in Conda Python version 3.10*****" conda run -n deepparse_pytest_3_10 --live-stream pytest --cov ../deepparse --cov-report html:html_report_3_10 --cov-report xml:export_xml_report_3_10.xml --cov-config=.coveragerc ../tests +if [ $? -eq 0 ]; then + python3_10_tests_res=1 +fi + # close conda env conda deactivate + +# Cleanup the conda env +conda env remove -n deepparse_pytest_3_10 + +# All tests env print +echo "*****The results of the tests are:" + +if [ $python3_7_tests_res -eq 1 ]; then + echo "Success for Python 3.7" +else + echo "Fail for Python 3.7" +fi + +if [ $python3_8_tests_res -eq 1 ]; then + echo "Success for Python 3.8" +else + echo "Fail for Python 3.8" +fi + +if [ $python3_9_tests_res -eq 1 ]; then + echo "Success for Python 3.9" +else + echo "Fail for Python 3.9" +fi + +if [ $python3_10_tests_res -eq 1 ]; then + echo "Success for Python 3.10" +else + echo "Fail for Python 3.10" +fi From 48fe1977bd70cfa2b11f820063f90a4ee2dfd2f3 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 29 Jul 2022 16:46:59 -0400 Subject: [PATCH 046/195] improve run tests python envs --- run_tests_python_envs.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/run_tests_python_envs.sh b/run_tests_python_envs.sh index 8a0c0b80..405f8fac 100755 --- a/run_tests_python_envs.sh +++ b/run_tests_python_envs.sh @@ -98,27 +98,38 @@ conda env remove -n deepparse_pytest_3_10 # All tests env print echo "*****The results of the tests are:" +return_status=0 if [ $python3_7_tests_res -eq 1 ]; then echo "Success for Python 3.7" else + return_status=1 echo "Fail for Python 3.7" fi if [ $python3_8_tests_res -eq 1 ]; then echo "Success for Python 3.8" else + return_status=1 echo "Fail for Python 3.8" fi if [ $python3_9_tests_res -eq 1 ]; then echo "Success for Python 3.9" else + return_status=1 echo "Fail for Python 3.9" fi if [ $python3_10_tests_res -eq 1 ]; then echo "Success for Python 3.10" else + return_status=1 echo "Fail for Python 3.10" fi + +if [ $return_status -eq 1]; then + exit 1 +else + exit 0 +fi From d3b5e6973bde9c6025b84706a85c5c88f228fda6 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Thu, 4 Aug 2022 14:42:32 -0400 Subject: [PATCH 047/195] fix naming of tests and some typos --- .../integration/test_integration_address_parser_cpu.py | 2 +- .../test_integration_address_parser_new_params.py | 6 +++--- .../test_integration_address_parser_new_params_new_tags.py | 2 +- .../test_integration_address_parser_retrain_cpu.py | 2 +- .../test_integration_address_parser_retrain_gpu.py | 2 +- ...gration_address_parser_retrain_new_address_components.py | 4 ++-- .../integration/test_integration_address_parser_test_cpu.py | 2 +- .../integration/test_integration_address_parser_test_gpu.py | 2 +- .../integration/test_integration_reload_retrain_parser.py | 4 ++-- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/parser/integration/test_integration_address_parser_cpu.py b/tests/parser/integration/test_integration_address_parser_cpu.py index c235c3ae..0f4ef5c3 100644 --- a/tests/parser/integration/test_integration_address_parser_cpu.py +++ b/tests/parser/integration/test_integration_address_parser_cpu.py @@ -16,7 +16,7 @@ not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), "download of model too long for test in runner", ) -class AddressParserTest(AddressParserBase): +class AddressParserCPUTest(AddressParserBase): def setUp(self) -> None: a_config = {"model_type": "fasttext", "device": "cpu", "verbose": False} self.setup_model_with_config(a_config) diff --git a/tests/parser/integration/test_integration_address_parser_new_params.py b/tests/parser/integration/test_integration_address_parser_new_params.py index 3481c935..f78142c8 100644 --- a/tests/parser/integration/test_integration_address_parser_new_params.py +++ b/tests/parser/integration/test_integration_address_parser_new_params.py @@ -12,7 +12,7 @@ @skipIf(not torch.cuda.is_available(), "no gpu available") # We skip it even if it is CPU since the downloading is too long -class AddressParserPredictTest(AddressParserPredictNewParamsBase): +class AddressParserPredictNewParamsTest(AddressParserPredictNewParamsBase): def test_givenAAddress_whenParseNewParamsFastTextCPU_thenParseAddressProperly(self): # Training setup fasttext_address_parser = AddressParser( @@ -35,7 +35,7 @@ def test_givenAAddress_whenParseNewParamsFastTextCPU_thenParseAddressProperly(se path_to_retrained_model=self.a_fasttext_retrain_model_path, ) - # Since we train a smaller model, it sometime return EOS, so we manage it by adding the EOS tag + # Since we train a smaller model, it sometime returns EOS, so we manage it by adding the EOS tag formatted_parsed_address.FIELDS = [ "StreetNumber", "Unit", @@ -47,7 +47,7 @@ def test_givenAAddress_whenParseNewParamsFastTextCPU_thenParseAddressProperly(se "GeneralDelivery", "EOS", ] - # We validate that the new settings are loaded and we can parse + # We validate that the new settings are loaded, and we can parse parse_address = fasttext_address_parser(self.an_address_to_parse) self.assertIsInstance(parse_address, FormattedParsedAddress) diff --git a/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py b/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py index e666caac..74222bbd 100644 --- a/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py +++ b/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py @@ -22,7 +22,7 @@ @skipIf(not torch.cuda.is_available(), "no gpu available") # We skip it even if it is CPU since the downloading is too long -class AddressParserPredictTest(TestCase): +class AddressParserPredictNewTagsTest(TestCase): @classmethod def setUpClass(cls): cls.an_address_to_parse = "350 rue des lilas o" diff --git a/tests/parser/integration/test_integration_address_parser_retrain_cpu.py b/tests/parser/integration/test_integration_address_parser_retrain_cpu.py index 552eb573..120cc563 100644 --- a/tests/parser/integration/test_integration_address_parser_retrain_cpu.py +++ b/tests/parser/integration/test_integration_address_parser_retrain_cpu.py @@ -18,7 +18,7 @@ not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), "download of model too long for test in runner", ) -class AddressParserIntegrationRetrainTest(AddressParserRetrainTestCase, CaptureOutputTestCase): +class AddressParserIntegrationRetrainCPUTest(AddressParserRetrainTestCase, CaptureOutputTestCase): def test_givenAFasttextAddressParser_whenRetrain_thenTrainingOccur(self): address_parser = AddressParser( model_type=self.a_fasttext_model_type, diff --git a/tests/parser/integration/test_integration_address_parser_retrain_gpu.py b/tests/parser/integration/test_integration_address_parser_retrain_gpu.py index 6a4698dc..29fbbcac 100644 --- a/tests/parser/integration/test_integration_address_parser_retrain_gpu.py +++ b/tests/parser/integration/test_integration_address_parser_retrain_gpu.py @@ -13,7 +13,7 @@ @skipIf(not torch.cuda.is_available(), "no gpu available") -class AddressParserIntegrationRetrainTest(AddressParserRetrainTestCase): +class AddressParserIntegrationRetrainGPUTest(AddressParserRetrainTestCase): def test_givenAFasttextAddressParser_whenRetrain_thenTrainingOccur(self): address_parser = AddressParser( model_type=self.a_fasttext_model_type, diff --git a/tests/parser/integration/test_integration_address_parser_retrain_new_address_components.py b/tests/parser/integration/test_integration_address_parser_retrain_new_address_components.py index 393e2fa7..a959119b 100644 --- a/tests/parser/integration/test_integration_address_parser_retrain_new_address_components.py +++ b/tests/parser/integration/test_integration_address_parser_retrain_new_address_components.py @@ -19,10 +19,10 @@ not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), "download of model too long for test in runner", ) -class AddressParserIntegrationTestNewTags(AddressParserRetrainTestCase): +class AddressParserIntegrationTestNewAddressComponents(AddressParserRetrainTestCase): @classmethod def setUpClass(cls): - super(AddressParserIntegrationTestNewTags, cls).setUpClass() + super(AddressParserIntegrationTestNewAddressComponents, cls).setUpClass() file_extension = "p" training_dataset_name = "test_sample_data_new_prediction_tags" diff --git a/tests/parser/integration/test_integration_address_parser_test_cpu.py b/tests/parser/integration/test_integration_address_parser_test_cpu.py index e7502b3d..afa0f929 100644 --- a/tests/parser/integration/test_integration_address_parser_test_cpu.py +++ b/tests/parser/integration/test_integration_address_parser_test_cpu.py @@ -15,7 +15,7 @@ not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), "download of model too long for test in runner", ) -class AddressParserIntegrationTestAPITest(AddressParserRetrainTestCase): +class AddressParserIntegrationTestAPICPUTest(AddressParserRetrainTestCase): def test_givenAFasttextAddressParser_whenTestWithNumWorkerAt0_thenTestOccur(self): address_parser = AddressParser( model_type=self.a_fasttext_model_type, diff --git a/tests/parser/integration/test_integration_address_parser_test_gpu.py b/tests/parser/integration/test_integration_address_parser_test_gpu.py index a07585ad..7dd6b6c7 100644 --- a/tests/parser/integration/test_integration_address_parser_test_gpu.py +++ b/tests/parser/integration/test_integration_address_parser_test_gpu.py @@ -12,7 +12,7 @@ @skipIf(not torch.cuda.is_available(), "no gpu available") -class AddressParserIntegrationTestAPITest(AddressParserRetrainTestCase): +class AddressParserIntegrationTestAPIGPUTest(AddressParserRetrainTestCase): def test_givenAFasttextAddressParser_whenTestWithNumWorkerAt0_thenTestOccur(self): address_parser = AddressParser( model_type=self.a_fasttext_model_type, diff --git a/tests/parser/integration/test_integration_reload_retrain_parser.py b/tests/parser/integration/test_integration_reload_retrain_parser.py index b8694f89..3e46edab 100644 --- a/tests/parser/integration/test_integration_reload_retrain_parser.py +++ b/tests/parser/integration/test_integration_reload_retrain_parser.py @@ -14,10 +14,10 @@ not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), "download of model too long for test in runner", ) -class AddressParserIntegrationTestAPITest(AddressParserRetrainTestCase, PretrainedWeightsBase): +class AddressParserIntegrationReloadRetrainAPITest(AddressParserRetrainTestCase, PretrainedWeightsBase): @classmethod def setUpClass(cls): - super(AddressParserIntegrationTestAPITest, cls).setUpClass() + super(AddressParserIntegrationReloadRetrainAPITest, cls).setUpClass() cls.download_pre_trained_weights(cls) def test_integration_parsing_with_retrain_fasttext(self): From 3c4ffde6ecf4806f45e347e33aa4392eca53776b Mon Sep 17 00:00:00 2001 From: David Beauchemin Date: Thu, 4 Aug 2022 15:59:44 -0400 Subject: [PATCH 048/195] add save_model_eights method (#147) --- CHANGELOG.md | 2 + deepparse/parser/address_parser.py | 30 +++++++ major_release_todo.md | 3 +- .../test_integration_address_parser.py | 80 +++++++++++++++++++ tests/parser/test_address_parser.py | 33 +++++++- 5 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 tests/parser/integration/test_integration_address_parser.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e4da67c..00b3f1fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -252,3 +252,5 @@ - Improve error handling when not a DatasetContainer is use in retrain and test API ## dev + +- Add `save_model_weights` method to `AddressParser` to save model weights (PyTorch state dictionary) \ No newline at end of file diff --git a/deepparse/parser/address_parser.py b/deepparse/parser/address_parser.py index 3deb77ea..dbe1f4a0 100644 --- a/deepparse/parser/address_parser.py +++ b/deepparse/parser/address_parser.py @@ -13,6 +13,7 @@ import platform import re import warnings +from pathlib import Path from typing import List, Union, Dict, Tuple import torch @@ -851,6 +852,35 @@ def test( return test_res + def save_model_weights(self, file_path: Union[str, Path]) -> None: + """ + Method to save, in a Pickle format, the address parser model weights (PyTorch state dictionary). + + file_path (Union[str, Path]): A complete file path with a pickle extension to save the model weights. + It can either be a string (e.g. 'path/to/save.p') or a path like path (e.g. Path('path/to/save.p'). + + Examples: + + .. code-block:: python + + address_parser = AddressParser(device=0) + + a_path = Path('some/path/to/save.p') + address_parser.save_address_parser_weights(a_path) + + + .. code-block:: python + + address_parser = AddressParser(device=0) + + a_path = 'some/path/to/save.p' + address_parser.save_address_parser_weights(a_path) + + """ + self.model.state_dict() + + torch.save(self.model.state_dict(), file_path) + def _fill_tagged_addresses_components( self, tags_predictions: List, diff --git a/major_release_todo.md b/major_release_todo.md index 30b2d39f..d35d184b 100644 --- a/major_release_todo.md +++ b/major_release_todo.md @@ -1 +1,2 @@ -- Remove deprecated `download_from_url` function \ No newline at end of file +- Remove deprecated `download_from_url` function +- https://zenodo.org/account/settings/github/repository/GRAAL-Research/deepparse \ No newline at end of file diff --git a/tests/parser/integration/test_integration_address_parser.py b/tests/parser/integration/test_integration_address_parser.py new file mode 100644 index 00000000..39dd6743 --- /dev/null +++ b/tests/parser/integration/test_integration_address_parser.py @@ -0,0 +1,80 @@ +# Bug with PyTorch source code makes torch.tensor as not callable for pylint. +# pylint: disable=not-callable + +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + +import os +from collections import OrderedDict +from os.path import exists +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest import skipIf + +import torch + +from tests.parser.integration.base_predict import ( + AddressParserBase, +) + + +@skipIf( + not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), + "download of model too long for test in runner", +) +class AddressParserTest(AddressParserBase): + @classmethod + def setUpClass(cls): + super(AddressParserTest, cls).setUpClass() + + cls.temp_dir_obj = TemporaryDirectory() + cls.a_saving_dir_path = cls.temp_dir_obj.name + + @classmethod + def tearDownClass(cls) -> None: + cls.temp_dir_obj.cleanup() + + def assert_file_exist(self, file_path): + file_exists = exists(file_path) + self.assertTrue(file_exists) + + def setUp(self) -> None: + a_config = {"model_type": "fasttext", "device": "cpu", "verbose": False} + self.setup_model_with_config(a_config) + + def test_givenAModelToExportDictStr_thenExportIt(self): + a_file_path = os.path.join(self.a_saving_dir_path, "exported_model.p") + + self.a_model.save_model_weights(file_path=a_file_path) + + self.assert_file_exist(a_file_path) + + def test_givenAModelToExportDictPathALike_thenExportIt(self): + a_file_path = Path(os.path.join(self.a_saving_dir_path, "exported_model.p")) + + self.a_model.save_model_weights(file_path=a_file_path) + + self.assert_file_exist(a_file_path) + + def test_givenAnExportedModelUsingTheMethod_whenReloadIt_thenReload(self): + a_file_path = Path(os.path.join(self.a_saving_dir_path, "exported_model.p")) + + self.a_model.save_model_weights(file_path=a_file_path) + + weights = torch.load(a_file_path) + + self.assertIsInstance(weights, OrderedDict) + + model_layer_keys = [ + 'encoder.lstm.weight_ih_l0', + 'encoder.lstm.weight_hh_l0', + 'encoder.lstm.bias_ih_l0', + 'encoder.lstm.bias_hh_l0', + 'decoder.lstm.weight_ih_l0', + 'decoder.lstm.weight_hh_l0', + 'decoder.lstm.bias_ih_l0', + 'decoder.lstm.bias_hh_l0', + 'decoder.linear.weight', + 'decoder.linear.bias', + ] + self.assertEqual(model_layer_keys, list(weights.keys())) diff --git a/tests/parser/test_address_parser.py b/tests/parser/test_address_parser.py index ef524f77..05a4c5d2 100644 --- a/tests/parser/test_address_parser.py +++ b/tests/parser/test_address_parser.py @@ -1,5 +1,5 @@ # Since we use a patch as model mock we skip the unused argument error -# pylint: disable=unused-argument, no-member, too-many-public-methods, too-many-lines +# pylint: disable=unused-argument, no-member, too-many-public-methods, too-many-lines, too-many-arguments # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with @@ -76,9 +76,13 @@ def setUpClass(cls): "EOS", ] + cls.export_temp_dir_obj = TemporaryDirectory() + cls.a_saving_dir_path = cls.export_temp_dir_obj.name + @classmethod def tearDownClass(cls) -> None: cls.temp_dir_obj.cleanup() + cls.export_temp_dir_obj.cleanup() def setUp(self): super().setUp() @@ -1617,6 +1621,33 @@ def test_givenANewCacheDirFastText_thenInitWeightsInNewCacheDir(self, embeddings ) download_weights_mock.assert_called_with(verbose=self.verbose, cache_dir=self.a_cache_dir) + @patch("deepparse.parser.address_parser.torch.save") + @patch("deepparse.parser.address_parser.FastTextSeq2SeqModel") + @patch("deepparse.parser.address_parser.fasttext_data_padding") + @patch("deepparse.parser.address_parser.FastTextVectorizer") + @patch("deepparse.parser.address_parser.FastTextEmbeddingsModel") + @patch("deepparse.parser.address_parser.download_fasttext_embeddings") + def test_givenAModelToExportDict_thenCallTorchSaveWithProperArgs( + self, + download_weights_mock, + embeddings_model_mock, + vectorizer_model_mock, + data_padding_mock, + model_mock, + torch_save_mock, + ): + address_parser = AddressParser( + model_type=self.a_fasttext_model_type, + device=self.a_cpu_device, + verbose=self.verbose, + ) + + a_file_path = os.path.join(self.a_saving_dir_path, "exported_model.p") + address_parser.save_model_weights(file_path=a_file_path) + + torch_save_mock.assert_called() + torch_save_mock.assert_called_with(model_mock().state_dict(), a_file_path) + if __name__ == "__main__": unittest.main() From 6b7ff291cb1825ac540c9a492668f7314a3f64aa Mon Sep 17 00:00:00 2001 From: davebulaval Date: Thu, 4 Aug 2022 21:17:17 -0400 Subject: [PATCH 049/195] bumb actions version (checkout and setup-python --- .github/workflows/deploy.yml | 4 ++-- .github/workflows/docs.yml | 4 ++-- .github/workflows/formatting.yml | 4 ++-- .github/workflows/linting.yml | 4 ++-- .github/workflows/python-publish.yml | 4 ++-- .github/workflows/tests.yml | 8 ++++---- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fcfdf599..498910c0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -12,9 +12,9 @@ jobs: DEEPPARSE_RELEASE_BUILD: "1" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: "3.10" - name: Install dependencies diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6a093454..dfe5893b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,9 +14,9 @@ jobs: python-version: [ "3.7", "3.8", "3.9", "3.10" ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index 51fd5763..c1d8aaea 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -10,9 +10,9 @@ jobs: python-version: [ "3.7", "3.8", "3.9", "3.10" ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 42ebc4c2..b2581a41 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -10,9 +10,9 @@ jobs: python-version: [ "3.7", "3.8", "3.9", "3.10" ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 55cd1ae0..ed3408a4 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -13,9 +13,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: "3.10" - name: Install dependencies diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d4a6449d..bcaa22e4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,9 +10,9 @@ jobs: python-version: [ "3.7", "3.8", "3.9", "3.10" ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -31,9 +31,9 @@ jobs: python-version: [ "3.7", "3.8", "3.9" ] # pip install requirements fail on 3.10 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From b635cf00d0c9c0e734614f051abd6da02d810b37 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Thu, 4 Aug 2022 21:18:25 -0400 Subject: [PATCH 050/195] fixed actions/checkout setted to 4 instead of 3 --- .github/workflows/deploy.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/formatting.yml | 2 +- .github/workflows/linting.yml | 2 +- .github/workflows/python-publish.yml | 2 +- .github/workflows/tests.yml | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 498910c0..44131cb4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -12,7 +12,7 @@ jobs: DEEPPARSE_RELEASE_BUILD: "1" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index dfe5893b..90be685c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,7 +14,7 @@ jobs: python-version: [ "3.7", "3.8", "3.9", "3.10" ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index c1d8aaea..72991179 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -10,7 +10,7 @@ jobs: python-version: [ "3.7", "3.8", "3.9", "3.10" ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index b2581a41..4ea7b976 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -10,7 +10,7 @@ jobs: python-version: [ "3.7", "3.8", "3.9", "3.10" ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index ed3408a4..2b0971a1 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bcaa22e4..32e28d45 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: python-version: [ "3.7", "3.8", "3.9", "3.10" ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: @@ -31,7 +31,7 @@ jobs: python-version: [ "3.7", "3.8", "3.9" ] # pip install requirements fail on 3.10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: From 77f29277393efa55fbf8d552ef3b9f68d69288fa Mon Sep 17 00:00:00 2001 From: davebulaval Date: Thu, 4 Aug 2022 21:20:16 -0400 Subject: [PATCH 051/195] add dependabot --- .github/dependabot.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..96b2889c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "monthly" + open-pull-requests-limit: 10 + allow: + - dependency-type: direct + - dependency-type: indirect \ No newline at end of file From e329471489fd897b943a75321d5c63b26f3a544e Mon Sep 17 00:00:00 2001 From: davebulaval Date: Thu, 4 Aug 2022 21:22:03 -0400 Subject: [PATCH 052/195] bump stale to v5 --- .github/workflows/60-days-stale-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/60-days-stale-check.yml b/.github/workflows/60-days-stale-check.yml index a7c5b028..330aaad8 100644 --- a/.github/workflows/60-days-stale-check.yml +++ b/.github/workflows/60-days-stale-check.yml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v3.0.14 + - uses: actions/stale@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: "This issue is stale because it has been open 60 days with no activity.
From b4fb9a5d4db08d3ecbdbf4fd24824400a28e8ac6 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Thu, 4 Aug 2022 21:32:11 -0400 Subject: [PATCH 053/195] add python 3.11 in linting --- .github/workflows/linting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 4ea7b976..0016a09b 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ "3.7", "3.8", "3.9", "3.10" ] + python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ] steps: - uses: actions/checkout@v3 From 680ab192362ece0c2aed1d4a579fcb7c3399199a Mon Sep 17 00:00:00 2001 From: davebulaval Date: Thu, 4 Aug 2022 21:34:26 -0400 Subject: [PATCH 054/195] remove python 3.11 since not supported for now and add 3.10 in windows test to see if still fails --- .github/workflows/linting.yml | 2 +- .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 0016a09b..4ea7b976 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ] + python-version: [ "3.7", "3.8", "3.9", "3.10" ] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 32e28d45..fe772631 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,7 +28,7 @@ jobs: runs-on: windows-latest strategy: matrix: - python-version: [ "3.7", "3.8", "3.9" ] # pip install requirements fail on 3.10 + python-version: [ "3.7", "3.8", "3.9", "3.10" ] # pip install requirements fail on 3.10 steps: - uses: actions/checkout@v3 From ceb9efffc544ef2a32ed96c46a659e585097f101 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Thu, 4 Aug 2022 21:39:48 -0400 Subject: [PATCH 055/195] revert windoes python 3.10 since still fail --- .github/workflows/tests.yml | 2 +- CHANGELOG.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fe772631..32e28d45 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,7 +28,7 @@ jobs: runs-on: windows-latest strategy: matrix: - python-version: [ "3.7", "3.8", "3.9", "3.10" ] # pip install requirements fail on 3.10 + python-version: [ "3.7", "3.8", "3.9" ] # pip install requirements fail on 3.10 steps: - uses: actions/checkout@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 00b3f1fa..094acee2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -253,4 +253,5 @@ ## dev -- Add `save_model_weights` method to `AddressParser` to save model weights (PyTorch state dictionary) \ No newline at end of file +- Add `save_model_weights` method to `AddressParser` to save model weights (PyTorch state dictionary) +- Improve CI \ No newline at end of file From b3d76a4df9c18442abfd2d5e10a29929826e69ab Mon Sep 17 00:00:00 2001 From: David Beauchemin Date: Thu, 4 Aug 2022 21:43:31 -0400 Subject: [PATCH 056/195] Add codeql (#148) * Create FUNDING.yml * Update README.md * Update FUNDING.yml * Create codeql-analysis.yml --- .github/FUNDING.yml | 3 +++ .github/workflows/codeql-analysis.yml | 37 +++++++++++++++++++++++++++ README.md | 5 +++- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..9175d198 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [davebulaval, mayas3] diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..e29585dd --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,37 @@ +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '3 3 15 * *' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language } + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/README.md b/README.md index 77047bd6..ef61a0ff 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,14 @@ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/deepparse)](https://pypi.org/project/deepparse) [![PyPI Status](https://badge.fury.io/py/deepparse.svg)](https://badge.fury.io/py/deepparse) [![PyPI Status](https://pepy.tech/badge/deepparse)](https://pepy.tech/project/deepparse) -[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](http://www.gnu.org/licenses/lgpl-3.0) + [![Continuous Integration](https://github.com/GRAAL-Research/deepparse/workflows/Continuous%20Integration/badge.svg)](https://github.com/GRAAL-Research/deepparse/actions?query=workflow%3A%22Continuous+Integration%22+branch%3Amaster) [![codecov](https://codecov.io/gh/GRAAL-Research/deepparse/branch/master/graph/badge.svg)](https://codecov.io/gh/GRAAL-Research/deepparse) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/62464699ff0740d0b8064227c4274b98)](https://www.codacy.com/gh/GRAAL-Research/deepparse/dashboard?utm_source=github.com&utm_medium=referral&utm_content=GRAAL-Research/deepparse&utm_campaign=Badge_Grade) +[![pr welcome](https://img.shields.io/badge/PR-Welcome-%23FF8300.svg?)](https://img.shields.io/badge/PR-Welcome-%23FF8300.svg?) +[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](http://www.gnu.org/licenses/lgpl-3.0) + [![Download](https://img.shields.io/badge/Download%20Dataset-blue?style=for-the-badge&logo=download)](https://github.com/GRAAL-Research/deepparse-address-data) [![Rate on Openbase](https://badges.openbase.com/python/rating/deepparse.svg)](https://openbase.com/python/deepparse?utm_source=embedded&utm_medium=badge&utm_campaign=rate-badge) From abe581400f2d5732e1d9db74cf7f881bbbec64af Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 10 Aug 2022 09:54:41 -0400 Subject: [PATCH 057/195] add deprecated warnings class type on deprecated download_from_url_fn --- deepparse/tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deepparse/tools.py b/deepparse/tools.py index cab23d27..baa40bb0 100644 --- a/deepparse/tools.py +++ b/deepparse/tools.py @@ -85,7 +85,8 @@ def latest_version(model: str, cache_path: str, verbose: bool) -> bool: def download_from_url(file_name: str, saving_dir: str, file_extension: str) -> None: # pragma: no cover warnings.warn( "download_from_url is deprecated; use download_from_public_repository to download files from " - "our public repository. The function will be removed in the next major release." + "our public repository. The function will be removed in the next major release.", + DeprecationWarning, ) download_from_public_repository(file_name=file_name, saving_dir=saving_dir, file_extension=file_extension) From 7318093701062e85cc22e70b586e62e5312b3bf5 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 10 Aug 2022 10:47:02 -0400 Subject: [PATCH 058/195] refactored dataset containter creation into a factory --- deepparse/cli/parse.py | 24 ++++++++---------------- deepparse/cli/retrain.py | 29 +++++++---------------------- deepparse/cli/test.py | 29 ++++++++--------------------- deepparse/cli/tools.py | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 59 deletions(-) diff --git a/deepparse/cli/parse.py b/deepparse/cli/parse.py index 34113e83..a77e24f1 100644 --- a/deepparse/cli/parse.py +++ b/deepparse/cli/parse.py @@ -24,8 +24,8 @@ to_json, replace_path_extension, attention_model_type_handling, + data_container_factory, ) -from ..dataset_container import CSVDatasetContainer, PickleDatasetContainer from ..parser import AddressParser @@ -59,21 +59,13 @@ def main(args=None) -> None: parsed_args = get_args(args) dataset_path = parsed_args.dataset_path - if is_csv_path(dataset_path): - csv_column_name = parsed_args.csv_column_name - if csv_column_name is None: - raise ValueError( - "For a CSV dataset path, you need to specify the 'csv_column_name' argument to provide the" - " column name to extract address." - ) - csv_column_separator = parsed_args.csv_column_separator - addresses_to_parse = CSVDatasetContainer( - dataset_path, column_names=csv_column_name, separator=csv_column_separator, is_training_container=False - ) - elif is_pickle_path(dataset_path): - addresses_to_parse = PickleDatasetContainer(dataset_path, is_training_container=False) - else: - raise ValueError("The dataset path argument is not a CSV or a pickle file.") + csv_column_separator = parsed_args.csv_column_separator + addresses_to_parse = data_container_factory( + dataset_path=dataset_path, + csv_column_separator=csv_column_separator, + trainable_dataset=False, + csv_column_names=parsed_args.csv_column_names, + ) export_filename = parsed_args.export_filename export_path = generate_export_path(dataset_path, export_filename) diff --git a/deepparse/cli/retrain.py b/deepparse/cli/retrain.py index dde35544..db12d5c4 100644 --- a/deepparse/cli/retrain.py +++ b/deepparse/cli/retrain.py @@ -13,13 +13,11 @@ add_csv_column_names_arg, ) from .tools import ( - is_csv_path, - is_pickle_path, wrap, bool_parse, attention_model_type_handling, + data_container_factory, ) -from ..dataset_container import CSVDatasetContainer, PickleDatasetContainer from ..parser import AddressParser _retrain_parameters = [ @@ -90,25 +88,12 @@ def main(args=None) -> None: parsed_args = get_args(args) - train_dataset_path = parsed_args.train_dataset_path - if is_csv_path(train_dataset_path): - csv_column_names = parsed_args.csv_column_names - if csv_column_names is None: - raise ValueError( - "To use a CSV dataset to retrain on, you need to specify the 'csv_column_names' argument to provide the" - " column names to extract address and labels (respectively). For example, Address Tags." - ) - csv_column_separator = parsed_args.csv_column_separator - training_data = CSVDatasetContainer( - train_dataset_path, - column_names=csv_column_names, - separator=csv_column_separator, - is_training_container=True, - ) - elif is_pickle_path(train_dataset_path): - training_data = PickleDatasetContainer(train_dataset_path, is_training_container=True) - else: - raise ValueError("The train dataset path argument is not a CSV or a pickle file.") + training_data = data_container_factory( + dataset_path=parsed_args.train_dataset_path, + csv_column_separator=parsed_args.csv_column_separator, + trainable_dataset=True, + csv_column_names=parsed_args.csv_column_names, + ) base_parsing_model = parsed_args.base_parsing_model device = parsed_args.device diff --git a/deepparse/cli/test.py b/deepparse/cli/test.py index 5f866864..96d21942 100644 --- a/deepparse/cli/test.py +++ b/deepparse/cli/test.py @@ -17,14 +17,12 @@ add_csv_column_names_arg, ) from .tools import ( - is_csv_path, - is_pickle_path, wrap, attention_model_type_handling, generate_export_path, replace_path_extension, + data_container_factory, ) -from ..dataset_container import CSVDatasetContainer, PickleDatasetContainer from ..parser import AddressParser @@ -55,24 +53,13 @@ def main(args=None) -> None: parsed_args = get_args(args) test_dataset_path = parsed_args.test_dataset_path - if is_csv_path(test_dataset_path): - csv_column_names = parsed_args.csv_column_names - if csv_column_names is None: - raise ValueError( - "To use a CSV dataset to test on, you need to specify the 'csv_column_names' argument to provide the" - " column name to extract address." - ) - csv_column_separator = parsed_args.csv_column_separator - testing_data = CSVDatasetContainer( - test_dataset_path, - column_names=csv_column_names, - separator=csv_column_separator, - is_training_container=True, - ) - elif is_pickle_path(test_dataset_path): - testing_data = PickleDatasetContainer(test_dataset_path, is_training_container=True) - else: - raise ValueError("The test dataset path argument is not a CSV or a pickle file.") + + testing_data = data_container_factory( + dataset_path=test_dataset_path, + csv_column_separator=parsed_args.csv_column_separator, + trainable_dataset=True, + csv_column_names=parsed_args.csv_column_names, + ) device = parsed_args.device diff --git a/deepparse/cli/tools.py b/deepparse/cli/tools.py index 02ac7708..8804ebba 100644 --- a/deepparse/cli/tools.py +++ b/deepparse/cli/tools.py @@ -7,6 +7,7 @@ import pandas as pd +from ..dataset_container import DatasetContainer, CSVDatasetContainer, PickleDatasetContainer from ..parser import FormattedParsedAddress @@ -179,3 +180,42 @@ def wrap(text, **kwargs): # pragma: no cover text = text.splitlines() text = [textwrap.fill(line, **kwargs) for line in text] return '\n'.join(text) + + +def data_container_factory( + dataset_path: str, + csv_column_separator: str, + trainable_dataset: bool, + csv_column_name: str = None, + csv_column_names: List = None, +) -> DatasetContainer: + """ + Factory to create the trainable dataset container + """ + if is_csv_path(dataset_path): + if trainable_dataset: + # Train or test dataset case + if csv_column_names is None: + raise ValueError( + "To use a CSV dataset to retrain on, you need to specify the 'csv_column_names' argument to " + "provide the column names to extract address and labels (respectively). For example, Address Tags." + ) + else: + # Parse dataset + if csv_column_name is None: + raise ValueError( + "For a CSV dataset path, you need to specify the 'csv_column_name' argument to provide the" + " column name to extract address." + ) + data_container = CSVDatasetContainer( + dataset_path, + column_names=csv_column_names, + separator=csv_column_separator, + is_training_container=trainable_dataset, + ) + elif is_pickle_path(dataset_path): + data_container = PickleDatasetContainer(dataset_path, is_training_container=trainable_dataset) + else: + raise ValueError("The train dataset path argument is not a CSV or a pickle file.") + + return data_container From bcdfe942d87aa1c205d1631a144f0b3f2d60afae Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 10 Aug 2022 10:55:48 -0400 Subject: [PATCH 059/195] fix errors for parsing cases --- deepparse/cli/parse.py | 2 +- deepparse/cli/tools.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/deepparse/cli/parse.py b/deepparse/cli/parse.py index a77e24f1..30b0e0e0 100644 --- a/deepparse/cli/parse.py +++ b/deepparse/cli/parse.py @@ -64,7 +64,7 @@ def main(args=None) -> None: dataset_path=dataset_path, csv_column_separator=csv_column_separator, trainable_dataset=False, - csv_column_names=parsed_args.csv_column_names, + csv_column_name=parsed_args.csv_column_name, ) export_filename = parsed_args.export_filename diff --git a/deepparse/cli/tools.py b/deepparse/cli/tools.py index 8804ebba..ee181b6c 100644 --- a/deepparse/cli/tools.py +++ b/deepparse/cli/tools.py @@ -207,6 +207,7 @@ def data_container_factory( "For a CSV dataset path, you need to specify the 'csv_column_name' argument to provide the" " column name to extract address." ) + csv_column_names = csv_column_name data_container = CSVDatasetContainer( dataset_path, column_names=csv_column_names, From 8578a9ac7ac60c9f7a235fa09c0a145bf9f562bb Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 10 Aug 2022 11:00:11 -0400 Subject: [PATCH 060/195] moved arguments in dataset factory --- deepparse/cli/parse.py | 2 +- deepparse/cli/retrain.py | 2 +- deepparse/cli/test.py | 2 +- deepparse/cli/tools.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deepparse/cli/parse.py b/deepparse/cli/parse.py index 30b0e0e0..f3a38bc3 100644 --- a/deepparse/cli/parse.py +++ b/deepparse/cli/parse.py @@ -62,8 +62,8 @@ def main(args=None) -> None: csv_column_separator = parsed_args.csv_column_separator addresses_to_parse = data_container_factory( dataset_path=dataset_path, - csv_column_separator=csv_column_separator, trainable_dataset=False, + csv_column_separator=csv_column_separator, csv_column_name=parsed_args.csv_column_name, ) diff --git a/deepparse/cli/retrain.py b/deepparse/cli/retrain.py index db12d5c4..864b7db6 100644 --- a/deepparse/cli/retrain.py +++ b/deepparse/cli/retrain.py @@ -90,8 +90,8 @@ def main(args=None) -> None: training_data = data_container_factory( dataset_path=parsed_args.train_dataset_path, - csv_column_separator=parsed_args.csv_column_separator, trainable_dataset=True, + csv_column_separator=parsed_args.csv_column_separator, csv_column_names=parsed_args.csv_column_names, ) diff --git a/deepparse/cli/test.py b/deepparse/cli/test.py index 96d21942..d5a86f64 100644 --- a/deepparse/cli/test.py +++ b/deepparse/cli/test.py @@ -56,8 +56,8 @@ def main(args=None) -> None: testing_data = data_container_factory( dataset_path=test_dataset_path, - csv_column_separator=parsed_args.csv_column_separator, trainable_dataset=True, + csv_column_separator=parsed_args.csv_column_separator, csv_column_names=parsed_args.csv_column_names, ) diff --git a/deepparse/cli/tools.py b/deepparse/cli/tools.py index ee181b6c..361197f0 100644 --- a/deepparse/cli/tools.py +++ b/deepparse/cli/tools.py @@ -184,13 +184,13 @@ def wrap(text, **kwargs): # pragma: no cover def data_container_factory( dataset_path: str, - csv_column_separator: str, trainable_dataset: bool, + csv_column_separator: str = None, csv_column_name: str = None, csv_column_names: List = None, ) -> DatasetContainer: """ - Factory to create the trainable dataset container + Factory to create the trainable dataset container. """ if is_csv_path(dataset_path): if trainable_dataset: From aeb1559612cfcd8fa22634b92bde88f0b7ed303f Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 10 Aug 2022 11:17:18 -0400 Subject: [PATCH 061/195] add tests case for new factory tool fn --- tests/cli/test_tools.py | 96 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/tests/cli/test_tools.py b/tests/cli/test_tools.py index 1edda80d..ac5c45d5 100644 --- a/tests/cli/test_tools.py +++ b/tests/cli/test_tools.py @@ -9,6 +9,7 @@ import pickle from tempfile import TemporaryDirectory from unittest import TestCase +from unittest.mock import patch import pandas as pd @@ -23,6 +24,7 @@ replace_path_extension, attention_model_type_handling, bool_parse, + data_container_factory, ) from deepparse.parser import FormattedParsedAddress @@ -320,3 +322,97 @@ def test_givenNotVariousTrueOrFalseValue_whenCallBoolParse_thenRaiseError(self): for wrong_value in wrong_values: with self.assertRaises(argparse.ArgumentTypeError): bool_parse(wrong_value) + + @patch("deepparse.cli.tools.PickleDatasetContainer") + def test_givenAParseSettingsPickle_whenDataContainerFactory_thenReturnDataContainer(self, dataset_container_mock): + a_pickle_path = "a/pickle/path.p" + + data_container_factory(dataset_path=a_pickle_path, trainable_dataset=False) + + dataset_container_mock.assert_called() + dataset_container_mock.assert_called_with(a_pickle_path, is_training_container=False) + + @patch("deepparse.cli.tools.CSVDatasetContainer") + def test_givenAParseSettingsCSV_whenDataContainerFactory_thenReturnProperlySetDataContainer( + self, dataset_container_mock + ): + a_csv_path = "a/pickle/path.csv" + a_csv_column_name = "a_column_name" + + data_container_factory(dataset_path=a_csv_path, trainable_dataset=False, csv_column_name=a_csv_column_name) + + dataset_container_mock.assert_called() + dataset_container_mock.assert_called_with( + a_csv_path, column_names=a_csv_column_name, separator=None, is_training_container=False + ) + + a_separator = "\t" + + data_container_factory( + dataset_path=a_csv_path, + trainable_dataset=False, + csv_column_name=a_csv_column_name, + csv_column_separator=a_separator, + ) + + dataset_container_mock.assert_called() + dataset_container_mock.assert_called_with( + a_csv_path, column_names=a_csv_column_name, separator=a_separator, is_training_container=False + ) + + def test_givenAParseOrTrainWrongExtension_thenRaiseError(self): + wrong_file_extension = "a/wrong/file/extension.txt" + with self.assertRaises(ValueError): + data_container_factory(dataset_path=wrong_file_extension, trainable_dataset=False) + + wrong_file_extension = "a/wrong/file/extension.txt" + with self.assertRaises(ValueError): + data_container_factory(dataset_path=wrong_file_extension, trainable_dataset=True) + + def test_givenAParseOrTrainCSVFileWithWrongSettings_thenRaiseError(self): + a_csv_path = "a/pickle/path.csv" + + # No CSV columns names + with self.assertRaises(ValueError): + data_container_factory(dataset_path=a_csv_path, trainable_dataset=False) + + with self.assertRaises(ValueError): + data_container_factory(dataset_path=a_csv_path, trainable_dataset=True) + + @patch("deepparse.cli.tools.PickleDatasetContainer") + def test_givenATrainSettingsPickle_whenDataContainerFactory_thenReturnDataContainer(self, dataset_container_mock): + a_pickle_path = "a/pickle/path.p" + + data_container_factory(dataset_path=a_pickle_path, trainable_dataset=True) + + dataset_container_mock.assert_called() + dataset_container_mock.assert_called_with(a_pickle_path, is_training_container=True) + + @patch("deepparse.cli.tools.CSVDatasetContainer") + def test_givenATrainSettingsCSV_whenDataContainerFactory_thenReturnProperlySetDataContainer( + self, dataset_container_mock + ): + # Good for train, test and val type data + a_csv_path = "a/pickle/path.csv" + a_csv_column_names = ["a_column_name", "a_column_name"] + + data_container_factory(dataset_path=a_csv_path, trainable_dataset=True, csv_column_names=a_csv_column_names) + + dataset_container_mock.assert_called() + dataset_container_mock.assert_called_with( + a_csv_path, column_names=a_csv_column_names, separator=None, is_training_container=True + ) + + a_separator = "\t" + + data_container_factory( + dataset_path=a_csv_path, + trainable_dataset=True, + csv_column_names=a_csv_column_names, + csv_column_separator=a_separator, + ) + + dataset_container_mock.assert_called() + dataset_container_mock.assert_called_with( + a_csv_path, column_names=a_csv_column_names, separator=a_separator, is_training_container=True + ) From 2971fad0c9525ec0649612bb1edec3aac4449710 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 10 Aug 2022 11:24:59 -0400 Subject: [PATCH 062/195] added val dataset handling --- deepparse/cli/retrain.py | 25 +++++- deepparse/parser/address_parser.py | 135 ++++++++++++++++++++--------- tests/cli/test_retrain.py | 13 +++ 3 files changed, 131 insertions(+), 42 deletions(-) diff --git a/deepparse/cli/retrain.py b/deepparse/cli/retrain.py index 864b7db6..cef603b2 100644 --- a/deepparse/cli/retrain.py +++ b/deepparse/cli/retrain.py @@ -95,6 +95,15 @@ def main(args=None) -> None: csv_column_names=parsed_args.csv_column_names, ) + val_data = parsed_args.val_dataset_path + if val_data is not None: + val_data = data_container_factory( + dataset_path=parsed_args.val_dataset_path, + trainable_dataset=True, + csv_column_separator=parsed_args.csv_column_separator, + csv_column_names=parsed_args.csv_column_names, + ) + base_parsing_model = parsed_args.base_parsing_model device = parsed_args.device @@ -108,7 +117,9 @@ def main(args=None) -> None: parsed_retain_arguments = parse_retrained_arguments(parsed_args) - address_parser.retrain(dataset_container=training_data, **parsed_retain_arguments) + address_parser.retrain( + train_dataset_container=training_data, val_dataset_container=val_data, **parsed_retain_arguments + ) def get_parser() -> argparse.ArgumentParser: @@ -124,6 +135,18 @@ def get_parser() -> argparse.ArgumentParser: type=str, ) + parser.add_argument( + "--val_dataset_path", + help=wrap( + "The path to the validation dataset file in a pickle (.p, .pickle or .pckl) or CSV format. " + "If the dataset are CSV, both train and val must have the same CSV formatting " + "(columns names). If not provided, the train dataset will be split in a train and val " + "dataset (default is None)." + ), + type=str, + default=None, + ) + parser.add_argument( "--train_ratio", help=wrap( diff --git a/deepparse/parser/address_parser.py b/deepparse/parser/address_parser.py index dbe1f4a0..6ff26c64 100644 --- a/deepparse/parser/address_parser.py +++ b/deepparse/parser/address_parser.py @@ -387,7 +387,8 @@ def __call__( def retrain( self, - dataset_container: DatasetContainer, + train_dataset_container: DatasetContainer = None, + val_dataset_container: Union[DatasetContainer, None] = None, train_ratio: float = 0.8, batch_size: int = 32, epochs: int = 5, @@ -415,12 +416,25 @@ def retrain( prediction tags, and if new ``seq2seq_params`` were used, the new seq2seq parameters. Args: - dataset_container (~deepparse.dataset_container.DatasetContainer): The dataset container of the data to use - such as any PyTorch Dataset (:class:`~torch.utils.data.Dataset`) user define class or one of our two - DatasetContainer (:class:`~deepparse.dataset_container.PickleDatasetContainer` or - :class:`~deepparse.dataset_container.CSVDatasetContainer`) - train_ratio (float): The ratio to use of the dataset for the training. The rest of the data is used for the - validation (e.g. a train ratio of 0.8 mean a 80-20 train-valid split) (by default, ``0.8``). + train_dataset_container (~deepparse.dataset_container.DatasetContainer): The train dataset container of + the training data to use such as any PyTorch Dataset + (:class:`~torch.utils.data.Dataset`) user define class or one of our + DatasetContainer (:class:`~deepparse.dataset_container.PickleDatasetContainer`, + :class:`~deepparse.dataset_container.CSVDatasetContainer` or + :class:`~deepparse.dataset_container.ListDatasetContainer`). The train dataset is use as two ways: + + 1. As is if a validating dataset is provided (``val_dataset_container``). + 2. Split in a training and validation dataset if ``val_dataset_container`` is set to None. + + Thus, it means that if ``val_dataset_container`` is set to the None default settings, we use the + ``train_ratio`` argument to split the training dataset into a train and val dataset. See examples for + more details. + val_dataset_container (Union[~deepparse.dataset_container.DatasetContainer, None]): The validation dataset + container to use for validating the model (by default, ``None``). + train_ratio (float): The ratio to use of the ``train_dataset_container`` for the training procedure. + The rest of the data is used for the validation (e.g. a train ratio of 0.8 mean an + 80-20 train-valid split) (by default, ``0.8``). The argument is ignored if ``val_dataset_container`` is + not None. batch_size (int): The size of the batch (by default, ``32``). epochs (int): The number of training epochs (by default, ``5``). num_workers (int): The number of workers to use for the data loader (by default, ``1`` worker). @@ -520,6 +534,8 @@ def retrain( container = PickleDatasetContainer(data_path) + # The validation dataset is created from the training dataset (container) + # 80% of the data is use for training and 20% as a validation dataset address_parser.retrain(container, 0.8, epochs=1, batch_size=128) Using the freezing layers parameters to freeze layers during training @@ -527,10 +543,17 @@ def retrain( .. code-block:: python address_parser = AddressParser(device=0) - data_path = "path_to_a_csv_dataset.p" + data_path = "path_to_a_csv_dataset.p" container = CSVDatasetContainer(data_path) - address_parser.retrain(container, 0.8, epochs=5, batch_size=128, layers_to_freeze="encoder") + + val_data_path = "path_to_a_csv_val_dataset.p" + val_container = CSVDatasetContainer(val_data_path) + + # We provide the train dataset (container) and the val dataset (val_container) + # Thus, the train_ratio argument is ignored, and we use instead the val_container + # as the validating dataset. + address_parser.retrain(container, val_container, epochs=5, batch_size=128, layers_to_freeze="encoder") Using learning rate scheduler callback. @@ -601,31 +624,9 @@ def retrain( name_of_the_retrain_parser="MyParserName") """ - - if "fasttext-light" in self.model_type: - raise ValueError("It's not possible to retrain a fasttext-light due to pymagnitude problem.") - - if platform.system().lower() == "windows" and "fasttext" in self.model_type and num_workers > 0: - raise ValueError( - "On Windows system, we cannot retrain FastText like models with parallelism workers since " - "FastText objects are not pickleable with the parallelism process use by Windows. " - "Thus, you need to set num_workers to 0 since 1 also means 'parallelism'." - ) - - if not isinstance(dataset_container, DatasetContainer): - raise ValueError( - "The dataset_container has to be a DatasetContainer. " - "Read the docs at https://deepparse.org/ for more details." - ) - - if not dataset_container.is_a_train_container(): - raise ValueError("The dataset container is not a train container.") - - if name_of_the_retrain_parser is not None: - if len(name_of_the_retrain_parser.split(".")) > 1: - raise ValueError( - "The name_of_the_retrain_parser should NOT include a file extension or a dot-like filename style." - ) + self._retrain_argumentation_validations( + train_dataset_container, val_dataset_container, num_workers, name_of_the_retrain_parser + ) model_factory_dict = {"prediction_layer_len": 9} # We set the default output dim size @@ -659,7 +660,7 @@ def retrain( callbacks = [] if callbacks is None else callbacks train_generator, valid_generator = self._create_training_data_generator( - dataset_container, train_ratio, batch_size, num_workers, seed=seed + train_dataset_container, val_dataset_container, train_ratio, batch_size, num_workers, seed=seed ) if layers_to_freeze is not None and seq2seq_params is None: @@ -950,7 +951,8 @@ def _set_data_transformer(self) -> DataTransform: def _create_training_data_generator( self, - dataset_container: DatasetContainer, + train_dataset_container: DatasetContainer, + val_dataset_container: DatasetContainer, train_ratio: float, batch_size: int, num_workers: int, @@ -959,11 +961,18 @@ def _create_training_data_generator( # pylint: disable=too-many-arguments data_transform = self._set_data_transformer() - train_indices, valid_indices = indices_splitting( - num_data=len(dataset_container), train_ratio=train_ratio, seed=seed - ) + if val_dataset_container is None: + train_indices, valid_indices = indices_splitting( + num_data=len(train_dataset_container), train_ratio=train_ratio, seed=seed + ) + + train_dataset = Subset(train_dataset_container, train_indices) + + valid_dataset = Subset(train_dataset_container, valid_indices) + else: + train_dataset = train_dataset_container + valid_dataset = val_dataset_container - train_dataset = Subset(dataset_container, train_indices) train_generator = DataLoader( train_dataset, collate_fn=data_transform.teacher_forcing_transform, @@ -972,7 +981,6 @@ def _create_training_data_generator( shuffle=True, ) - valid_dataset = Subset(dataset_container, valid_indices) valid_generator = DataLoader( valid_dataset, collate_fn=data_transform.output_transform, @@ -1125,3 +1133,48 @@ def _formatted_named_parser_name(self, prediction_tags: Dict, seq2seq_params: Di layers_to_freeze_str = f"FreezedLayer{layers_to_freeze.capitalize()}" if layers_to_freeze is not None else "" parser_name = self._model_type_formatted + prediction_tags_str + seq2seq_params_str + layers_to_freeze_str return parser_name + + def _retrain_argumentation_validations( + self, + train_dataset_container: DatasetContainer, + val_dataset_container: DatasetContainer, + num_workers: int, + name_of_the_retrain_parser: Union[str, None], + ): + """ + Arguments validation test for retrain methods. + """ + if "fasttext-light" in self.model_type: + raise ValueError("It's not possible to retrain a fasttext-light due to pymagnitude problem.") + + if platform.system().lower() == "windows" and "fasttext" in self.model_type and num_workers > 0: + raise ValueError( + "On Windows system, we cannot retrain FastText like models with parallelism workers since " + "FastText objects are not pickleable with the parallelism process use by Windows. " + "Thus, you need to set num_workers to 0 since 1 also means 'parallelism'." + ) + + if not isinstance(train_dataset_container, DatasetContainer): + raise ValueError( + "The train dataset container (train_dataset_container) has to be a DatasetContainer. " + "Read the docs at https://deepparse.org/ for more details." + ) + + if not train_dataset_container.is_a_train_container(): + raise ValueError("The train dataset container (train_dataset_container) is not a trainable container.") + + if val_dataset_container is not None: + if not isinstance(val_dataset_container, DatasetContainer): + raise ValueError( + "The val dataset container (val_dataset_container) has to be a DatasetContainer. " + "Read the docs at https://deepparse.org/ for more details." + ) + + if not val_dataset_container.is_a_train_container(): + raise ValueError("The val dataset container (val_dataset_container) is not a trainable container.") + + if name_of_the_retrain_parser is not None: + if len(name_of_the_retrain_parser.split(".")) > 1: + raise ValueError( + "The name_of_the_retrain_parser should NOT include a file extension or a dot-like filename style." + ) diff --git a/tests/cli/test_retrain.py b/tests/cli/test_retrain.py index afd005bc..9d7f6a1a 100644 --- a/tests/cli/test_retrain.py +++ b/tests/cli/test_retrain.py @@ -53,6 +53,7 @@ def set_up_params( self, model_type=None, # None to handle the default tests case. train_dataset_path=None, # None to handle the default tests case. + val_dataset_path=None, train_ratio="0.8", batch_size="32", epochs="1", # As opposed to default CLI function, we set the epoch value number to 1, @@ -81,6 +82,8 @@ def set_up_params( parser_params = [ model_type, train_dataset_path, + "--val_dataset_path", + val_dataset_path, "--train_ratio", train_ratio, "--batch_size", @@ -264,6 +267,16 @@ def test_ifCachePath_thenUseNewCachePath(self): device=self.cpu_device, cache_dir=self.a_cache_dir, model_type=self.a_fasttext_model_type ) # Default tests case default model type is the FastText model + def test_integrationWithValDataset(self): + parser_params = self.set_up_params(device=self.cpu_device, val_dataset_path=self.a_train_pickle_dataset_path) + retrain.main(parser_params) + + self.assertTrue( + os.path.isfile( + os.path.join(self.temp_checkpoints_obj.name, "checkpoints", "retrained_fasttext_address_parser.ckpt") + ) + ) + if __name__ == "__main__": unittest.main() From 9cc7201fad7e983e61ab376c7c1cbe67bd1f769b Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 10 Aug 2022 13:41:22 -0400 Subject: [PATCH 063/195] fixed tests and remove major release todo --- deepparse/parser/address_parser.py | 2 +- major_release_todo.md | 2 - tests/parser/integration/base_predict.py | 9 +-- tests/parser/integration/base_retrain.py | 12 ++-- ...t_integration_address_parser_new_params.py | 20 +++--- ...tion_address_parser_new_params_new_tags.py | 13 ++-- ..._integration_address_parser_retrain_api.py | 59 +++++++++++++++ ..._integration_address_parser_retrain_cpu.py | 26 +++---- ...on_address_parser_retrain_freeze_layers.py | 12 ++-- ..._integration_address_parser_retrain_gpu.py | 37 +++++++--- ...s_parser_retrain_new_address_components.py | 48 ++++++------- .../parser/test_address_parser_retrain_api.py | 71 ++++++++++++++++--- 12 files changed, 221 insertions(+), 90 deletions(-) delete mode 100644 major_release_todo.md create mode 100644 tests/parser/integration/test_integration_address_parser_retrain_api.py diff --git a/deepparse/parser/address_parser.py b/deepparse/parser/address_parser.py index 6ff26c64..e21785a1 100644 --- a/deepparse/parser/address_parser.py +++ b/deepparse/parser/address_parser.py @@ -387,7 +387,7 @@ def __call__( def retrain( self, - train_dataset_container: DatasetContainer = None, + train_dataset_container: DatasetContainer, val_dataset_container: Union[DatasetContainer, None] = None, train_ratio: float = 0.8, batch_size: int = 32, diff --git a/major_release_todo.md b/major_release_todo.md deleted file mode 100644 index d35d184b..00000000 --- a/major_release_todo.md +++ /dev/null @@ -1,2 +0,0 @@ -- Remove deprecated `download_from_url` function -- https://zenodo.org/account/settings/github/repository/GRAAL-Research/deepparse \ No newline at end of file diff --git a/tests/parser/integration/base_predict.py b/tests/parser/integration/base_predict.py index f92b26ff..1c4db84f 100644 --- a/tests/parser/integration/base_predict.py +++ b/tests/parser/integration/base_predict.py @@ -93,14 +93,15 @@ def tearDown(self) -> None: def training( self, address_parser: AddressParser, - data_container: DatasetContainer, - num_workers: int, + train_data_container: DatasetContainer, + num_workers: int = 1, prediction_tags=None, seq2seq_params=None, ): address_parser.retrain( - data_container, - self.a_train_ratio, + train_data_container, + val_dataset_container=None, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=num_workers, diff --git a/tests/parser/integration/base_retrain.py b/tests/parser/integration/base_retrain.py index 31ab11c1..a8cc2a62 100644 --- a/tests/parser/integration/base_retrain.py +++ b/tests/parser/integration/base_retrain.py @@ -1,6 +1,6 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # no-member skip is so because child define the training_container in setup -# pylint: disable=not-callable, too-many-public-methods, no-member +# pylint: disable=not-callable, too-many-public-methods, no-member, too-many-arguments # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with @@ -91,13 +91,15 @@ def tearDown(self) -> None: def training( self, address_parser: AddressParser, - data_container: DatasetContainer, - num_workers: int, + train_data_container: DatasetContainer, + val_data_container: DatasetContainer = None, + num_workers: int = 1, prediction_tags=None, ): address_parser.retrain( - data_container, - self.a_train_ratio, + train_data_container, + val_data_container, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=num_workers, diff --git a/tests/parser/integration/test_integration_address_parser_new_params.py b/tests/parser/integration/test_integration_address_parser_new_params.py index f78142c8..c99a8ed6 100644 --- a/tests/parser/integration/test_integration_address_parser_new_params.py +++ b/tests/parser/integration/test_integration_address_parser_new_params.py @@ -22,8 +22,8 @@ def test_givenAAddress_whenParseNewParamsFastTextCPU_thenParseAddressProperly(se ) self.training( fasttext_address_parser, - self.training_container, - self.a_number_of_workers, + train_data_container=self.training_container, + num_workers=self.a_number_of_workers, seq2seq_params=self.seq2seq_params, ) @@ -60,8 +60,8 @@ def test_givenAAddress_whenParseNewParamsFastTextGPU_thenParseAddressProperly(se ) self.training( fasttext_address_parser, - self.training_container, - self.a_number_of_workers, + train_data_container=self.training_container, + num_workers=self.a_number_of_workers, seq2seq_params=self.seq2seq_params, ) @@ -73,7 +73,7 @@ def test_givenAAddress_whenParseNewParamsFastTextGPU_thenParseAddressProperly(se path_to_retrained_model=self.a_fasttext_retrain_model_path, ) - # Since we train a smaller model, it sometime return EOS, so we manage it by adding the EOS tag + # Since we train a smaller model, it sometime returns EOS, so we manage it by adding the EOS tag formatted_parsed_address.FIELDS = [ "StreetNumber", "Unit", @@ -85,7 +85,7 @@ def test_givenAAddress_whenParseNewParamsFastTextGPU_thenParseAddressProperly(se "GeneralDelivery", "EOS", ] - # We validate that the new settings are loaded and we can parse + # We validate that the new settings are loaded, and we can parse parse_address = fasttext_address_parser(self.an_address_to_parse) self.assertIsInstance(parse_address, FormattedParsedAddress) @@ -98,8 +98,8 @@ def test_givenAAddress_whenParseNewParamsBPEmbCPU_thenParseAddressProperly(self) ) self.training( bpemb_address_parser, - self.training_container, - self.a_number_of_workers, + train_data_container=self.training_container, + num_workers=self.a_number_of_workers, seq2seq_params=self.seq2seq_params, ) @@ -137,8 +137,8 @@ def test_givenAAddress_whenParseNewParamsBPEmbGPU_thenParseAddressProperly(self) ) self.training( bpemb_address_parser, - self.training_container, - self.a_number_of_workers, + train_data_container=self.training_container, + num_workers=self.a_number_of_workers, seq2seq_params=self.seq2seq_params, ) diff --git a/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py b/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py index 74222bbd..db3d237b 100644 --- a/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py +++ b/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py @@ -83,14 +83,15 @@ def tearDown(self) -> None: def training( self, address_parser: AddressParser, - data_container: DatasetContainer, - num_workers: int, + train_data_container: DatasetContainer, + num_workers: int = 1, prediction_tags=None, seq2seq_params=None, ): address_parser.retrain( - data_container, - self.a_train_ratio, + train_data_container, + val_dataset_container=None, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=num_workers, @@ -111,7 +112,7 @@ def test_givenAAddress_whenParseNewParamsNewTagsBPEmb_thenParseAddressProperly( self.training( bpemb_address_parser, self.training_container, - self.a_number_of_workers, + num_workers=self.a_number_of_workers, seq2seq_params=self.seq2seq_params, prediction_tags=self.with_new_prediction_tags, ) @@ -143,7 +144,7 @@ def test_givenAAddress_whenParseNewParamsNewTagsFastText_thenParseAddressProperl self.training( fasttext_address_parser, self.training_container, - self.a_number_of_workers, + num_workers=self.a_number_of_workers, seq2seq_params=self.seq2seq_params, prediction_tags=self.with_new_prediction_tags, ) diff --git a/tests/parser/integration/test_integration_address_parser_retrain_api.py b/tests/parser/integration/test_integration_address_parser_retrain_api.py new file mode 100644 index 00000000..9f8899cc --- /dev/null +++ b/tests/parser/integration/test_integration_address_parser_retrain_api.py @@ -0,0 +1,59 @@ +# Bug with PyTorch source code makes torch.tensor as not callable for pylint. +# We also skip protected-access since we test the encoder and decoder step +# pylint: disable=not-callable, too-many-public-methods + +import os +import unittest +from unittest import skipIf + +from deepparse.parser import AddressParser +from tests.base_capture_output import CaptureOutputTestCase +from tests.parser.integration.base_retrain import AddressParserRetrainTestCase + + +@skipIf( + not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), + "download of model too long for test in runner", +) +class AddressParserIntegrationRetrainCPUTest(AddressParserRetrainTestCase, CaptureOutputTestCase): + def test_givenAFasttextAddressParser_whenRetrainNoValDataset_thenTrainingOccur(self): + address_parser = AddressParser( + model_type=self.a_fasttext_model_type, + device=self.a_cpu_device, + verbose=self.verbose, + ) + + performance_after_training = address_parser.retrain( + self.training_container, + val_dataset_container=None, + train_ratio=self.a_train_ratio, + epochs=self.a_single_epoch, + batch_size=self.a_batch_size, + num_workers=self.a_number_of_workers, + logging_path=self.a_checkpoints_saving_dir, + ) + + self.assertIsNotNone(performance_after_training) + + def test_givenAFasttextAddressParser_whenRetrainWithValDataset_thenTrainingOccur(self): + address_parser = AddressParser( + model_type=self.a_fasttext_model_type, + device=self.a_cpu_device, + verbose=self.verbose, + ) + + performance_after_training = address_parser.retrain( + self.training_container, + val_dataset_container=self.training_container, + train_ratio=self.a_train_ratio, + epochs=self.a_single_epoch, + batch_size=self.a_batch_size, + num_workers=self.a_number_of_workers, + logging_path=self.a_checkpoints_saving_dir, + ) + + self.assertIsNotNone(performance_after_training) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/parser/integration/test_integration_address_parser_retrain_cpu.py b/tests/parser/integration/test_integration_address_parser_retrain_cpu.py index 120cc563..97a9c1ed 100644 --- a/tests/parser/integration/test_integration_address_parser_retrain_cpu.py +++ b/tests/parser/integration/test_integration_address_parser_retrain_cpu.py @@ -28,7 +28,7 @@ def test_givenAFasttextAddressParser_whenRetrain_thenTrainingOccur(self): performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -51,7 +51,7 @@ def test_givenAnAddressParser_whenRetrainWithPoutyne17andBefore_thenTrainingOccu address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -81,7 +81,7 @@ def test_givenAnAddressParser_whenRetrainWithPoutyne18andAfter_thenTrainingOccur address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -112,7 +112,7 @@ def test_givenAnAddressParser_whenRetrainWithPoutyne111andAfter_thenTrainingOccu address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -141,7 +141,7 @@ def test_givenAnAddressParser_whenRetrainWithPoutyne2_thenTrainingOccurWithoutAW address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -169,7 +169,7 @@ def test_givenAFasttextAddressParser_whenRetrainMultipleEpochs_thenTrainingOccur performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_three_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -187,7 +187,7 @@ def test_givenAFasttextAddressParser_whenRetrainWithConfig_thenTrainingOccur(sel performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -209,7 +209,7 @@ def test_givenAFasttextAddressParser_whenRetrainWithConfigWithCallbacks_thenCall callback_mock = MagicMock(spec=Callback) performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -237,7 +237,7 @@ def test_givenAFasttextLightAddressParser_whenRetrain_thenTrainingDoesNotOccur( with self.assertRaises(ValueError): address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -253,7 +253,7 @@ def test_givenABPEmbAddressParser_whenRetrain_thenTrainingOccur(self): performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -273,7 +273,7 @@ def test_givenABPEmbAddressParser_whenRetrainMultipleEpochs_thenTrainingOccurCor performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_three_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -291,7 +291,7 @@ def test_givenABPEmbAddressParser_whenRetrainWithConfig_thenTrainingOccur(self): performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -313,7 +313,7 @@ def test_givenABPEmbAddressParser_whenRetrainWithConfigWithCallbacks_thenCallbac callback_mock = MagicMock(spec=Callback) performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, diff --git a/tests/parser/integration/test_integration_address_parser_retrain_freeze_layers.py b/tests/parser/integration/test_integration_address_parser_retrain_freeze_layers.py index 45ad6cbd..01426c2f 100644 --- a/tests/parser/integration/test_integration_address_parser_retrain_freeze_layers.py +++ b/tests/parser/integration/test_integration_address_parser_retrain_freeze_layers.py @@ -32,7 +32,7 @@ def test_givenEncoderToFreeze_thenFreezeLayer(self): ) address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -52,7 +52,7 @@ def test_givenDecoderToFreeze_thenFreezeLayer(self): ) address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -72,7 +72,7 @@ def test_givenDecoderBPEmbToFreeze_thenFreezeEmbeddingsLayer(self): ) address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -90,7 +90,7 @@ def test_givenSeq2SeqToFreeze_thenFreezeLayer(self): ) address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -110,7 +110,7 @@ def test_givenSeq2SeqBPEmbToFreeze_thenFreezeEmbeddingsLayer(self): ) address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -128,7 +128,7 @@ def test_givenLinearToFreeze_thenFreezeLayer(self): ) address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, diff --git a/tests/parser/integration/test_integration_address_parser_retrain_gpu.py b/tests/parser/integration/test_integration_address_parser_retrain_gpu.py index 29fbbcac..d5b0a686 100644 --- a/tests/parser/integration/test_integration_address_parser_retrain_gpu.py +++ b/tests/parser/integration/test_integration_address_parser_retrain_gpu.py @@ -23,7 +23,26 @@ def test_givenAFasttextAddressParser_whenRetrain_thenTrainingOccur(self): performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, + epochs=self.a_single_epoch, + batch_size=self.a_batch_size, + num_workers=self.a_number_of_workers, + logging_path=self.a_checkpoints_saving_dir, + ) + + self.assertIsNotNone(performance_after_training) + + def test_givenAFasttextAddressParser_whenRetrainWithValDataset_thenTrainingOccur(self): + address_parser = AddressParser( + model_type=self.a_fasttext_model_type, + device=self.a_torch_device, + verbose=self.verbose, + ) + + performance_after_training = address_parser.retrain( + self.training_container, + val_dataset_container=self.training_container, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -43,7 +62,7 @@ def test_givenAFasttextAddressParser_whenRetrainMultipleEpochs_thenTrainingOccur performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_three_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -61,7 +80,7 @@ def test_givenAFasttextAddressParser_whenRetrainWithConfig_thenTrainingOccur(sel performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -83,7 +102,7 @@ def test_givenAFasttextAddressParser_whenRetrainWithConfigWithCallbacks_thenCall callback_mock = MagicMock(spec=Callback) performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -111,7 +130,7 @@ def test_givenAFasttextLightAddressParser_whenRetrain_thenTrainingDoesNotOccur( with self.assertRaises(ValueError): _ = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -127,7 +146,7 @@ def test_givenABPEmbAddressParser_whenRetrain_thenTrainingOccur(self): performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -147,7 +166,7 @@ def test_givenABPEmbAddressParser_whenRetrainMultipleEpochs_thenTrainingOccurCor performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_three_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -165,7 +184,7 @@ def test_givenABPEmbAddressParser_whenRetrainWithConfig_thenTrainingOccur(self): performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -187,7 +206,7 @@ def test_givenABPEmbAddressParser_whenRetrainWithConfigWithCallbacks_thenCallbac callback_mock = MagicMock(spec=Callback) performance_after_training = address_parser.retrain( self.training_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, diff --git a/tests/parser/integration/test_integration_address_parser_retrain_new_address_components.py b/tests/parser/integration/test_integration_address_parser_retrain_new_address_components.py index a959119b..943df9c5 100644 --- a/tests/parser/integration/test_integration_address_parser_retrain_new_address_components.py +++ b/tests/parser/integration/test_integration_address_parser_retrain_new_address_components.py @@ -42,7 +42,7 @@ def test_givenAFasttextAddressParser_whenRetrainNewTags_thenTrainingOccur(self): performance_after_training = address_parser.retrain( self.new_prediction_data_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -63,7 +63,7 @@ def test_givenAFasttextAddressParser_whenRetrainMultipleEpochsNewTags_thenTraini performance_after_training = address_parser.retrain( self.new_prediction_data_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_three_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -84,7 +84,7 @@ def test_givenAFasttextAddressParser_whenRetrainWithConfigNewTags_thenTrainingOc performance_after_training = address_parser.retrain( self.new_prediction_data_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -107,7 +107,7 @@ def test_givenAFasttextAddressParser_whenRetrainWithConfigWithCallbacksNewTags_t callback_mock = MagicMock(spec=Callback) performance_after_training = address_parser.retrain( self.new_prediction_data_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -136,7 +136,7 @@ def test_givenAFasttextLightAddressParser_whenRetrainNewTags_thenTrainingDoesNot with self.assertRaises(ValueError): _ = address_parser.retrain( self.new_prediction_data_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -153,7 +153,7 @@ def test_givenAddressParser_whenRetrainNewTagsNoEOS_thenTrainingDoesNotOccur(sel with self.assertRaises(ValueError): _ = address_parser.retrain( self.new_prediction_data_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -170,7 +170,7 @@ def test_givenABPEmbAddressParser_whenRetrainNewTags_thenTrainingOccur(self): performance_after_training = address_parser.retrain( self.new_prediction_data_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -191,7 +191,7 @@ def test_givenABPEmbAddressParser_whenRetrainMultipleEpochsNewTags_thenTrainingO performance_after_training = address_parser.retrain( self.new_prediction_data_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_three_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -212,7 +212,7 @@ def test_givenABPEmbAddressParser_whenRetrainWithConfigNewTags_thenTrainingOccur performance_after_training = address_parser.retrain( self.new_prediction_data_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -235,7 +235,7 @@ def test_givenABPEmbAddressParser_whenRetrainWithConfigWithCallbacksNewTags_then callback_mock = MagicMock(spec=Callback) performance_after_training = address_parser.retrain( self.new_prediction_data_container, - self.a_train_ratio, + train_ratio=self.a_train_ratio, epochs=self.a_single_epoch, batch_size=self.a_batch_size, num_workers=self.a_number_of_workers, @@ -265,7 +265,7 @@ def test_givenAFasttextAddressParser_whenTestWithNumWorkerAt0NewTags_thenTestOcc self.training( address_parser, self.new_prediction_data_container, - self.a_zero_number_of_workers, + num_workers=self.a_zero_number_of_workers, prediction_tags=self.with_new_prediction_tags, ) @@ -288,7 +288,7 @@ def test_givenAFasttextAddressParser_whenTestWithNumWorkerGreaterThen0NewTags_th self.training( address_parser, self.new_prediction_data_container, - self.a_number_of_workers, + num_workers=self.a_number_of_workers, prediction_tags=self.with_new_prediction_tags, ) @@ -312,7 +312,7 @@ def test_givenAFasttextAddressParser_whenTestMultipleEpochsNewTags_thenTestOccur self.training( address_parser, self.new_prediction_data_container, - self.a_number_of_workers, + num_workers=self.a_number_of_workers, prediction_tags=self.with_new_prediction_tags, ) @@ -334,7 +334,7 @@ def test_givenAFasttextAddressParser_whenTestWithConfigNewTags_thenTestOccur(sel self.training( address_parser, self.new_prediction_data_container, - self.a_number_of_workers, + num_workers=self.a_number_of_workers, prediction_tags=self.with_new_prediction_tags, ) @@ -358,7 +358,7 @@ def test_givenAFasttextAddressParser_whenTestWithConfigWithCallbacksNewTags_then self.training( address_parser, self.new_prediction_data_container, - self.a_number_of_workers, + num_workers=self.a_number_of_workers, prediction_tags=self.with_new_prediction_tags, ) @@ -398,7 +398,7 @@ def test_givenAFasttextAddressParser_whenTestWithFasttextCkptNewTags_thenTestOcc self.training( address_parser, self.new_prediction_data_container, - self.a_number_of_workers, + num_workers=self.a_number_of_workers, prediction_tags=self.with_new_prediction_tags, ) @@ -420,7 +420,7 @@ def test_givenAFasttextAddressParser_whenTestWithStrCkptNewTags_thenTestOccur(se self.training( address_parser, self.new_prediction_data_container, - self.a_number_of_workers, + num_workers=self.a_number_of_workers, prediction_tags=self.with_new_prediction_tags, ) @@ -443,7 +443,7 @@ def test_givenABPEmbAddressParser_whenTestWithNumberWorkersAt0NewTags_thenTestOc self.training( address_parser, self.new_prediction_data_container, - self.a_zero_number_of_workers, + num_workers=self.a_zero_number_of_workers, prediction_tags=self.with_new_prediction_tags, ) @@ -466,7 +466,7 @@ def test_givenABPEmbAddressParser_whenTestWithNumberWorkersGreaterThen0NewTags_t self.training( address_parser, self.new_prediction_data_container, - self.a_number_of_workers, + num_workers=self.a_number_of_workers, prediction_tags=self.with_new_prediction_tags, ) @@ -490,7 +490,7 @@ def test_givenABPEmbAddressParser_whenTestMultipleEpochsNewTags_thenTestOccurCor self.training( address_parser, self.new_prediction_data_container, - self.a_number_of_workers, + num_workers=self.a_number_of_workers, prediction_tags=self.with_new_prediction_tags, ) @@ -512,7 +512,7 @@ def test_givenABPEmbAddressParser_whenTestWithConfigNewTags_thenTestOccur(self): self.training( address_parser, self.new_prediction_data_container, - self.a_number_of_workers, + num_workers=self.a_number_of_workers, prediction_tags=self.with_new_prediction_tags, ) @@ -536,7 +536,7 @@ def test_givenABPEmbAddressParser_whenTestWithConfigWithCallbacksNewTags_thenCal self.training( address_parser, self.new_prediction_data_container, - self.a_number_of_workers, + num_workers=self.a_number_of_workers, prediction_tags=self.with_new_prediction_tags, ) @@ -574,7 +574,7 @@ def test_givenABPEmbAddressParser_whenTestWithBPEmbCkptNewTags_thenTestOccur(sel self.training( address_parser, self.new_prediction_data_container, - self.a_number_of_workers, + num_workers=self.a_number_of_workers, prediction_tags=self.with_new_prediction_tags, ) @@ -596,7 +596,7 @@ def test_givenABPEmbAddressParser_whenTestWithStrCkptNewTags_thenTestOccur(self) self.training( address_parser, self.new_prediction_data_container, - self.a_number_of_workers, + num_workers=self.a_number_of_workers, prediction_tags=self.with_new_prediction_tags, ) diff --git a/tests/parser/test_address_parser_retrain_api.py b/tests/parser/test_address_parser_retrain_api.py index 7f9c1f64..f75c71fc 100644 --- a/tests/parser/test_address_parser_retrain_api.py +++ b/tests/parser/test_address_parser_retrain_api.py @@ -67,7 +67,9 @@ def tearDown(self) -> None: def address_parser_retrain_call( self, - dataset_container=None, + train_dataset_container=None, + val_dataset_container=None, + train_ratio=None, # None to handle default test case prediction_tags=None, seq2seq_params=None, layers_to_freeze=None, @@ -83,13 +85,17 @@ def address_parser_retrain_call( else: num_workers = 1 - if dataset_container is None: - dataset_container = self.mocked_data_container + if train_dataset_container is None: + train_dataset_container = self.mocked_data_container # To handle by default for most of the tests. + if train_ratio is None: + train_ratio = self.a_train_ratio + self.address_parser.retrain( - dataset_container, - self.a_train_ratio, + train_dataset_container, + val_dataset_container, + train_ratio, self.a_batch_size, self.a_epoch_number, num_workers=num_workers, @@ -1207,9 +1213,9 @@ def test_givenNotTrainingDataContainer_thenRaiseValueError( with self.assertRaises(ValueError): self.address_parser.retrain( mocked_data_container, - self.a_train_ratio, - self.a_batch_size, - self.a_epoch_number, + train_ratio=self.a_train_ratio, + batch_size=self.a_batch_size, + epochs=self.a_epoch_number, num_workers=a_number_of_workers, learning_rate=self.a_learning_rate, callbacks=self.a_callbacks_list, @@ -1638,11 +1644,56 @@ def test_givenNotADatasetContainer_whenRetrainCall_thenRaiseValueError( not_a_dataset_container_obj = [] with self.assertRaises(ValueError): - self.address_parser_retrain_call(dataset_container=not_a_dataset_container_obj) + self.address_parser_retrain_call(train_dataset_container=not_a_dataset_container_obj) + + not_a_dataset_container_obj = {} + with self.assertRaises(ValueError): + self.address_parser_retrain_call(train_dataset_container=not_a_dataset_container_obj) + + # For val dataset + not_a_dataset_container_obj = [] + with self.assertRaises(ValueError): + self.address_parser_retrain_call(val_dataset_container=not_a_dataset_container_obj) not_a_dataset_container_obj = {} with self.assertRaises(ValueError): - self.address_parser_retrain_call(dataset_container=not_a_dataset_container_obj) + self.address_parser_retrain_call(val_dataset_container=not_a_dataset_container_obj) + + @patch("deepparse.parser.address_parser.os.path.join") + @patch("deepparse.parser.address_parser.torch.save") + @patch("deepparse.parser.address_parser.DataLoader") + @patch("deepparse.parser.address_parser.Experiment") + @patch("deepparse.parser.address_parser.SGD") + @patch("deepparse.parser.address_parser.DataTransform") + @patch("deepparse.parser.address_parser.FastTextSeq2SeqModel") + @patch("deepparse.parser.address_parser.fasttext_data_padding") + @patch("deepparse.parser.address_parser.FastTextVectorizer") + @patch("deepparse.parser.address_parser.FastTextEmbeddingsModel") + @patch("deepparse.parser.address_parser.download_fasttext_embeddings") + def test_givenNotADatasetContainer_whenRetrainCallWithValDataset_thenDontUseTrainRatio( + self, + download_weights_mock, + embeddings_model_mock, + vectorizer_model_mock, + data_padding_mock, + model_mock, + data_transform_mock, + optimizer_mock, + experiment_mock, + data_loader_mock, + torch_save_mock, + os_path_join_mock, + ): + self.address_parser = AddressParser( + model_type=self.a_fasttext_model_type, + device=self.a_device, + verbose=self.verbose, + ) + + train_ratio_mock = MagicMock() + self.address_parser_retrain_call(val_dataset_container=self.mocked_data_container, train_ratio=train_ratio_mock) + + train_ratio_mock.assert_not_called() if __name__ == "__main__": From 639f35a659b5a87acf746b9b5240dddc16e6c121 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 10 Aug 2022 14:56:40 -0400 Subject: [PATCH 064/195] added cleaning conda env --- codecov_push.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codecov_push.sh b/codecov_push.sh index 17195f6a..aa0d8e2a 100755 --- a/codecov_push.sh +++ b/codecov_push.sh @@ -20,3 +20,5 @@ conda run -n deepparse_pytest_3_10 --live-stream pytest --cov ./deepparse --cov- # close conda env conda deactivate + +conda env remove -n deepparse_pytest_3_10 From 7c7211ebb19ee474a62fa350a4f3e1e21e6a9cda Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 10 Aug 2022 14:57:46 -0400 Subject: [PATCH 065/195] improved scirpt with warmup training --- fine_tuning_po_boxes_script.py | 150 +++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 fine_tuning_po_boxes_script.py diff --git a/fine_tuning_po_boxes_script.py b/fine_tuning_po_boxes_script.py new file mode 100644 index 00000000..8b06e31d --- /dev/null +++ b/fine_tuning_po_boxes_script.py @@ -0,0 +1,150 @@ +import pickle + +import numpy as np +import poutyne +from poutyne import set_seeds, EarlyStopping +from sklearn.model_selection import train_test_split +from torch.utils.data import Subset + +from deepparse.dataset_container import ListDatasetContainer +from deepparse.parser import AddressParser + +seed = 42 +set_seeds(seed) + + +def get_train_test_split(dataset): + train_indices, test_indices = train_test_split(np.arange(len(dataset)), train_size=train_split_percent) + + train_dataset = Subset(dataset, train_indices) + test_dataset = Subset(dataset, test_indices) + + return train_dataset, test_dataset + + +def change_PoBox_to_POBox(data): + for key in data.keys(): + for d in data.get(key): + for idx, t in enumerate(d[1]): + if t == "PoBox": + d[1][idx] = "POBox" + + +with open("/fast/davidbeauchemin/libpostal_data/train/new_full_data.p", "rb") as file: + complete_and_incomplete_train_dataset = pickle.load(file) + +train_split_percent = 0.8 +with open("/fast/davidbeauchemin/libpostal_data/po_boxes/pobox_data_ca.p", "rb") as file: + ca_po_boxes_complete_and_incomplete = pickle.load(file) +change_PoBox_to_POBox(ca_po_boxes_complete_and_incomplete) + +ca_po_boxes_complete_train, ca_po_boxes_complete_test = get_train_test_split( + ca_po_boxes_complete_and_incomplete.get("complete") +) + +ca_po_boxes_incomplete_train, ca_po_boxes_incomplete_test = get_train_test_split( + ca_po_boxes_complete_and_incomplete.get("incomplete") +) + +with open("/fast/davidbeauchemin/libpostal_data/po_boxes/pobox_data_gb.p", "rb") as file: + gb_po_boxes_complete_and_incomplete = pickle.load(file) +change_PoBox_to_POBox(gb_po_boxes_complete_and_incomplete) + +gb_po_boxes_complete_train, gb_po_boxes_complete_test = get_train_test_split( + gb_po_boxes_complete_and_incomplete.get("complete") +) + +gb_po_boxes_incomplete_train, gb_po_boxes_incomplete_test = get_train_test_split( + gb_po_boxes_complete_and_incomplete.get("incomplete") +) + +with open("/fast/davidbeauchemin/libpostal_data/po_boxes/pobox_data_us.p", "rb") as file: + us_po_boxes_complete_and_incomplete = pickle.load(file) +change_PoBox_to_POBox(us_po_boxes_complete_and_incomplete) + +us_po_boxes_complete_train, us_po_boxes_complete_test = get_train_test_split( + us_po_boxes_complete_and_incomplete.get("complete") +) + +us_po_boxes_incomplete_train, us_po_boxes_incomplete_test = get_train_test_split( + us_po_boxes_complete_and_incomplete.get("incomplete") +) + +train_po_boxes_complete_all_country = ( + ca_po_boxes_complete_train + gb_po_boxes_complete_train + us_po_boxes_complete_train +) +train_po_boxes_incomplete_all_country = ( + ca_po_boxes_incomplete_train + gb_po_boxes_incomplete_train + us_po_boxes_incomplete_train +) + +address_parser = AddressParser(model_type='fasttext', attention_mechanism=False) + +new_prediction_tags = { + "StreetNumber": 0, + "StreetName": 1, + "Unit": 2, + "Municipality": 3, + "Province": 4, + "PostalCode": 5, + "Orientation": 6, + "GeneralDelivery": 7, + "POBox": 8, + "EOS": 9, # the 10th is the EOS with idx 9 +} + +po_boxes_dataset = list(train_po_boxes_complete_all_country) + list(train_po_boxes_incomplete_all_country) +address_parser.retrain( + ListDatasetContainer(po_boxes_dataset), + name_of_the_retrain_parser="fasttext_po_boxes_model", + logging_path="./po_boxes_retrain", + epochs=10, + batch_size=8, + learning_rate=0.0001, + prediction_tags=new_prediction_tags, +) + +ca_complete_res = address_parser.test(ListDatasetContainer(list(ca_po_boxes_complete_test))) +print("Warmup Training CA complete" + " ".join([f"{key}: {value}" for key, value in ca_complete_res.items()])) +ca_incomplete_res = address_parser.test(ListDatasetContainer(list(ca_po_boxes_incomplete_test))) +print("Warmup Training CA incomplete" + " ".join([f"{key}: {value}" for key, value in ca_incomplete_res.items()])) + +gb_complete_res = address_parser.test(ListDatasetContainer(list(gb_po_boxes_complete_test))) +print("Warmup Training GB complete" + " ".join([f"{key}: {value}" for key, value in gb_complete_res.items()])) +gb_incomplete_res = address_parser.test(ListDatasetContainer(list(gb_po_boxes_incomplete_test))) +print("Warmup Training GB incomplete" + " ".join([f"{key}: {value}" for key, value in gb_incomplete_res.items()])) + +us_complete_res = address_parser.test(ListDatasetContainer(list(us_po_boxes_complete_test))) +print("Warmup Training US complete" + " ".join([f"{key}: {value}" for key, value in us_complete_res.items()])) +us_incomplete_res = address_parser.test(ListDatasetContainer(list(us_po_boxes_incomplete_test))) +print("Warmup Training US incomplete" + " ".join([f"{key}: {value}" for key, value in us_incomplete_res.items()])) + +lr_scheduler = poutyne.StepLR(step_size=5, gamma=0.1) # reduce LR by a factor of 10 each epoch +patience = 11 +early_stopping = EarlyStopping(patience=patience) + +all_train_data = complete_and_incomplete_train_dataset + po_boxes_dataset + +address_parser.retrain( + ListDatasetContainer(all_train_data), + name_of_the_retrain_parser="fasttext_po_boxes_model", + logging_path="./po_boxes_retrain", + epochs=100, + batch_size=256, + learning_rate=0.0001, + callbacks=[lr_scheduler, early_stopping], +) + +ca_complete_res = address_parser.test(ListDatasetContainer(list(ca_po_boxes_complete_test))) +print("Training CA complete" + " ".join([f"{key}: {value}" for key, value in ca_complete_res.items()])) +ca_incomplete_res = address_parser.test(ListDatasetContainer(list(ca_po_boxes_incomplete_test))) +print("Training CA incomplete" + " ".join([f"{key}: {value}" for key, value in ca_incomplete_res.items()])) + +gb_complete_res = address_parser.test(ListDatasetContainer(list(gb_po_boxes_complete_test))) +print("Training GB complete" + " ".join([f"{key}: {value}" for key, value in gb_complete_res.items()])) +gb_incomplete_res = address_parser.test(ListDatasetContainer(list(gb_po_boxes_incomplete_test))) +print("Training GB incomplete" + " ".join([f"{key}: {value}" for key, value in gb_incomplete_res.items()])) + +us_complete_res = address_parser.test(ListDatasetContainer(list(us_po_boxes_complete_test))) +print("Training US complete" + " ".join([f"{key}: {value}" for key, value in us_complete_res.items()])) +us_incomplete_res = address_parser.test(ListDatasetContainer(list(us_po_boxes_incomplete_test))) +print("Training US incomplete" + " ".join([f"{key}: {value}" for key, value in us_incomplete_res.items()])) From 9e5c71e255b072e7bd2bddb3131aec92ec1d8b8b Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 10 Aug 2022 15:11:13 -0400 Subject: [PATCH 066/195] remove fine_tuning script since in branch --- fine_tuning_po_boxes_script.py | 150 --------------------------------- 1 file changed, 150 deletions(-) delete mode 100644 fine_tuning_po_boxes_script.py diff --git a/fine_tuning_po_boxes_script.py b/fine_tuning_po_boxes_script.py deleted file mode 100644 index 8b06e31d..00000000 --- a/fine_tuning_po_boxes_script.py +++ /dev/null @@ -1,150 +0,0 @@ -import pickle - -import numpy as np -import poutyne -from poutyne import set_seeds, EarlyStopping -from sklearn.model_selection import train_test_split -from torch.utils.data import Subset - -from deepparse.dataset_container import ListDatasetContainer -from deepparse.parser import AddressParser - -seed = 42 -set_seeds(seed) - - -def get_train_test_split(dataset): - train_indices, test_indices = train_test_split(np.arange(len(dataset)), train_size=train_split_percent) - - train_dataset = Subset(dataset, train_indices) - test_dataset = Subset(dataset, test_indices) - - return train_dataset, test_dataset - - -def change_PoBox_to_POBox(data): - for key in data.keys(): - for d in data.get(key): - for idx, t in enumerate(d[1]): - if t == "PoBox": - d[1][idx] = "POBox" - - -with open("/fast/davidbeauchemin/libpostal_data/train/new_full_data.p", "rb") as file: - complete_and_incomplete_train_dataset = pickle.load(file) - -train_split_percent = 0.8 -with open("/fast/davidbeauchemin/libpostal_data/po_boxes/pobox_data_ca.p", "rb") as file: - ca_po_boxes_complete_and_incomplete = pickle.load(file) -change_PoBox_to_POBox(ca_po_boxes_complete_and_incomplete) - -ca_po_boxes_complete_train, ca_po_boxes_complete_test = get_train_test_split( - ca_po_boxes_complete_and_incomplete.get("complete") -) - -ca_po_boxes_incomplete_train, ca_po_boxes_incomplete_test = get_train_test_split( - ca_po_boxes_complete_and_incomplete.get("incomplete") -) - -with open("/fast/davidbeauchemin/libpostal_data/po_boxes/pobox_data_gb.p", "rb") as file: - gb_po_boxes_complete_and_incomplete = pickle.load(file) -change_PoBox_to_POBox(gb_po_boxes_complete_and_incomplete) - -gb_po_boxes_complete_train, gb_po_boxes_complete_test = get_train_test_split( - gb_po_boxes_complete_and_incomplete.get("complete") -) - -gb_po_boxes_incomplete_train, gb_po_boxes_incomplete_test = get_train_test_split( - gb_po_boxes_complete_and_incomplete.get("incomplete") -) - -with open("/fast/davidbeauchemin/libpostal_data/po_boxes/pobox_data_us.p", "rb") as file: - us_po_boxes_complete_and_incomplete = pickle.load(file) -change_PoBox_to_POBox(us_po_boxes_complete_and_incomplete) - -us_po_boxes_complete_train, us_po_boxes_complete_test = get_train_test_split( - us_po_boxes_complete_and_incomplete.get("complete") -) - -us_po_boxes_incomplete_train, us_po_boxes_incomplete_test = get_train_test_split( - us_po_boxes_complete_and_incomplete.get("incomplete") -) - -train_po_boxes_complete_all_country = ( - ca_po_boxes_complete_train + gb_po_boxes_complete_train + us_po_boxes_complete_train -) -train_po_boxes_incomplete_all_country = ( - ca_po_boxes_incomplete_train + gb_po_boxes_incomplete_train + us_po_boxes_incomplete_train -) - -address_parser = AddressParser(model_type='fasttext', attention_mechanism=False) - -new_prediction_tags = { - "StreetNumber": 0, - "StreetName": 1, - "Unit": 2, - "Municipality": 3, - "Province": 4, - "PostalCode": 5, - "Orientation": 6, - "GeneralDelivery": 7, - "POBox": 8, - "EOS": 9, # the 10th is the EOS with idx 9 -} - -po_boxes_dataset = list(train_po_boxes_complete_all_country) + list(train_po_boxes_incomplete_all_country) -address_parser.retrain( - ListDatasetContainer(po_boxes_dataset), - name_of_the_retrain_parser="fasttext_po_boxes_model", - logging_path="./po_boxes_retrain", - epochs=10, - batch_size=8, - learning_rate=0.0001, - prediction_tags=new_prediction_tags, -) - -ca_complete_res = address_parser.test(ListDatasetContainer(list(ca_po_boxes_complete_test))) -print("Warmup Training CA complete" + " ".join([f"{key}: {value}" for key, value in ca_complete_res.items()])) -ca_incomplete_res = address_parser.test(ListDatasetContainer(list(ca_po_boxes_incomplete_test))) -print("Warmup Training CA incomplete" + " ".join([f"{key}: {value}" for key, value in ca_incomplete_res.items()])) - -gb_complete_res = address_parser.test(ListDatasetContainer(list(gb_po_boxes_complete_test))) -print("Warmup Training GB complete" + " ".join([f"{key}: {value}" for key, value in gb_complete_res.items()])) -gb_incomplete_res = address_parser.test(ListDatasetContainer(list(gb_po_boxes_incomplete_test))) -print("Warmup Training GB incomplete" + " ".join([f"{key}: {value}" for key, value in gb_incomplete_res.items()])) - -us_complete_res = address_parser.test(ListDatasetContainer(list(us_po_boxes_complete_test))) -print("Warmup Training US complete" + " ".join([f"{key}: {value}" for key, value in us_complete_res.items()])) -us_incomplete_res = address_parser.test(ListDatasetContainer(list(us_po_boxes_incomplete_test))) -print("Warmup Training US incomplete" + " ".join([f"{key}: {value}" for key, value in us_incomplete_res.items()])) - -lr_scheduler = poutyne.StepLR(step_size=5, gamma=0.1) # reduce LR by a factor of 10 each epoch -patience = 11 -early_stopping = EarlyStopping(patience=patience) - -all_train_data = complete_and_incomplete_train_dataset + po_boxes_dataset - -address_parser.retrain( - ListDatasetContainer(all_train_data), - name_of_the_retrain_parser="fasttext_po_boxes_model", - logging_path="./po_boxes_retrain", - epochs=100, - batch_size=256, - learning_rate=0.0001, - callbacks=[lr_scheduler, early_stopping], -) - -ca_complete_res = address_parser.test(ListDatasetContainer(list(ca_po_boxes_complete_test))) -print("Training CA complete" + " ".join([f"{key}: {value}" for key, value in ca_complete_res.items()])) -ca_incomplete_res = address_parser.test(ListDatasetContainer(list(ca_po_boxes_incomplete_test))) -print("Training CA incomplete" + " ".join([f"{key}: {value}" for key, value in ca_incomplete_res.items()])) - -gb_complete_res = address_parser.test(ListDatasetContainer(list(gb_po_boxes_complete_test))) -print("Training GB complete" + " ".join([f"{key}: {value}" for key, value in gb_complete_res.items()])) -gb_incomplete_res = address_parser.test(ListDatasetContainer(list(gb_po_boxes_incomplete_test))) -print("Training GB incomplete" + " ".join([f"{key}: {value}" for key, value in gb_incomplete_res.items()])) - -us_complete_res = address_parser.test(ListDatasetContainer(list(us_po_boxes_complete_test))) -print("Training US complete" + " ".join([f"{key}: {value}" for key, value in us_complete_res.items()])) -us_incomplete_res = address_parser.test(ListDatasetContainer(list(us_po_boxes_incomplete_test))) -print("Training US incomplete" + " ".join([f"{key}: {value}" for key, value in us_incomplete_res.items()])) From 6021e4956d49745ecfe8d8fdd3efb81eade2172e Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 10 Aug 2022 15:21:58 -0400 Subject: [PATCH 067/195] fixed tests --- tests/cli/test_retrain.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/cli/test_retrain.py b/tests/cli/test_retrain.py index 9d7f6a1a..a714fd20 100644 --- a/tests/cli/test_retrain.py +++ b/tests/cli/test_retrain.py @@ -82,8 +82,6 @@ def set_up_params( parser_params = [ model_type, train_dataset_path, - "--val_dataset_path", - val_dataset_path, "--train_ratio", train_ratio, "--batch_size", @@ -108,6 +106,10 @@ def set_up_params( csv_column_separator, ] + if val_dataset_path is not None: + # To handle the None case (that is using the default None of the argparser). + parser_params.extend(["--val_dataset_path", val_dataset_path]) + if cache_dir is not None: # To handle the None case (that is using the default None of the argparser). parser_params.extend(["--cache_dir", cache_dir]) @@ -117,6 +119,7 @@ def set_up_params( parser_params.extend(["--name_of_the_retrain_parser", name_of_the_retrain_parser]) if csv_column_names is not None: + # To handle the None case (that is using the default None of the argparser). parser_params.extend(["--csv_column_names"]) parser_params.extend(csv_column_names) # Since csv_column_names is a list From dc3bfc378eb1a4617b5b9caf4e1bbc53f9527e88 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 10 Aug 2022 16:25:09 -0400 Subject: [PATCH 068/195] fixed test without clear num_workers arg --- ...est_integration_address_parser_test_cpu.py | 32 +++++++++---------- ...est_integration_address_parser_test_gpu.py | 32 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/tests/parser/integration/test_integration_address_parser_test_cpu.py b/tests/parser/integration/test_integration_address_parser_test_cpu.py index afa0f929..89f9ff54 100644 --- a/tests/parser/integration/test_integration_address_parser_test_cpu.py +++ b/tests/parser/integration/test_integration_address_parser_test_cpu.py @@ -22,12 +22,12 @@ def test_givenAFasttextAddressParser_whenTestWithNumWorkerAt0_thenTestOccur(self device=self.a_cpu_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_zero_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_zero_number_of_workers) performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, - num_workers=self.a_number_of_workers, + num_workers=self.a_zero_number_of_workers, ) self.assertIsNotNone(performance_after_test) @@ -40,7 +40,7 @@ def test_givenAFasttextAddressParser_whenTestWithNumWorkerGreaterThen0_thenTestO device=self.a_cpu_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -59,7 +59,7 @@ def test_givenAFasttextAddressParser_whenTestMultipleEpochs_thenTestOccurCorrect verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -76,7 +76,7 @@ def test_givenAFasttextAddressParser_whenTestWithConfig_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -95,7 +95,7 @@ def test_givenAFasttextAddressParser_whenTestWithConfigWithCallbacks_thenCallbac verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) callback_mock = MagicMock() performance_after_test = address_parser.test( @@ -128,7 +128,7 @@ def test_givenAFasttextAddressParser_whenTestWithFasttextCkpt_thenTestOccur(self verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -145,7 +145,7 @@ def test_givenAFasttextAddressParser_whenTestWithStrCkpt_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -161,12 +161,12 @@ def test_givenABPEmbAddressParser_whenTestWithNumberWorkersAt0_thenTestOccur(sel device=self.a_cpu_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_zero_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_zero_number_of_workers) performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, - num_workers=self.a_number_of_workers, + num_workers=self.a_zero_number_of_workers, ) self.assertIsNotNone(performance_after_test) @@ -179,7 +179,7 @@ def test_givenABPEmbAddressParser_whenTestWithNumberWorkersGreaterThen0_thenTest device=self.a_cpu_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -198,7 +198,7 @@ def test_givenABPEmbAddressParser_whenTestMultipleEpochs_thenTestOccurCorrectly( verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -215,7 +215,7 @@ def test_givenABPEmbAddressParser_whenTestWithConfig_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -234,7 +234,7 @@ def test_givenABPEmbAddressParser_whenTestWithConfigWithCallbacks_thenCallbackAr verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) callback_mock = MagicMock() performance_after_test = address_parser.test( @@ -267,7 +267,7 @@ def test_givenABPEmbAddressParser_whenTestWithBPEmbCkpt_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -284,7 +284,7 @@ def test_givenABPEmbAddressParser_whenTestWithStrCkpt_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, diff --git a/tests/parser/integration/test_integration_address_parser_test_gpu.py b/tests/parser/integration/test_integration_address_parser_test_gpu.py index 7dd6b6c7..58ac1a0a 100644 --- a/tests/parser/integration/test_integration_address_parser_test_gpu.py +++ b/tests/parser/integration/test_integration_address_parser_test_gpu.py @@ -19,12 +19,12 @@ def test_givenAFasttextAddressParser_whenTestWithNumWorkerAt0_thenTestOccur(self device=self.a_torch_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_zero_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_zero_number_of_workers) performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, - num_workers=self.a_number_of_workers, + num_workers=self.a_zero_number_of_workers, ) self.assertIsNotNone(performance_after_test) @@ -37,7 +37,7 @@ def test_givenAFasttextAddressParser_whenTestWithNumWorkerGreaterThen0_thenTestO device=self.a_torch_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -56,7 +56,7 @@ def test_givenAFasttextAddressParser_whenTestMultipleEpochs_thenTestOccurCorrect verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -73,7 +73,7 @@ def test_givenAFasttextAddressParser_whenTestWithConfig_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -92,7 +92,7 @@ def test_givenAFasttextAddressParser_whenTestWithConfigWithCallbacks_thenCallbac verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) callback_mock = MagicMock() performance_after_test = address_parser.test( @@ -125,7 +125,7 @@ def test_givenAFasttextAddressParser_whenTestWithFasttextCkpt_thenTestOccur(self verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -142,7 +142,7 @@ def test_givenAFasttextAddressParser_whenTestWithStrCkpt_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -158,12 +158,12 @@ def test_givenABPEmbAddressParser_whenTestWithNumWorkerAt0_thenTestOccur(self): device=self.a_torch_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_zero_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_zero_number_of_workers) performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, - num_workers=self.a_number_of_workers, + num_workers=self.a_zero_number_of_workers, ) self.assertIsNotNone(performance_after_test) @@ -176,7 +176,7 @@ def test_givenABPEmbAddressParser_whenTestWithNumWorkerGreaterThen0_thenTestOccu device=self.a_torch_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -195,7 +195,7 @@ def test_givenABPEmbAddressParser_whenTestMultipleEpochs_thenTestOccurCorrectly( verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -212,7 +212,7 @@ def test_givenABPEmbAddressParser_whenTestWithConfig_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -231,7 +231,7 @@ def test_givenABPEmbAddressParser_whenTestWithConfigWithCallbacks_thenCallbackAr verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) callback_mock = MagicMock() performance_after_test = address_parser.test( @@ -264,7 +264,7 @@ def test_givenABPEmbAddressParser_whenTestWithBPEmbCkpt_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -281,7 +281,7 @@ def test_givenABPEmbAddressParser_whenTestWithStrCkpt_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, self.a_number_of_workers) + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, From a936803240bde3b272a3520819e522f98fb118fd Mon Sep 17 00:00:00 2001 From: davebulaval Date: Wed, 10 Aug 2022 21:21:09 -0400 Subject: [PATCH 069/195] remove fn download_from_url --- deepparse/tools.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/deepparse/tools.py b/deepparse/tools.py index baa40bb0..12a696fd 100644 --- a/deepparse/tools.py +++ b/deepparse/tools.py @@ -82,15 +82,6 @@ def latest_version(model: str, cache_path: str, verbose: bool) -> bool: return is_latest_version -def download_from_url(file_name: str, saving_dir: str, file_extension: str) -> None: # pragma: no cover - warnings.warn( - "download_from_url is deprecated; use download_from_public_repository to download files from " - "our public repository. The function will be removed in the next major release.", - DeprecationWarning, - ) - download_from_public_repository(file_name=file_name, saving_dir=saving_dir, file_extension=file_extension) - - def download_from_public_repository(file_name: str, saving_dir: str, file_extension: str) -> None: """ Simple function to download the content of a file from Deepparse public repository. From 76c2c229b2a23cd774934a4e82a46569c433f761 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Thu, 11 Aug 2022 11:33:58 -0400 Subject: [PATCH 070/195] removed unecessary retrain in test api tests --- ...est_integration_address_parser_test_cpu.py | 24 ------------------ ...est_integration_address_parser_test_gpu.py | 25 ------------------- 2 files changed, 49 deletions(-) diff --git a/tests/parser/integration/test_integration_address_parser_test_cpu.py b/tests/parser/integration/test_integration_address_parser_test_cpu.py index 89f9ff54..06934db2 100644 --- a/tests/parser/integration/test_integration_address_parser_test_cpu.py +++ b/tests/parser/integration/test_integration_address_parser_test_cpu.py @@ -22,7 +22,6 @@ def test_givenAFasttextAddressParser_whenTestWithNumWorkerAt0_thenTestOccur(self device=self.a_cpu_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_zero_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -40,7 +39,6 @@ def test_givenAFasttextAddressParser_whenTestWithNumWorkerGreaterThen0_thenTestO device=self.a_cpu_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -59,8 +57,6 @@ def test_givenAFasttextAddressParser_whenTestMultipleEpochs_thenTestOccurCorrect verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -76,8 +72,6 @@ def test_givenAFasttextAddressParser_whenTestWithConfig_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -95,8 +89,6 @@ def test_givenAFasttextAddressParser_whenTestWithConfigWithCallbacks_thenCallbac verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - callback_mock = MagicMock() performance_after_test = address_parser.test( self.test_container, @@ -128,8 +120,6 @@ def test_givenAFasttextAddressParser_whenTestWithFasttextCkpt_thenTestOccur(self verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -145,8 +135,6 @@ def test_givenAFasttextAddressParser_whenTestWithStrCkpt_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -161,7 +149,6 @@ def test_givenABPEmbAddressParser_whenTestWithNumberWorkersAt0_thenTestOccur(sel device=self.a_cpu_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_zero_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -179,7 +166,6 @@ def test_givenABPEmbAddressParser_whenTestWithNumberWorkersGreaterThen0_thenTest device=self.a_cpu_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -198,8 +184,6 @@ def test_givenABPEmbAddressParser_whenTestMultipleEpochs_thenTestOccurCorrectly( verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -215,8 +199,6 @@ def test_givenABPEmbAddressParser_whenTestWithConfig_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -234,8 +216,6 @@ def test_givenABPEmbAddressParser_whenTestWithConfigWithCallbacks_thenCallbackAr verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - callback_mock = MagicMock() performance_after_test = address_parser.test( self.test_container, @@ -267,8 +247,6 @@ def test_givenABPEmbAddressParser_whenTestWithBPEmbCkpt_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -284,8 +262,6 @@ def test_givenABPEmbAddressParser_whenTestWithStrCkpt_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, diff --git a/tests/parser/integration/test_integration_address_parser_test_gpu.py b/tests/parser/integration/test_integration_address_parser_test_gpu.py index 58ac1a0a..895d92da 100644 --- a/tests/parser/integration/test_integration_address_parser_test_gpu.py +++ b/tests/parser/integration/test_integration_address_parser_test_gpu.py @@ -19,7 +19,6 @@ def test_givenAFasttextAddressParser_whenTestWithNumWorkerAt0_thenTestOccur(self device=self.a_torch_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_zero_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -37,7 +36,6 @@ def test_givenAFasttextAddressParser_whenTestWithNumWorkerGreaterThen0_thenTestO device=self.a_torch_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -56,8 +54,6 @@ def test_givenAFasttextAddressParser_whenTestMultipleEpochs_thenTestOccurCorrect verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -73,8 +69,6 @@ def test_givenAFasttextAddressParser_whenTestWithConfig_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -92,8 +86,6 @@ def test_givenAFasttextAddressParser_whenTestWithConfigWithCallbacks_thenCallbac verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - callback_mock = MagicMock() performance_after_test = address_parser.test( self.test_container, @@ -125,8 +117,6 @@ def test_givenAFasttextAddressParser_whenTestWithFasttextCkpt_thenTestOccur(self verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -142,8 +132,6 @@ def test_givenAFasttextAddressParser_whenTestWithStrCkpt_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -158,7 +146,6 @@ def test_givenABPEmbAddressParser_whenTestWithNumWorkerAt0_thenTestOccur(self): device=self.a_torch_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_zero_number_of_workers) performance_after_test = address_parser.test( self.test_container, @@ -176,8 +163,6 @@ def test_givenABPEmbAddressParser_whenTestWithNumWorkerGreaterThen0_thenTestOccu device=self.a_torch_device, verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -195,8 +180,6 @@ def test_givenABPEmbAddressParser_whenTestMultipleEpochs_thenTestOccurCorrectly( verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -212,8 +195,6 @@ def test_givenABPEmbAddressParser_whenTestWithConfig_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -231,8 +212,6 @@ def test_givenABPEmbAddressParser_whenTestWithConfigWithCallbacks_thenCallbackAr verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - callback_mock = MagicMock() performance_after_test = address_parser.test( self.test_container, @@ -264,8 +243,6 @@ def test_givenABPEmbAddressParser_whenTestWithBPEmbCkpt_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, @@ -281,8 +258,6 @@ def test_givenABPEmbAddressParser_whenTestWithStrCkpt_thenTestOccur(self): verbose=self.verbose, ) - self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) - performance_after_test = address_parser.test( self.test_container, batch_size=self.a_batch_size, From d4e4928ee8c5eee9e7f8116e37d50695a7c97426 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Thu, 11 Aug 2022 11:51:28 -0400 Subject: [PATCH 071/195] added verbose for test and improved tests for retrain test integration --- deepparse/parser/address_parser.py | 20 +++-- ..._integration_address_parser_retrain_api.py | 6 +- ...est_integration_address_parser_test_api.py | 74 +++++++++++++++++++ ...ntegration_retrain_test_api_integration.py | 28 +++++++ .../parser/test_address_parser_retrain_api.py | 3 + 5 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 tests/parser/integration/test_integration_address_parser_test_api.py create mode 100644 tests/parser/integration/test_integration_retrain_test_api_integration.py diff --git a/deepparse/parser/address_parser.py b/deepparse/parser/address_parser.py index e21785a1..b6bcebe9 100644 --- a/deepparse/parser/address_parser.py +++ b/deepparse/parser/address_parser.py @@ -553,7 +553,8 @@ def retrain( # We provide the train dataset (container) and the val dataset (val_container) # Thus, the train_ratio argument is ignored, and we use instead the val_container # as the validating dataset. - address_parser.retrain(container, val_container, epochs=5, batch_size=128, layers_to_freeze="encoder") + address_parser.retrain(container, val_container, epochs=5, batch_size=128, + layers_to_freeze="encoder") Using learning rate scheduler callback. @@ -758,6 +759,7 @@ def test( num_workers: int = 1, callbacks: Union[List, None] = None, seed: int = 42, + verbose: Union[None, bool] = None, ) -> Dict: # pylint: disable=too-many-arguments, too-many-locals """ @@ -773,6 +775,9 @@ def test( See Poutyne `callback `_ for more information. By default, we set no callback. seed (int): Seed to use (by default, ``42``). + verbose (Union[None, bool]): To override the AddressParser verbosity for the test. When set to True or + False, it will override (but it does not change the AddressParser verbosity) the test verbosity. + If set to the default value None, the AddressParser verbosity is used as the test verbosity. Return: A dictionary with the stats (see `Experiment class @@ -785,12 +790,13 @@ def test( .. code-block:: python - address_parser = AddressParser(device=0) # On GPU device 0 + address_parser = AddressParser(device=0, verbose=True) # On GPU device 0 data_path = "path_to_a_pickle_test_dataset.p" test_container = PickleDatasetContainer(data_path, is_training_container=False) - address_parser.test(test_container) # We test the model on the data + # We test the model on the data, and we override the test verbosity + address_parser.test(test_container, verbose=False) You can also test your fine-tuned model @@ -849,7 +855,10 @@ def test( logging=False, ) # We set logging to false since we don't need it - test_res = exp.test(test_generator, seed=seed, callbacks=callbacks, verbose=self.verbose) + # Handle the verbose overriding param + if verbose is None: + verbose = self.verbose + test_res = exp.test(test_generator, seed=seed, callbacks=callbacks, verbose=verbose) return test_res @@ -1062,8 +1071,8 @@ def _predict_pipeline(self, data: List) -> Tuple: """ return self.data_converter(self.vectorizer(data)) + @staticmethod def _retrain( - self, experiment: Experiment, train_generator: DatasetContainer, valid_generator: DatasetContainer, @@ -1083,7 +1092,6 @@ def _retrain( epochs=epochs, seed=seed, callbacks=callbacks, - verbose=self.verbose, disable_tensorboard=disable_tensorboard, ) return train_res diff --git a/tests/parser/integration/test_integration_address_parser_retrain_api.py b/tests/parser/integration/test_integration_address_parser_retrain_api.py index 9f8899cc..3b1fc355 100644 --- a/tests/parser/integration/test_integration_address_parser_retrain_api.py +++ b/tests/parser/integration/test_integration_address_parser_retrain_api.py @@ -1,7 +1,3 @@ -# Bug with PyTorch source code makes torch.tensor as not callable for pylint. -# We also skip protected-access since we test the encoder and decoder step -# pylint: disable=not-callable, too-many-public-methods - import os import unittest from unittest import skipIf @@ -15,7 +11,7 @@ not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), "download of model too long for test in runner", ) -class AddressParserIntegrationRetrainCPUTest(AddressParserRetrainTestCase, CaptureOutputTestCase): +class AddressParserIntegrationRetrainAPITest(AddressParserRetrainTestCase, CaptureOutputTestCase): def test_givenAFasttextAddressParser_whenRetrainNoValDataset_thenTrainingOccur(self): address_parser = AddressParser( model_type=self.a_fasttext_model_type, diff --git a/tests/parser/integration/test_integration_address_parser_test_api.py b/tests/parser/integration/test_integration_address_parser_test_api.py new file mode 100644 index 00000000..c944c9ce --- /dev/null +++ b/tests/parser/integration/test_integration_address_parser_test_api.py @@ -0,0 +1,74 @@ +import os +import unittest +from typing import List +from unittest import skipIf + +from deepparse.parser import AddressParser +from tests.base_capture_output import CaptureOutputTestCase +from tests.parser.integration.base_retrain import AddressParserRetrainTestCase + + +@skipIf( + not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), + "download of model too long for test in runner", +) +class AddressParserIntegrationTestAPITest(AddressParserRetrainTestCase, CaptureOutputTestCase): + def test_givenAnVerboseTrueAddressParser_whenTestOverrideTrue_thenPrint(self): + self._capture_output() + + address_parser = AddressParser( + model_type=self.a_fasttext_model_type, + device=self.a_cpu_device, + verbose=True, + ) + + performance_after_test = address_parser.test( + self.test_container, batch_size=self.a_batch_size, verbose=True # Print + ) + + actual = self.test_out.getvalue().strip() + + expected_contains = [ + "test_loss:", + str(round(performance_after_test.get("test_loss"), 6)), + "test_accuracy", + str(round(performance_after_test.get("test_accuracy"), 6)), + ] + + self.assertContains(actual, expected_contains) + + def test_givenAnVerboseTrueAddressParser_whenTestOverrideFalse_thenDontPrint(self): + self._capture_output() + + address_parser = AddressParser( + model_type=self.a_fasttext_model_type, + device=self.a_cpu_device, + verbose=True, + ) + + performance_after_test = address_parser.test( + self.test_container, batch_size=self.a_batch_size, verbose=False # No print + ) + + actual = self.test_out.getvalue().strip() + + expected_contains = [ + "test_loss:", + str(round(performance_after_test.get("test_loss"), 6)), + "test_accuracy", + str(round(performance_after_test.get("test_accuracy"), 6)), + ] + + self.assertNotContains(actual, expected_contains) + + def assertContains(self, actual: str, expected_contains: List): + for expected_contain in expected_contains: + self.assertIn(expected_contain, actual) + + def assertNotContains(self, actual: str, not_expected_contains: List): + for not_expected_contain in not_expected_contains: + self.assertNotIn(not_expected_contain, actual) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/parser/integration/test_integration_retrain_test_api_integration.py b/tests/parser/integration/test_integration_retrain_test_api_integration.py new file mode 100644 index 00000000..c3b7f1cc --- /dev/null +++ b/tests/parser/integration/test_integration_retrain_test_api_integration.py @@ -0,0 +1,28 @@ +import os +from unittest import skipIf + +from deepparse.parser import AddressParser +from tests.parser.integration.base_retrain import AddressParserRetrainTestCase + + +@skipIf( + not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), + "download of model too long for test in runner", +) +class AddressParserIntegrationTestAPITest(AddressParserRetrainTestCase): + def test_givenARetrainAnTestLoop_whenRunBoth_thenWork(self): + address_parser = AddressParser( + model_type=self.a_bpemb_model_type, + device=self.a_cpu_device, + verbose=self.verbose, + ) + + self.training(address_parser, self.training_container, num_workers=self.a_number_of_workers) + + performance_after_test = address_parser.test( + self.test_container, + batch_size=self.a_batch_size, + num_workers=self.a_number_of_workers, + ) + + self.assertIsNotNone(performance_after_test) diff --git a/tests/parser/test_address_parser_retrain_api.py b/tests/parser/test_address_parser_retrain_api.py index f75c71fc..24a42fbb 100644 --- a/tests/parser/test_address_parser_retrain_api.py +++ b/tests/parser/test_address_parser_retrain_api.py @@ -12,6 +12,7 @@ import platform import unittest from tempfile import TemporaryDirectory +from typing import Union from unittest import skipIf from unittest.mock import patch, call, MagicMock @@ -75,6 +76,7 @@ def address_parser_retrain_call( layers_to_freeze=None, name_of_the_retrain_parser=None, num_workers=None, + verbose: Union[bool, None] = True, ): if num_workers is None: # AddressParser default num_workers settings is 1 @@ -107,6 +109,7 @@ def address_parser_retrain_call( seq2seq_params=seq2seq_params, layers_to_freeze=layers_to_freeze, name_of_the_retrain_parser=name_of_the_retrain_parser, + verbose=verbose, ) def assert_experiment_retrain(self, experiment_mock, model_mock, optimizer_mock, device): From b36765aeb0312f183ddbaa352b38c5478bf9ea33 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Thu, 11 Aug 2022 11:52:27 -0400 Subject: [PATCH 072/195] updated changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 094acee2..83d594c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -254,4 +254,5 @@ ## dev - Add `save_model_weights` method to `AddressParser` to save model weights (PyTorch state dictionary) -- Improve CI \ No newline at end of file +- Improve CI +- Added verbose flag for test to activate or deactivate the test verbosity (it override the AddressParser verbosity) \ No newline at end of file From caa616b83b98001d077c73dd677223162713ae97 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Thu, 11 Aug 2022 23:39:18 -0400 Subject: [PATCH 073/195] fixed missing hint typing, improved internal doc, fixed train_ratio arg error in code examples and in doc --- README.md | 12 +++++----- deepparse/comparer/addresses_comparer.py | 2 +- deepparse/converter/data_padding.py | 6 ++--- deepparse/converter/data_transform.py | 2 +- deepparse/converter/target_converter.py | 2 +- deepparse/data_error/data_error.py | 4 ++-- .../dataset_container/dataset_container.py | 23 +++++++++++++++---- .../embeddings_models/embeddings_model.py | 2 +- .../fasttext_embeddings_model.py | 2 +- deepparse/metrics/accuracy.py | 3 ++- deepparse/metrics/nll_loss.py | 3 ++- deepparse/network/decoder.py | 8 +++---- deepparse/parser/address_parser.py | 19 ++++++++------- deepparse/preprocessing/address_cleaner.py | 2 +- deepparse/tools.py | 2 +- deepparse/vectorizer/bpemb_vectorizer.py | 2 +- deepparse/vectorizer/train_vectorizer.py | 15 +++++++----- docs/source/examples/fine_tuning.rst | 4 ++-- .../examples/fine_tuning_with_csv_dataset.rst | 4 ++-- .../examples/retrain_attention_model.rst | 2 +- .../retrain_with_new_prediction_tags.rst | 2 +- .../retrain_with_new_seq2seq_params.rst | 2 +- docs/source/index.rst | 12 +++++----- examples/fine_tuning.py | 4 ++-- examples/fine_tuning_with_csv_dataset.py | 4 ++-- examples/retrain_attention_model.py | 2 +- examples/retrain_with_new_prediction_tags.py | 4 ++-- examples/retrain_with_new_seq2seq_params.py | 2 +- tests/parser/test_address_parser.py | 16 +++++++++++-- 29 files changed, 101 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index ef61a0ff..9aab4fc1 100644 --- a/README.md +++ b/README.md @@ -212,21 +212,21 @@ for a complete example using CSV. # We will retrain the fasttext version of our pretrained model. address_parser = AddressParser(model_type="fasttext", device=0) -address_parser.retrain(training_container, 0.8, epochs=5, batch_size=8) +address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size=8) ``` One can also freeze some layers to speed up the training using the ``layers_to_freeze`` parameter. ```python -address_parser.retrain(training_container, 0.8, epochs=5, batch_size=8, layers_to_freeze="seq2seq") +address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size=8, layers_to_freeze="seq2seq") ``` Or you can also give a specific name to the retrained model. This name will be use as the model name (for print and class name) when reloading it. ```python -address_parser.retrain(training_container, 0.8, epochs=5, batch_size=8, name_of_the_retrain_parser="MyNewParser") +address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size=8, name_of_the_retrain_parser="MyNewParser") ``` ### Retrain a Model With an Attention Mechanism @@ -237,7 +237,7 @@ address_parser.retrain(training_container, 0.8, epochs=5, batch_size=8, name_of_ # We will retrain the fasttext version of our pretrained model. address_parser = AddressParser(model_type="fasttext", device=0, attention_mechanism=True) -address_parser.retrain(training_container, 0.8, epochs=5, batch_size=8) +address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size=8) ``` @@ -248,7 +248,7 @@ address_parser.retrain(training_container, 0.8, epochs=5, batch_size=8) ```python address_components = {"ATag": 0, "AnotherTag": 1, "EOS": 2} -address_parser.retrain(training_container, 0.8, epochs=1, batch_size=128, +address_parser.retrain(training_container, train_ratio=0.8, epochs=1, batch_size=128, prediction_tags=address_components) ``` @@ -261,7 +261,7 @@ address_parser.retrain(training_container, 0.8, epochs=1, batch_size=128, ```python seq2seq_params = {"encoder_hidden_size": 512, "decoder_hidden_size": 512} -address_parser.retrain(training_container, 0.8, epochs=1, batch_size=128, +address_parser.retrain(training_container, train_ratio=0.8, epochs=1, batch_size=128, seq2seq_params=seq2seq_params) ``` diff --git a/deepparse/comparer/addresses_comparer.py b/deepparse/comparer/addresses_comparer.py index 73ad6714..70b7f704 100644 --- a/deepparse/comparer/addresses_comparer.py +++ b/deepparse/comparer/addresses_comparer.py @@ -203,5 +203,5 @@ def _format_comparisons_dict(comparison_tuples: List, origin_tuple: Tuple[str, s return list_of_formatted_comparisons_dict @staticmethod - def _check_if_with_prob(list_of_tuple): + def _check_if_with_prob(list_of_tuple: List[Tuple]) -> bool: return len(list_of_tuple[0][1]) == 2 and isinstance(list_of_tuple[0][1][1], float) diff --git a/deepparse/converter/data_padding.py b/deepparse/converter/data_padding.py index c8960c29..baaa01e3 100644 --- a/deepparse/converter/data_padding.py +++ b/deepparse/converter/data_padding.py @@ -5,7 +5,7 @@ # if not the case. # pylint: disable=no-member -from typing import List, Tuple +from typing import List, Tuple, Iterable import numpy as np import torch @@ -203,7 +203,7 @@ def bpemb_data_padding_with_target(batch: List[Tuple]) -> Tuple: return (padded_sequences_vectors, list(decomp_len), lengths), padded_target_vectors -def _convert_sequence_to_tensor(batch): +def _convert_sequence_to_tensor(batch: List) -> Iterable: """ Sort and convert sequence into a tensor with target element """ @@ -222,7 +222,7 @@ def _convert_sequence_to_tensor(batch): ) -def _convert_bpemb_sequence_to_tensor(batch): +def _convert_bpemb_sequence_to_tensor(batch: List[Tuple]) -> Iterable: """ Sort and convert a BPEmb sequence into a tensor with target element """ diff --git a/deepparse/converter/data_transform.py b/deepparse/converter/data_transform.py index 001e9918..2dd1aaea 100644 --- a/deepparse/converter/data_transform.py +++ b/deepparse/converter/data_transform.py @@ -26,7 +26,7 @@ class DataTransform: `issue `_. """ - def __init__(self, vectorizer: TrainVectorizer, model_type: str): + def __init__(self, vectorizer: TrainVectorizer, model_type: str) -> None: self.vectorizer = vectorizer if "fasttext" in model_type and "light" not in model_type: self.teacher_forcing_data_padding_fn = fasttext_data_padding_teacher_forcing diff --git a/deepparse/converter/target_converter.py b/deepparse/converter/target_converter.py index e5556562..c55e4017 100644 --- a/deepparse/converter/target_converter.py +++ b/deepparse/converter/target_converter.py @@ -23,5 +23,5 @@ def __call__(self, key: Union[str, int]) -> int: return self.idx_to_tags[key] @property - def dim(self): + def dim(self) -> int: return len(self.tags_to_idx) diff --git a/deepparse/data_error/data_error.py b/deepparse/data_error/data_error.py index 52c4b9f2..20f41a9c 100644 --- a/deepparse/data_error/data_error.py +++ b/deepparse/data_error/data_error.py @@ -3,9 +3,9 @@ class DataError(Exception): User error when data is not construct as expected. """ - def __init__(self, value): + def __init__(self, value: str) -> None: super().__init__() self.value = value - def __str__(self): + def __str__(self) -> str: return repr(self.value) # pragma: no cover diff --git a/deepparse/dataset_container/dataset_container.py b/deepparse/dataset_container/dataset_container.py index 1e33b420..bc41b65c 100644 --- a/deepparse/dataset_container/dataset_container.py +++ b/deepparse/dataset_container/dataset_container.py @@ -50,7 +50,22 @@ def __init__(self, is_training_container: bool = True) -> None: def __len__(self) -> int: return len(self.data) - def __getitem__(self, idx: Union[int, slice]): + def __getitem__( + self, idx: Union[int, slice] + ) -> Union[List[str], str, List[List[tuple[str, List]]], tuple[str, List]]: + """ + If the DatasetContainer is a predict one: + + - it can be a list of string items (e.g. a list of address (str)), or + - it can be a unique string item (e.g. one address). + + If the DatasetContainer is a training one: + + - it can be a list of tuple (str, list) items, namely a list of parsed example (e.g. an address with + the tags), or + - it can be a tuple (str, list) item. + + """ if isinstance(idx, slice): start, stop, _ = idx.indices(len(self)) result = [self.data[index] for index in range(start, stop)] @@ -83,10 +98,10 @@ def validate_dataset(self) -> None: if validate_if_any_whitespace_only(string_elements=data_to_validate): raise DataError("Some addresses only include whitespace thus cannot be parsed.") - def _data_is_a_list(self): + def _data_is_a_list(self) -> bool: return isinstance(self.data, list) - def _training_validation(self): + def _training_validation(self) -> None: if not self._data_is_list_of_tuple(): raise TypeError( "The pickled dataset data are not in a tuple format. Data" @@ -290,7 +305,7 @@ class ListDatasetContainer(DatasetContainer): The default value is true. """ - def __init__(self, data: List, is_training_container: bool = True): + def __init__(self, data: List, is_training_container: bool = True) -> None: super().__init__(is_training_container=is_training_container) self.data = data self.validate_dataset() diff --git a/deepparse/embeddings_models/embeddings_model.py b/deepparse/embeddings_models/embeddings_model.py index 6a03fd78..416a97c8 100644 --- a/deepparse/embeddings_models/embeddings_model.py +++ b/deepparse/embeddings_models/embeddings_model.py @@ -18,5 +18,5 @@ def __call__(self, words: str) -> ndarray: pass @property - def dim(self): + def dim(self) -> int: return self.model.dim diff --git a/deepparse/embeddings_models/fasttext_embeddings_model.py b/deepparse/embeddings_models/fasttext_embeddings_model.py index e8ef9dc7..b4b34e08 100644 --- a/deepparse/embeddings_models/fasttext_embeddings_model.py +++ b/deepparse/embeddings_models/fasttext_embeddings_model.py @@ -44,5 +44,5 @@ def __call__(self, word: str) -> ndarray: return self.model[word] @property - def dim(self): + def dim(self) -> int: return self.model.get_dimension() diff --git a/deepparse/metrics/accuracy.py b/deepparse/metrics/accuracy.py index 0301617d..7a133e75 100644 --- a/deepparse/metrics/accuracy.py +++ b/deepparse/metrics/accuracy.py @@ -1,7 +1,8 @@ +import torch from poutyne.framework.metrics import acc -def accuracy(pred, ground_truth): +def accuracy(pred: torch.Tensor, ground_truth: torch.Tensor): """ Accuracy per tag. """ diff --git a/deepparse/metrics/nll_loss.py b/deepparse/metrics/nll_loss.py index fd6c835f..f2f97e3f 100644 --- a/deepparse/metrics/nll_loss.py +++ b/deepparse/metrics/nll_loss.py @@ -1,9 +1,10 @@ +import torch from torch.nn import NLLLoss criterion = NLLLoss() -def nll_loss(pred, ground_truth): +def nll_loss(pred: torch.Tensor, ground_truth: torch.Tensor): """ NLL loss compute per tag. """ diff --git a/deepparse/network/decoder.py b/deepparse/network/decoder.py index 9405be13..93964afb 100644 --- a/deepparse/network/decoder.py +++ b/deepparse/network/decoder.py @@ -39,7 +39,7 @@ def __init__( # Since layer also have attention mechanism self.hidden_size = hidden_size input_size = input_size + hidden_size - self.attention_mechanism_set_up() + self._attention_mechanism_set_up() self.softmax = nn.LogSoftmax(dim=1) self.lstm = nn.LSTM(input_size, hidden_size, num_layers=num_layers) self.lstm.apply(weights_init) @@ -64,7 +64,7 @@ def forward( Return: A tuple (``x``, ``y``, ``z``) where ``x`` is the address components tags predictions, y is the hidden - states and `̀`z`` is None if no attention mechanism is setter or the attention weights. + states and ``z`` is None if no attention mechanism is setter or the attention weights. """ to_predict = to_predict.float() @@ -84,11 +84,11 @@ def forward( attention_weights, ) # attention_weights: None or the real attention weights - def linear_layer_set_up(self, output_size: int, hidden_size: int = 1024): + def linear_layer_set_up(self, output_size: int, hidden_size: int = 1024) -> None: self.linear = nn.Linear(hidden_size, output_size) self.linear.apply(weights_init) - def attention_mechanism_set_up(self, hidden_size: int = 1024): + def _attention_mechanism_set_up(self, hidden_size: int = 1024) -> None: self.linear_attention_mechanism_encoder_outputs = nn.Linear(hidden_size, hidden_size) self.linear_attention_mechanism_encoder_outputs.apply(weights_init) diff --git a/deepparse/parser/address_parser.py b/deepparse/parser/address_parser.py index b6bcebe9..80a45a29 100644 --- a/deepparse/parser/address_parser.py +++ b/deepparse/parser/address_parser.py @@ -536,7 +536,7 @@ def retrain( # The validation dataset is created from the training dataset (container) # 80% of the data is use for training and 20% as a validation dataset - address_parser.retrain(container, 0.8, epochs=1, batch_size=128) + address_parser.retrain(container, train_ratio=0.8, epochs=1, batch_size=128) Using the freezing layers parameters to freeze layers during training @@ -568,7 +568,7 @@ def retrain( container = CSVDatasetContainer(data_path) lr_scheduler = poutyne.StepLR(step_size=1, gamma=0.1) # reduce LR by a factor of 10 each epoch - address_parser.retrain(container, 0.8, epochs=5, batch_size=128, callbacks=[lr_scheduler]) + address_parser.retrain(container, train_ratio=0.8, epochs=5, batch_size=128, callbacks=[lr_scheduler]) Using your own prediction tags dictionary. @@ -581,7 +581,8 @@ def retrain( container = PickleDatasetContainer(data_path) - address_parser.retrain(container, 0.8, epochs=1, batch_size=128, prediction_tags=address_components) + address_parser.retrain(container, train_ratio=0.8, epochs=1, batch_size=128, + prediction_tags=address_components) Using your own seq2seq parameters. @@ -594,7 +595,8 @@ def retrain( container = PickleDatasetContainer(data_path) - address_parser.retrain(container, 0.8, epochs=1, batch_size=128, seq2seq_params=seq2seq_params) + address_parser.retrain(container, train_ratio=0.8, epochs=1, batch_size=128, + seq2seq_params=seq2seq_params) Using your own seq2seq parameters and prediction tags dictionary. @@ -609,8 +611,8 @@ def retrain( container = PickleDatasetContainer(data_path) - address_parser.retrain(container, 0.8, epochs=1, batch_size=128, seq2seq_params=seq2seq_params, - prediction_tags=address_components) + address_parser.retrain(container, train_ratio=0.8, epochs=1, batch_size=128, + seq2seq_params=seq2seq_params, prediction_tags=address_components) Using a named retrain parser name. @@ -621,7 +623,7 @@ def retrain( container = PickleDatasetContainer(data_path) - address_parser.retrain(container, 0.8, epochs=1, batch_size=128, + address_parser.retrain(container, train_ratio=0.8, epochs=1, batch_size=128, name_of_the_retrain_parser="MyParserName") """ @@ -811,7 +813,8 @@ def test( train_container = PickleDatasetContainer(data_path) - address_parser.retrain(container, 0.8, epochs=1, batch_size=128, prediction_tags=address_components) + address_parser.retrain(container, train_ratio=0.8, epochs=1, batch_size=128, + prediction_tags=address_components) # Test phase data_path = "path_to_a_pickle_test_dataset.p" diff --git a/deepparse/preprocessing/address_cleaner.py b/deepparse/preprocessing/address_cleaner.py index e9402d17..abcc23a0 100644 --- a/deepparse/preprocessing/address_cleaner.py +++ b/deepparse/preprocessing/address_cleaner.py @@ -8,7 +8,7 @@ class AddressCleaner: - def __init__(self, with_hyphen_split: bool = False): + def __init__(self, with_hyphen_split: bool = False) -> None: self.with_hyphen_split = with_hyphen_split def clean(self, addresses: List[str]) -> List[str]: diff --git a/deepparse/tools.py b/deepparse/tools.py index 12a696fd..bd2da975 100644 --- a/deepparse/tools.py +++ b/deepparse/tools.py @@ -85,7 +85,7 @@ def latest_version(model: str, cache_path: str, verbose: bool) -> bool: def download_from_public_repository(file_name: str, saving_dir: str, file_extension: str) -> None: """ Simple function to download the content of a file from Deepparse public repository. - The repository URL string is ̀`'https://graal.ift.ulaval.ca/public/deepparse/{}.{}'`` + The repository URL string is `'https://graal.ift.ulaval.ca/public/deepparse/{}.{}'`` where the first bracket is the file name and the second is the file extension. """ url = BASE_URL.format(file_name, file_extension) diff --git a/deepparse/vectorizer/bpemb_vectorizer.py b/deepparse/vectorizer/bpemb_vectorizer.py index edd7f356..4d3ee609 100644 --- a/deepparse/vectorizer/bpemb_vectorizer.py +++ b/deepparse/vectorizer/bpemb_vectorizer.py @@ -19,7 +19,7 @@ def __init__(self, embeddings_model: EmbeddingsModel) -> None: self.padding_value = 0 self._max_length = 0 - def _reset_max_length(self): + def _reset_max_length(self) -> None: self._max_length = 0 def __call__(self, addresses: List[str]) -> List[Tuple]: diff --git a/deepparse/vectorizer/train_vectorizer.py b/deepparse/vectorizer/train_vectorizer.py index 453b96eb..9d115d76 100644 --- a/deepparse/vectorizer/train_vectorizer.py +++ b/deepparse/vectorizer/train_vectorizer.py @@ -1,15 +1,18 @@ -from typing import List +from typing import List, Iterable + +from deepparse.converter import TagsConverter +from deepparse.vectorizer import Vectorizer class TrainVectorizer: - def __init__(self, embedding_vectorizer, tags_vectorizer): + def __init__(self, embedding_vectorizer: Vectorizer, tags_converter: TagsConverter) -> None: """ Vectorizer use during training to convert an address into word embeddings and to provide the target. """ self.embedding_vectorizer = embedding_vectorizer - self.tags_vectorizer = tags_vectorizer + self.tags_converter = tags_converter - def __call__(self, addresses: List[str]): + def __call__(self, addresses: List[str]) -> Iterable: """ Method to vectorizer addresses for training. @@ -28,7 +31,7 @@ def __call__(self, addresses: List[str]): # Otherwise, the padding for byte-pair encoding will be broken for address in addresses: - target_tmp = [self.tags_vectorizer(target) for target in address[1]] - target_tmp.append(self.tags_vectorizer("EOS")) # to append the End Of Sequence token + target_tmp = [self.tags_converter(target) for target in address[1]] + target_tmp.append(self.tags_converter("EOS")) # to append the End Of Sequence token target_sequence.append(target_tmp) return zip(input_sequence, target_sequence) diff --git a/docs/source/examples/fine_tuning.rst b/docs/source/examples/fine_tuning.rst index e30dab2e..b5aab8c8 100644 --- a/docs/source/examples/fine_tuning.rst +++ b/docs/source/examples/fine_tuning.rst @@ -54,7 +54,7 @@ an error when retraining since Poutyne will try to use the last checkpoint. .. code-block:: python - address_parser.retrain(training_container, 0.8, epochs=5, batch_size=8, num_workers=2, callbacks=[lr_scheduler]) + address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size=8, num_workers=2, callbacks=[lr_scheduler]) Now, let's test our fine-tuned model using the best checkpoint (default parameter). @@ -74,7 +74,7 @@ Otherwise, poutyne will try to reload the previous checkpoints, and our model ha .. code-block:: python address_parser.retrain(training_container, - 0.8, + train_ratio=0.8, epochs=5, batch_size=8, num_workers=2, diff --git a/docs/source/examples/fine_tuning_with_csv_dataset.rst b/docs/source/examples/fine_tuning_with_csv_dataset.rst index f5c67193..18e5df20 100644 --- a/docs/source/examples/fine_tuning_with_csv_dataset.rst +++ b/docs/source/examples/fine_tuning_with_csv_dataset.rst @@ -54,7 +54,7 @@ an error when retraining since Poutyne will try to use the last checkpoint. .. code-block:: python - address_parser.retrain(training_container, 0.8, epochs=5, batch_size=8, num_workers=2, callbacks=[lr_scheduler]) + address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size=8, num_workers=2, callbacks=[lr_scheduler]) Now, let's test our fine-tuned model using the best checkpoint (default parameter). @@ -74,7 +74,7 @@ Otherwise, poutyne will try to reload the previous checkpoints, and our model ha .. code-block:: python address_parser.retrain(training_container, - 0.8, + train_ratio=0.8, epochs=5, batch_size=8, num_workers=2, diff --git a/docs/source/examples/retrain_attention_model.rst b/docs/source/examples/retrain_attention_model.rst index 92a603b3..12f63882 100644 --- a/docs/source/examples/retrain_attention_model.rst +++ b/docs/source/examples/retrain_attention_model.rst @@ -51,7 +51,7 @@ as we progress. address_parser.retrain( training_container, - 0.8, + train_ratio=0.8, epochs=5, batch_size=8, num_workers=2, diff --git a/docs/source/examples/retrain_with_new_prediction_tags.rst b/docs/source/examples/retrain_with_new_prediction_tags.rst index 29297a8c..e2480062 100644 --- a/docs/source/examples/retrain_with_new_prediction_tags.rst +++ b/docs/source/examples/retrain_with_new_prediction_tags.rst @@ -56,7 +56,7 @@ Let's start with the default learning rate of 0.01 and use a learning rate sched logging_path = "./checkpoints" address_parser.retrain(training_container, - 0.8, + train_ratio=0.8, epochs=5, batch_size=8, num_workers=2, diff --git a/docs/source/examples/retrain_with_new_seq2seq_params.rst b/docs/source/examples/retrain_with_new_seq2seq_params.rst index 87cb7f79..099f3654 100644 --- a/docs/source/examples/retrain_with_new_seq2seq_params.rst +++ b/docs/source/examples/retrain_with_new_seq2seq_params.rst @@ -63,7 +63,7 @@ Let's start with the default learning rate of 0.01 and use a learning rate sched } address_parser.retrain(training_container, - 0.8, + train_ratio=0.8, epochs=5, batch_size=8, num_workers=2, diff --git a/docs/source/index.rst b/docs/source/index.rst index 290e1422..d431f58a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -635,13 +635,13 @@ for a complete example using CSV. .. code-block:: python - address_parser.retrain(training_container, 0.8, epochs=5, batch_size=8) + address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size=8) One can also freeze some layers to speed up the training using the ``layers_to_freeze`` parameter. .. code-block:: python - address_parser.retrain(training_container, 0.8, epochs=5, batch_size=8, layers_to_freeze="seq2seq") + address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size=8, layers_to_freeze="seq2seq") Or you can also give a specific name to the retrained model. This name will be use as the model name (for print and @@ -649,7 +649,7 @@ class name) when reloading it. .. code-block:: python - address_parser.retrain(training_container, 0.8, epochs=5, batch_size=8, name_of_the_retrain_parser="MyNewParser") + address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size=8, name_of_the_retrain_parser="MyNewParser") Retrain a Model With an Attention Mechanism @@ -661,7 +661,7 @@ See `here Date: Thu, 11 Aug 2022 23:47:06 -0400 Subject: [PATCH 074/195] add pylint step on code examples --- .github/workflows/linting.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 4ea7b976..96902c1e 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -23,4 +23,5 @@ jobs: pip install -e . - name: PyLint run: | - pylint deepparse/ tests/ \ No newline at end of file + pylint deepparse/ tests/ + pylint examples/ --disable=no-member # For Poutyne false error \ No newline at end of file From 01e3e0862a58a395f365b63f6d8122e1e2447b2a Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 12 Aug 2022 11:02:30 -0400 Subject: [PATCH 075/195] added missing typing, uniformization of assertFileExist fn, added integration test and improved doc --- .../dataset_container/dataset_container.py | 4 +- deepparse/fasttext_tools.py | 10 +++-- deepparse/weights_init.py | 5 ++- tests/base_file_exist.py | 10 +++++ ...test_integration_bpemb_embeddings_model.py | 5 ++- .../test_integration_address_parser.py | 12 ++---- tests/test_fasttext_tools.py | 2 +- tests/test_integration_fasttext_tools.py | 37 +++++++++++++++++++ tests/test_tools.py | 7 ++-- 9 files changed, 71 insertions(+), 21 deletions(-) create mode 100644 tests/base_file_exist.py create mode 100644 tests/test_integration_fasttext_tools.py diff --git a/deepparse/dataset_container/dataset_container.py b/deepparse/dataset_container/dataset_container.py index bc41b65c..5793136f 100644 --- a/deepparse/dataset_container/dataset_container.py +++ b/deepparse/dataset_container/dataset_container.py @@ -1,7 +1,7 @@ # pylint: disable=too-many-arguments from abc import ABC, abstractmethod from pickle import load -from typing import Union, List, Dict, Callable +from typing import Union, List, Dict, Callable, Tuple import pandas as pd from torch.utils.data import Dataset @@ -52,7 +52,7 @@ def __len__(self) -> int: def __getitem__( self, idx: Union[int, slice] - ) -> Union[List[str], str, List[List[tuple[str, List]]], tuple[str, List]]: + ) -> Union[List[str], str, List[List[Tuple[str, List]]], Tuple[str, List]]: """ If the DatasetContainer is a predict one: diff --git a/deepparse/fasttext_tools.py b/deepparse/fasttext_tools.py index 7fad8d59..d41dc3be 100644 --- a/deepparse/fasttext_tools.py +++ b/deepparse/fasttext_tools.py @@ -13,6 +13,8 @@ def download_fasttext_magnitude_embeddings(cache_dir: str, verbose: bool = True, saving_dir=None) -> str: """ Function to download the magnitude pretrained fastText model. + + Return the full path to the fastText embeddings. """ if saving_dir is not None: # pragma: no cover # Deprecated argument handling @@ -82,6 +84,8 @@ def download_fasttext_embeddings(cache_dir: str, verbose: bool = True, saving_di Simpler version of the download_model function from fastText to download pretrained common-crawl vectors from fastText's website https://fasttext.cc/docs/en/crawl-vectors.html and save it in the saving directory (saving_dir). + + Return the full path to the fastText embeddings. """ if saving_dir is not None: # pragma: no cover # Deprecated argument handling @@ -154,7 +158,7 @@ def _download_file(url: str, write_file_name: str, chunk_size: int = 2**13, verb # Better print formatting for some shell that don't update properly. -def _print_progress(downloaded_bytes, total_size): +def _print_progress(downloaded_bytes: int, total_size: int) -> None: percent = float(downloaded_bytes) / total_size bar_size = 50 progress_bar = int(percent * bar_size) @@ -170,8 +174,8 @@ def _print_progress(downloaded_bytes, total_size): # The difference with the original code is the removal of the print warning. -def load_fasttext_embeddings(path): +def load_fasttext_embeddings(path: str) -> _FastText: """ - Load a model given a filepath and return a model object. + Wrapper to load a model given a filepath and return a model object. """ return _FastText(model_path=path) diff --git a/deepparse/weights_init.py b/deepparse/weights_init.py index 551f5d86..5e6b13b2 100644 --- a/deepparse/weights_init.py +++ b/deepparse/weights_init.py @@ -2,9 +2,10 @@ from torch.nn import init -def weights_init(m): +def weights_init(m: nn.Module) -> None: """ - Function to initialize the weights of our layers. + Function to initialize the weights of a model layers. + Usage: network = Model() network.apply(weight_init) diff --git a/tests/base_file_exist.py b/tests/base_file_exist.py new file mode 100644 index 00000000..9c0301a8 --- /dev/null +++ b/tests/base_file_exist.py @@ -0,0 +1,10 @@ +from os.path import exists +from pathlib import Path +from typing import Union +from unittest import TestCase + + +class FileCreationTestCase(TestCase): + def assertFileExist(self, file_path: Union[str, Path]) -> None: + file_exists = exists(file_path) + self.assertTrue(file_exists) diff --git a/tests/embeddings_models/integration/test_integration_bpemb_embeddings_model.py b/tests/embeddings_models/integration/test_integration_bpemb_embeddings_model.py index 1aafdae9..8b1d9bed 100644 --- a/tests/embeddings_models/integration/test_integration_bpemb_embeddings_model.py +++ b/tests/embeddings_models/integration/test_integration_bpemb_embeddings_model.py @@ -10,11 +10,12 @@ from torch.utils.data import DataLoader from deepparse.embeddings_models import BPEmbEmbeddingsModel +from tests.base_file_exist import FileCreationTestCase from tests.embeddings_models.integration.tools import MockedDataTransform from tests.parser.integration.base_retrain import AddressParserRetrainTestCase -class BPEmbEmbeddingsModelIntegrationTest(AddressParserRetrainTestCase): +class BPEmbEmbeddingsModelIntegrationTest(AddressParserRetrainTestCase, FileCreationTestCase): @classmethod def setUpClass(cls): super(BPEmbEmbeddingsModelIntegrationTest, cls).setUpClass() @@ -27,7 +28,7 @@ def setUpClass(cls): def test_givenANewCacheDir_whenBPEmbModelInit_thenCreateNewCache(self): BPEmbEmbeddingsModel(self.fake_cache_path, verbose=self.verbose) - self.assertTrue(os.path.exists(os.path.join(self.fake_cache_path, "multi"))) + self.assertFileExist(os.path.join(self.fake_cache_path, "multi")) @skipIf(platform.system() != "Windows", "Integration test on Windows env.") def test_givenAWindowsOS_whenBPEmbModelInit_thenLoadWithProperFunction(self): diff --git a/tests/parser/integration/test_integration_address_parser.py b/tests/parser/integration/test_integration_address_parser.py index 39dd6743..8023cd3f 100644 --- a/tests/parser/integration/test_integration_address_parser.py +++ b/tests/parser/integration/test_integration_address_parser.py @@ -6,13 +6,13 @@ import os from collections import OrderedDict -from os.path import exists from pathlib import Path from tempfile import TemporaryDirectory from unittest import skipIf import torch +from tests.base_file_exist import FileCreationTestCase from tests.parser.integration.base_predict import ( AddressParserBase, ) @@ -22,7 +22,7 @@ not os.path.exists(os.path.join(os.path.expanduser("~"), ".cache", "deepparse", "cc.fr.300.bin")), "download of model too long for test in runner", ) -class AddressParserTest(AddressParserBase): +class AddressParserTest(AddressParserBase, FileCreationTestCase): @classmethod def setUpClass(cls): super(AddressParserTest, cls).setUpClass() @@ -34,10 +34,6 @@ def setUpClass(cls): def tearDownClass(cls) -> None: cls.temp_dir_obj.cleanup() - def assert_file_exist(self, file_path): - file_exists = exists(file_path) - self.assertTrue(file_exists) - def setUp(self) -> None: a_config = {"model_type": "fasttext", "device": "cpu", "verbose": False} self.setup_model_with_config(a_config) @@ -47,14 +43,14 @@ def test_givenAModelToExportDictStr_thenExportIt(self): self.a_model.save_model_weights(file_path=a_file_path) - self.assert_file_exist(a_file_path) + self.assertFileExist(a_file_path) def test_givenAModelToExportDictPathALike_thenExportIt(self): a_file_path = Path(os.path.join(self.a_saving_dir_path, "exported_model.p")) self.a_model.save_model_weights(file_path=a_file_path) - self.assert_file_exist(a_file_path) + self.assertFileExist(a_file_path) def test_givenAnExportedModelUsingTheMethod_whenReloadIt_thenReload(self): a_file_path = Path(os.path.join(self.a_saving_dir_path, "exported_model.p")) diff --git a/tests/test_fasttext_tools.py b/tests/test_fasttext_tools.py index 4f90a782..bc6fd28e 100644 --- a/tests/test_fasttext_tools.py +++ b/tests/test_fasttext_tools.py @@ -23,7 +23,7 @@ from tests.tools import create_file -class ToolsTests(CaptureOutputTestCase): +class FastTextToolsTests(CaptureOutputTestCase): @classmethod def setUpClass(cls) -> None: cls.temp_dir_obj = TemporaryDirectory() diff --git a/tests/test_integration_fasttext_tools.py b/tests/test_integration_fasttext_tools.py new file mode 100644 index 00000000..81394b73 --- /dev/null +++ b/tests/test_integration_fasttext_tools.py @@ -0,0 +1,37 @@ +# Pylint error for TemporaryDirectory ask for with statement +# pylint: disable=consider-using-with + +import os +from tempfile import TemporaryDirectory +from unittest import skipIf + +import torch + +from deepparse import download_fasttext_embeddings, download_fasttext_magnitude_embeddings +from tests.base_file_exist import FileCreationTestCase + + +@skipIf(not torch.cuda.is_available(), "Cannot run test on GitHub action runner.") +class IntegrationFastTextToolsTests(FileCreationTestCase): + def setUp(self) -> None: + self.temp_dir_obj = TemporaryDirectory() + self.a_cache_dir = self.temp_dir_obj.name + self.a_fasttext_file_name_path = os.path.join(self.a_cache_dir, "cc.fr.300.bin") + self.a_fasttext_light_name_path = os.path.join(self.a_cache_dir, "fasttext.magnitude") + + def tearDown(self) -> None: + self.temp_dir_obj.cleanup() + + def test_integrationDownloadFastTextEmbeddings(self): + expected = self.a_fasttext_file_name_path + actual = download_fasttext_embeddings(self.a_cache_dir) + self.assertEqual(expected, actual) + + self.assertFileExist(actual) + + def test_integrationDownloadFastTextMagnitudeEmbeddings(self): + expected = self.a_fasttext_light_name_path + actual = download_fasttext_magnitude_embeddings(self.a_cache_dir) + self.assertEqual(expected, actual) + + self.assertFileExist(actual) diff --git a/tests/test_tools.py b/tests/test_tools.py index e0173fff..4bb5199d 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -23,10 +23,11 @@ DataError, ) from tests.base_capture_output import CaptureOutputTestCase +from tests.base_file_exist import FileCreationTestCase from tests.tools import create_file -class ToolsTests(CaptureOutputTestCase): +class ToolsTests(CaptureOutputTestCase, FileCreationTestCase): def setUp(self) -> None: self.temp_dir_obj = TemporaryDirectory() self.fake_cache_path = self.temp_dir_obj.name @@ -141,7 +142,7 @@ def test_givenFasttextVersion_whenDownloadOk_thenDownloadIt(self): download_from_public_repository(file_name, self.fake_cache_path, self.a_file_extension) - self.assertTrue(os.path.exists(os.path.join(self.fake_cache_path, f"{file_name}.{self.a_file_extension}"))) + self.assertFileExist(os.path.join(self.fake_cache_path, f"{file_name}.{self.a_file_extension}")) def test_givenFasttextVersion_whenDownload404_thenHTTPError(self): wrong_file_name = "wrong_fasttext" @@ -154,7 +155,7 @@ def test_givenBPEmbVersion_whenDownloadOk_thenDownloadIt(self): download_from_public_repository(file_name, self.fake_cache_path, self.a_file_extension) - self.assertTrue(os.path.exists(os.path.join(self.fake_cache_path, f"{file_name}.{self.a_file_extension}"))) + self.assertFileExist(os.path.join(self.fake_cache_path, f"{file_name}.{self.a_file_extension}")) def test_givenBPEmbVersion_whenDownload404_thenHTTPError(self): wrong_file_name = "wrong_bpemb" From b3c9713a5aab1a3b010982be47e5f07cd041b94a Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 12 Aug 2022 11:12:19 -0400 Subject: [PATCH 076/195] remove comment in linting ci to bug fix if failling problem --- .github/workflows/linting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 96902c1e..b9203f99 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -24,4 +24,4 @@ jobs: - name: PyLint run: | pylint deepparse/ tests/ - pylint examples/ --disable=no-member # For Poutyne false error \ No newline at end of file + pylint examples/ --disable=no-member \ No newline at end of file From a5bf182ec039368dd8b824e0e1fd2f4648f317c8 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 12 Aug 2022 11:17:59 -0400 Subject: [PATCH 077/195] fix dead verbose retrain api flag --- tests/parser/test_address_parser_retrain_api.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/parser/test_address_parser_retrain_api.py b/tests/parser/test_address_parser_retrain_api.py index 24a42fbb..f75c71fc 100644 --- a/tests/parser/test_address_parser_retrain_api.py +++ b/tests/parser/test_address_parser_retrain_api.py @@ -12,7 +12,6 @@ import platform import unittest from tempfile import TemporaryDirectory -from typing import Union from unittest import skipIf from unittest.mock import patch, call, MagicMock @@ -76,7 +75,6 @@ def address_parser_retrain_call( layers_to_freeze=None, name_of_the_retrain_parser=None, num_workers=None, - verbose: Union[bool, None] = True, ): if num_workers is None: # AddressParser default num_workers settings is 1 @@ -109,7 +107,6 @@ def address_parser_retrain_call( seq2seq_params=seq2seq_params, layers_to_freeze=layers_to_freeze, name_of_the_retrain_parser=name_of_the_retrain_parser, - verbose=verbose, ) def assert_experiment_retrain(self, experiment_mock, model_mock, optimizer_mock, device): From 4983b3a1a5f17114af7fd092136726922ac977b5 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 12 Aug 2022 11:25:53 -0400 Subject: [PATCH 078/195] add ini option for django --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fed5a3b6..badaca8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,4 +9,7 @@ addopts = "--cov ./deepparse --cov-report html --cov-report xml --cov-config=.co testpaths = [ "tests", -] \ No newline at end of file +] + +[tool.pylint.ini_options] +DJANGO_SETTINGS_MODULE = "settings.py" \ No newline at end of file From 13ba1ac1ce7e23e91d616e23fd56f8b4061698c4 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 12 Aug 2022 12:59:56 -0400 Subject: [PATCH 079/195] remove linting of code example since fail due to pylint-django and I am unable to make it work --- .github/workflows/linting.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index b9203f99..4ea7b976 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -23,5 +23,4 @@ jobs: pip install -e . - name: PyLint run: | - pylint deepparse/ tests/ - pylint examples/ --disable=no-member \ No newline at end of file + pylint deepparse/ tests/ \ No newline at end of file From 3c968b6c7330708e6e366cefb73bc9a46051f240 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 12 Aug 2022 13:06:10 -0400 Subject: [PATCH 080/195] fixed django settings --- .github/workflows/linting.yml | 3 ++- pyproject.toml | 2 +- settings/__init__.py | 0 settings.py => settings/settings.py | 0 4 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 settings/__init__.py rename settings.py => settings/settings.py (100%) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 4ea7b976..5d660b0c 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -23,4 +23,5 @@ jobs: pip install -e . - name: PyLint run: | - pylint deepparse/ tests/ \ No newline at end of file + pylint deepparse/ tests/ + DJANGO_SETTINGS_MODULE=settings pylint examples/ --disable=no-member \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index badaca8a..ce320ed4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,4 +12,4 @@ testpaths = [ ] [tool.pylint.ini_options] -DJANGO_SETTINGS_MODULE = "settings.py" \ No newline at end of file +DJANGO_SETTINGS_MODULE = "settings" \ No newline at end of file diff --git a/settings/__init__.py b/settings/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/settings.py b/settings/settings.py similarity index 100% rename from settings.py rename to settings/settings.py From 73ccc94c9c4810543a8cfbc1692fa48744196b95 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 12 Aug 2022 13:13:18 -0400 Subject: [PATCH 081/195] add steps to install depparse for code examples linting --- .github/workflows/linting.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 5d660b0c..e59ecffb 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -24,4 +24,10 @@ jobs: - name: PyLint run: | pylint deepparse/ tests/ + - name: Install code examples dependencies + run: | + pip install -U deepparse + pip install -e . + - name: PyLint code examples + run: | DJANGO_SETTINGS_MODULE=settings pylint examples/ --disable=no-member \ No newline at end of file From 4634523eef9659665f99b0c87fed63e550bc87eb Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 12 Aug 2022 13:24:30 -0400 Subject: [PATCH 082/195] remove install -e --- .github/workflows/linting.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index e59ecffb..58152500 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -27,7 +27,6 @@ jobs: - name: Install code examples dependencies run: | pip install -U deepparse - pip install -e . - name: PyLint code examples run: | DJANGO_SETTINGS_MODULE=settings pylint examples/ --disable=no-member \ No newline at end of file From 2fbb44b63d9689dcd0cc8a12c5c01e2ded82b1a0 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 12 Aug 2022 13:31:48 -0400 Subject: [PATCH 083/195] reinstaller install -e . --- .github/workflows/linting.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 58152500..e59ecffb 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -27,6 +27,7 @@ jobs: - name: Install code examples dependencies run: | pip install -U deepparse + pip install -e . - name: PyLint code examples run: | DJANGO_SETTINGS_MODULE=settings pylint examples/ --disable=no-member \ No newline at end of file From 4c318c027f2addec5fe1c7dbd74f2971f6efcf83 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 12 Aug 2022 13:59:39 -0400 Subject: [PATCH 084/195] add skip=no-member since it is mostly flase positive --- .github/workflows/linting.yml | 9 +-------- .pylintrc | 3 ++- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index e59ecffb..1f1148ea 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -23,11 +23,4 @@ jobs: pip install -e . - name: PyLint run: | - pylint deepparse/ tests/ - - name: Install code examples dependencies - run: | - pip install -U deepparse - pip install -e . - - name: PyLint code examples - run: | - DJANGO_SETTINGS_MODULE=settings pylint examples/ --disable=no-member \ No newline at end of file + pylint deepparse/ tests/ examples/ \ No newline at end of file diff --git a/.pylintrc b/.pylintrc index 330e86d6..69e33623 100644 --- a/.pylintrc +++ b/.pylintrc @@ -93,7 +93,8 @@ disable=raw-checker-failed, attribute-defined-outside-init, too-many-instance-attributes, cyclic-import, - duplicate-code + duplicate-code, + no-member # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option From 61f70305f0de6c1cf70f99ddad2b5154d7f62d64 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 12 Aug 2022 14:06:30 -0400 Subject: [PATCH 085/195] removed no-member pylint disable --- deepparse/converter/data_padding.py | 4 ---- deepparse/network/bpemb_seq2seq.py | 4 ---- deepparse/network/decoder.py | 3 --- deepparse/network/embedding_network.py | 4 ---- deepparse/network/fasttext_seq2seq.py | 3 --- deepparse/network/seq2seq.py | 3 --- deepparse/parser/address_parser.py | 4 ---- deepparse/parser/tools.py | 4 ---- tests/cli/test_tools.py | 2 +- tests/converter/test_data_padding.py | 4 ---- tests/metrics/test_accuracy.py | 4 ---- tests/metrics/test_nll_loss.py | 4 ---- tests/network/base.py | 4 ---- tests/network/integration/base.py | 4 ---- tests/network/integration/test_integration_decoder.py | 4 ---- .../integration/test_integration_embedding_network.py | 4 ---- tests/network/integration/test_integration_encoder.py | 4 ---- .../integration/test_integration_seq2seq_bpemb_model_cpu.py | 4 ---- .../integration/test_integration_seq2seq_bpemb_model_gpu.py | 4 ---- .../test_integration_seq2seq_fasttext_model_cpu.py | 4 ---- .../test_integration_seq2seq_fasttext_model_gpu.py | 4 ---- .../integration/test_integration_seq2seq_model_cpu.py | 4 ---- .../integration/test_integration_seq2seq_model_gpu.py | 4 ---- tests/network/test_bpemb_seq2seq_model_cpu.py | 4 ---- tests/network/test_bpemb_seq2seq_model_gpu.py | 4 ---- tests/network/test_decoder.py | 4 ---- tests/network/test_fasttext_seq2seq_model_cpu.py | 4 ---- tests/network/test_fasttext_seq2seq_model_gpu.py | 4 ---- tests/network/test_seq2seq.py | 4 ---- tests/parser/base.py | 5 ----- tests/parser/integration/base_predict.py | 3 +-- tests/parser/integration/base_retrain.py | 3 +-- .../integration/test_integration_address_parser_gpu.py | 4 ---- .../test_integration_address_parser_new_params_new_tags.py | 3 +-- tests/parser/test_address_parser.py | 6 +----- tests/parser/test_address_parser_retrain_api.py | 4 ---- tests/parser/test_address_parser_test_api.py | 4 ---- tests/parser/test_formatted_parsed_address.py | 2 +- tests/parser/test_tools.py | 4 ---- tests/parser/test_user_fromatted_parsed_address.py | 2 +- tests/tools.py | 4 ---- 41 files changed, 7 insertions(+), 148 deletions(-) diff --git a/deepparse/converter/data_padding.py b/deepparse/converter/data_padding.py index baaa01e3..f23af767 100644 --- a/deepparse/converter/data_padding.py +++ b/deepparse/converter/data_padding.py @@ -1,10 +1,6 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - from typing import List, Tuple, Iterable import numpy as np diff --git a/deepparse/network/bpemb_seq2seq.py b/deepparse/network/bpemb_seq2seq.py index de0851f3..7978d2b7 100644 --- a/deepparse/network/bpemb_seq2seq.py +++ b/deepparse/network/bpemb_seq2seq.py @@ -1,9 +1,5 @@ # pylint: disable=too-many-arguments, duplicate-code -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - from typing import List, Union import torch diff --git a/deepparse/network/decoder.py b/deepparse/network/decoder.py index 93964afb..0ac68557 100644 --- a/deepparse/network/decoder.py +++ b/deepparse/network/decoder.py @@ -1,9 +1,6 @@ # temporary fix for _forward_unimplemented for torch 1.6 https://github.com/pytorch/pytorch/issues/42305 # pylint: disable=W0223, too-many-arguments, too-many-instance-attributes -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member from typing import Tuple import torch diff --git a/deepparse/network/embedding_network.py b/deepparse/network/embedding_network.py index 13da67c8..c781b8bc 100644 --- a/deepparse/network/embedding_network.py +++ b/deepparse/network/embedding_network.py @@ -4,10 +4,6 @@ # temporary fix for _forward_unimplemented for PyTorch 1.6 https://github.com/pytorch/pytorch/issues/42305 # pylint: disable=W0223 -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - from typing import Tuple, List import torch diff --git a/deepparse/network/fasttext_seq2seq.py b/deepparse/network/fasttext_seq2seq.py index 6030d8c6..b7e7ff5f 100644 --- a/deepparse/network/fasttext_seq2seq.py +++ b/deepparse/network/fasttext_seq2seq.py @@ -1,8 +1,5 @@ # pylint: disable=too-many-arguments, duplicate-code -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member from typing import Union import torch diff --git a/deepparse/network/seq2seq.py b/deepparse/network/seq2seq.py index 3b5443ee..9f158f19 100644 --- a/deepparse/network/seq2seq.py +++ b/deepparse/network/seq2seq.py @@ -1,8 +1,5 @@ # pylint: disable=too-many-arguments -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member import os import random import warnings diff --git a/deepparse/parser/address_parser.py b/deepparse/parser/address_parser.py index 80a45a29..700e6f61 100644 --- a/deepparse/parser/address_parser.py +++ b/deepparse/parser/address_parser.py @@ -1,9 +1,5 @@ # pylint: disable=too-many-lines -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - # Pylint raise error for an inconsistent-return-statements for the retrain function # It must be due to the complex try, except else case. # pylint: disable=inconsistent-return-statements diff --git a/deepparse/parser/tools.py b/deepparse/parser/tools.py index 2cad51d6..c4b92ace 100644 --- a/deepparse/parser/tools.py +++ b/deepparse/parser/tools.py @@ -1,7 +1,3 @@ -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import math import os from typing import List, Tuple, OrderedDict diff --git a/tests/cli/test_tools.py b/tests/cli/test_tools.py index ac5c45d5..bf195597 100644 --- a/tests/cli/test_tools.py +++ b/tests/cli/test_tools.py @@ -1,4 +1,4 @@ -# pylint: disable=no-member, too-many-public-methods +# pylint: disable=too-many-public-methods # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with diff --git a/tests/converter/test_data_padding.py b/tests/converter/test_data_padding.py index b2b32595..3fda7409 100644 --- a/tests/converter/test_data_padding.py +++ b/tests/converter/test_data_padding.py @@ -1,10 +1,6 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable, too-many-public-methods -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import unittest from unittest import TestCase diff --git a/tests/metrics/test_accuracy.py b/tests/metrics/test_accuracy.py index 1fbefd37..684ed118 100644 --- a/tests/metrics/test_accuracy.py +++ b/tests/metrics/test_accuracy.py @@ -1,10 +1,6 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import unittest from unittest import TestCase diff --git a/tests/metrics/test_nll_loss.py b/tests/metrics/test_nll_loss.py index bfad334f..e80b066b 100644 --- a/tests/metrics/test_nll_loss.py +++ b/tests/metrics/test_nll_loss.py @@ -1,10 +1,6 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import unittest from unittest import TestCase diff --git a/tests/network/base.py b/tests/network/base.py index e8793ede..34e908b0 100644 --- a/tests/network/base.py +++ b/tests/network/base.py @@ -1,10 +1,6 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - # Pylint raise error for the call method mocking # pylint: disable=unnecessary-dunder-call diff --git a/tests/network/integration/base.py b/tests/network/integration/base.py index 7d689644..daff21b8 100644 --- a/tests/network/integration/base.py +++ b/tests/network/integration/base.py @@ -4,10 +4,6 @@ # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import os import pickle from tempfile import TemporaryDirectory diff --git a/tests/network/integration/test_integration_decoder.py b/tests/network/integration/test_integration_decoder.py index 7eb16b0b..b1f1359b 100644 --- a/tests/network/integration/test_integration_decoder.py +++ b/tests/network/integration/test_integration_decoder.py @@ -4,10 +4,6 @@ # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import os import pickle import unittest diff --git a/tests/network/integration/test_integration_embedding_network.py b/tests/network/integration/test_integration_embedding_network.py index 7e78fd52..8cc404e4 100644 --- a/tests/network/integration/test_integration_embedding_network.py +++ b/tests/network/integration/test_integration_embedding_network.py @@ -1,9 +1,5 @@ # pylint: disable=line-too-long -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import unittest from unittest import TestCase diff --git a/tests/network/integration/test_integration_encoder.py b/tests/network/integration/test_integration_encoder.py index ab5dec7c..43fdb1a2 100644 --- a/tests/network/integration/test_integration_encoder.py +++ b/tests/network/integration/test_integration_encoder.py @@ -4,10 +4,6 @@ # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import os import pickle import unittest diff --git a/tests/network/integration/test_integration_seq2seq_bpemb_model_cpu.py b/tests/network/integration/test_integration_seq2seq_bpemb_model_cpu.py index 39803a65..ff462e0e 100644 --- a/tests/network/integration/test_integration_seq2seq_bpemb_model_cpu.py +++ b/tests/network/integration/test_integration_seq2seq_bpemb_model_cpu.py @@ -2,10 +2,6 @@ # We also skip protected-access since we test the encoder and decoder step # pylint: disable=not-callable, protected-access -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import os import unittest from unittest import skipIf diff --git a/tests/network/integration/test_integration_seq2seq_bpemb_model_gpu.py b/tests/network/integration/test_integration_seq2seq_bpemb_model_gpu.py index 7385764c..6db1a9de 100644 --- a/tests/network/integration/test_integration_seq2seq_bpemb_model_gpu.py +++ b/tests/network/integration/test_integration_seq2seq_bpemb_model_gpu.py @@ -2,10 +2,6 @@ # We also skip protected-access since we test the encoder and decoder step # pylint: disable=not-callable, protected-access -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import os import unittest from unittest import skipIf diff --git a/tests/network/integration/test_integration_seq2seq_fasttext_model_cpu.py b/tests/network/integration/test_integration_seq2seq_fasttext_model_cpu.py index 3ba73b8c..f8545801 100644 --- a/tests/network/integration/test_integration_seq2seq_fasttext_model_cpu.py +++ b/tests/network/integration/test_integration_seq2seq_fasttext_model_cpu.py @@ -2,10 +2,6 @@ # We also skip protected-access since we test the encoder and decoder step # pylint: disable=not-callable, protected-access -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import os import unittest from unittest import skipIf diff --git a/tests/network/integration/test_integration_seq2seq_fasttext_model_gpu.py b/tests/network/integration/test_integration_seq2seq_fasttext_model_gpu.py index b591cb91..3c5cebd1 100644 --- a/tests/network/integration/test_integration_seq2seq_fasttext_model_gpu.py +++ b/tests/network/integration/test_integration_seq2seq_fasttext_model_gpu.py @@ -2,10 +2,6 @@ # We also skip protected-access since we test the encoder and decoder step # pylint: disable=not-callable, protected-access -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import os import unittest from unittest import skipIf diff --git a/tests/network/integration/test_integration_seq2seq_model_cpu.py b/tests/network/integration/test_integration_seq2seq_model_cpu.py index 98bcde7f..4fc12d0b 100644 --- a/tests/network/integration/test_integration_seq2seq_model_cpu.py +++ b/tests/network/integration/test_integration_seq2seq_model_cpu.py @@ -2,10 +2,6 @@ # We also skip protected-access since we test the encoder and decoder step # pylint: disable=not-callable, protected-access -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import os import unittest from unittest import skipIf diff --git a/tests/network/integration/test_integration_seq2seq_model_gpu.py b/tests/network/integration/test_integration_seq2seq_model_gpu.py index b1b39168..8e91a758 100644 --- a/tests/network/integration/test_integration_seq2seq_model_gpu.py +++ b/tests/network/integration/test_integration_seq2seq_model_gpu.py @@ -2,10 +2,6 @@ # We also skip protected-access since we test the encoder and decoder step # pylint: disable=not-callable, protected-access -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import unittest from unittest import skipIf from unittest.mock import patch diff --git a/tests/network/test_bpemb_seq2seq_model_cpu.py b/tests/network/test_bpemb_seq2seq_model_cpu.py index fb110477..6894cc66 100644 --- a/tests/network/test_bpemb_seq2seq_model_cpu.py +++ b/tests/network/test_bpemb_seq2seq_model_cpu.py @@ -4,10 +4,6 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - # Pylint raise error for the call method mocking # pylint: disable=unnecessary-dunder-call diff --git a/tests/network/test_bpemb_seq2seq_model_gpu.py b/tests/network/test_bpemb_seq2seq_model_gpu.py index 85dd2c22..f5009f4b 100644 --- a/tests/network/test_bpemb_seq2seq_model_gpu.py +++ b/tests/network/test_bpemb_seq2seq_model_gpu.py @@ -3,10 +3,6 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=unused-argument, protected-access, too-many-arguments, not-callable, too-many-locals -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - # Pylint raise error for the call method mocking # pylint: disable=unnecessary-dunder-call diff --git a/tests/network/test_decoder.py b/tests/network/test_decoder.py index 79427f1c..9a386273 100644 --- a/tests/network/test_decoder.py +++ b/tests/network/test_decoder.py @@ -1,7 +1,3 @@ -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - # Pylint raise error for the getitem method mocking # pylint: disable=unnecessary-dunder-call diff --git a/tests/network/test_fasttext_seq2seq_model_cpu.py b/tests/network/test_fasttext_seq2seq_model_cpu.py index 6c03e393..0b160b54 100644 --- a/tests/network/test_fasttext_seq2seq_model_cpu.py +++ b/tests/network/test_fasttext_seq2seq_model_cpu.py @@ -4,10 +4,6 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - # Pylint raise error for the call method mocking # pylint: disable=unnecessary-dunder-call diff --git a/tests/network/test_fasttext_seq2seq_model_gpu.py b/tests/network/test_fasttext_seq2seq_model_gpu.py index 307a3302..e662a0d7 100644 --- a/tests/network/test_fasttext_seq2seq_model_gpu.py +++ b/tests/network/test_fasttext_seq2seq_model_gpu.py @@ -3,10 +3,6 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=unused-argument, protected-access, too-many-arguments, not-callable, too-many-locals -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - # Pylint raise error for the call method mocking # pylint: disable=unnecessary-dunder-call diff --git a/tests/network/test_seq2seq.py b/tests/network/test_seq2seq.py index 92525f60..b22c5332 100644 --- a/tests/network/test_seq2seq.py +++ b/tests/network/test_seq2seq.py @@ -3,10 +3,6 @@ # We also skip protected-access since we test the _load_weights # pylint: disable=protected-access, unused-argument, not-callable -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import os import unittest from unittest import TestCase diff --git a/tests/parser/base.py b/tests/parser/base.py index cc104e89..23258d5f 100644 --- a/tests/parser/base.py +++ b/tests/parser/base.py @@ -4,10 +4,6 @@ # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - # Pylint raise error for the call method mocking # pylint: disable=unnecessary-dunder-call @@ -220,5 +216,4 @@ def download_pre_trained_weights(self): @classmethod def tearDownClass(cls) -> None: - # pylint: disable=no-member cls.model_weights_temp_dir.cleanup() diff --git a/tests/parser/integration/base_predict.py b/tests/parser/integration/base_predict.py index 1c4db84f..86d1c442 100644 --- a/tests/parser/integration/base_predict.py +++ b/tests/parser/integration/base_predict.py @@ -1,6 +1,5 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. -# no-member skip is so because child define the training_container in setup -# pylint: disable=not-callable, too-many-public-methods, no-member, too-many-arguments +# pylint: disable=not-callable, too-many-public-methods,too-many-arguments # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with diff --git a/tests/parser/integration/base_retrain.py b/tests/parser/integration/base_retrain.py index a8cc2a62..cdd1aeb2 100644 --- a/tests/parser/integration/base_retrain.py +++ b/tests/parser/integration/base_retrain.py @@ -1,6 +1,5 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. -# no-member skip is so because child define the training_container in setup -# pylint: disable=not-callable, too-many-public-methods, no-member, too-many-arguments +# pylint: disable=not-callable, too-many-public-methods, too-many-arguments # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with diff --git a/tests/parser/integration/test_integration_address_parser_gpu.py b/tests/parser/integration/test_integration_address_parser_gpu.py index 771bad2e..4cdba708 100644 --- a/tests/parser/integration/test_integration_address_parser_gpu.py +++ b/tests/parser/integration/test_integration_address_parser_gpu.py @@ -1,10 +1,6 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. # pylint: disable=not-callable -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - from unittest import skipIf import torch diff --git a/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py b/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py index db3d237b..f4b37369 100644 --- a/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py +++ b/tests/parser/integration/test_integration_address_parser_new_params_new_tags.py @@ -1,6 +1,5 @@ # Bug with PyTorch source code makes torch.tensor as not callable for pylint. -# no-member skip is so because child define the training_container in setup -# pylint: disable=not-callable, too-many-public-methods, no-member, too-many-arguments +# pylint: disable=not-callable, too-many-public-methods, too-many-arguments # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with diff --git a/tests/parser/test_address_parser.py b/tests/parser/test_address_parser.py index 9cf752f1..f32d30a9 100644 --- a/tests/parser/test_address_parser.py +++ b/tests/parser/test_address_parser.py @@ -1,13 +1,9 @@ # Since we use a patch as model mock we skip the unused argument error -# pylint: disable=unused-argument, no-member, too-many-public-methods, too-many-lines, too-many-arguments +# pylint: disable=unused-argument, too-many-public-methods, too-many-lines, too-many-arguments # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - # Pylint raise error for from torch import device # pylint: disable=no-name-in-module diff --git a/tests/parser/test_address_parser_retrain_api.py b/tests/parser/test_address_parser_retrain_api.py index f75c71fc..ba5201e9 100644 --- a/tests/parser/test_address_parser_retrain_api.py +++ b/tests/parser/test_address_parser_retrain_api.py @@ -4,10 +4,6 @@ # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import os import platform import unittest diff --git a/tests/parser/test_address_parser_test_api.py b/tests/parser/test_address_parser_test_api.py index 5e36127a..eb7a2dcb 100644 --- a/tests/parser/test_address_parser_test_api.py +++ b/tests/parser/test_address_parser_test_api.py @@ -1,10 +1,6 @@ # Since we use a patch as model mock we skip the unused argument error # pylint: disable=unused-argument, too-many-arguments -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import unittest from unittest import skipIf from unittest.mock import patch, call diff --git a/tests/parser/test_formatted_parsed_address.py b/tests/parser/test_formatted_parsed_address.py index 226b8f9d..6bd935ec 100644 --- a/tests/parser/test_formatted_parsed_address.py +++ b/tests/parser/test_formatted_parsed_address.py @@ -1,4 +1,4 @@ -# pylint: disable=no-member, too-many-public-methods +# pylint: disable=too-many-public-methods # Pylint raise error for the repr method mocking # pylint: disable=unnecessary-dunder-call diff --git a/tests/parser/test_tools.py b/tests/parser/test_tools.py index 9ec90b89..d3f1aa19 100644 --- a/tests/parser/test_tools.py +++ b/tests/parser/test_tools.py @@ -3,10 +3,6 @@ # Pylint error for TemporaryDirectory ask for with statement # pylint: disable=consider-using-with -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import os import unittest diff --git a/tests/parser/test_user_fromatted_parsed_address.py b/tests/parser/test_user_fromatted_parsed_address.py index 1f78ff0b..880abf92 100644 --- a/tests/parser/test_user_fromatted_parsed_address.py +++ b/tests/parser/test_user_fromatted_parsed_address.py @@ -1,4 +1,4 @@ -# pylint: disable=no-member, too-many-public-methods +# pylint: disable=too-many-public-methods # Pylint raise error for the call method mocking # pylint: disable=unnecessary-dunder-call diff --git a/tests/tools.py b/tests/tools.py index 52d01ee2..c613555e 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -1,9 +1,5 @@ # pylint: disable=too-many-arguments, self-assigning-variable -# Pylint raise error for torch.tensor, torch.zeros, ... as a no-member event -# if not the case. -# pylint: disable=no-member - import pickle from typing import List From ad14e047a8229ca78aaea0c4f87b0fa25ccef740 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 12 Aug 2022 14:28:38 -0400 Subject: [PATCH 086/195] add docker image --- .github/workflows/docker-publish.yml | 41 ++++++++++++++++++++++++++++ .github/workflows/docker.yml | 20 ++++++++++++++ CHANGELOG.md | 3 +- Dockerfile | 33 ++++++++++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/docker-publish.yml create mode 100644 .github/workflows/docker.yml create mode 100644 Dockerfile diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 00000000..ece84a84 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,41 @@ +name: Create and publish a Docker image as a Github Package + +on: + release: + types: [ created ] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..3331847f --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,20 @@ +name: Docker Image CI + +on: + push: + pull_request: + release: + types: [ created ] + schedule: + # Run the tests at 00:00 each week on sunday + - cron: '0 0 * * 0' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Build the Docker image + run: | + docker build . --file Dockerfile --tag poutyne:$(date +%s) \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 83d594c8..36742132 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -255,4 +255,5 @@ - Add `save_model_weights` method to `AddressParser` to save model weights (PyTorch state dictionary) - Improve CI -- Added verbose flag for test to activate or deactivate the test verbosity (it override the AddressParser verbosity) \ No newline at end of file +- Added verbose flag for test to activate or deactivate the test verbosity (it override the AddressParser verbosity) +- Add Docker image \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..9f35a946 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +# syntax = docker/dockerfile:experimental +FROM pytorch/pytorch:1.11.0-cuda11.3-cudnn8-runtime + +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN pip3 install --no-cache-dir --upgrade pip +RUN CFLAGS="-g0 -Os -DNDEBUG -Wl,--strip-all -I/usr/include:/usr/local/include -L/usr/lib:/usr/local/lib" \ + DEEPPARSE_RELEASE_BUILD=1 \ + pip3 install --no-cache-dir \ + --compile \ + --global-option=build_ext \ + --global-option="-j 4" \ + --no-deps -U git+https://github.com/GRAAL-Research/deepparse.git@stable + +RUN find /opt/conda/lib/ -follow -type f -name '*.a' -delete \ + && find /opt/conda/lib/ -follow -type f -name '*.pyc' -delete \ + && find /opt/conda/lib/ -follow -type f -name '*.txt' -delete \ + && find /opt/conda/lib/ -follow -type f -name '*.mc' -delete \ + && find /opt/conda/lib/ -follow -type f -name '*.js.map' -delete \ + && find /opt/conda/lib/ -name '*.c' -delete \ + && find /opt/conda/lib/ -name '*.pxd' -delete \ + && find /opt/conda/lib/ -follow -type f -name '*.md' -delete \ + && find /opt/conda/lib/ -follow -type f -name '*.png' -delete \ + && find /opt/conda/lib/ -follow -type f -name '*.jpg' -delete \ + && find /opt/conda/lib/ -follow -type f -name '*.jpeg' -delete \ + && find /opt/conda/lib/ -name '*.pyd' -delete \ + && find /opt/conda/lib/ -name '__pycache__' | xargs rm -r + +ENV PATH /opt/conda/bin:$PATH \ No newline at end of file From 1a32a6471656aeacfd001a083eef03b67e221c9a Mon Sep 17 00:00:00 2001 From: davebulaval Date: Sun, 14 Aug 2022 16:45:55 -0400 Subject: [PATCH 087/195] formating --- .github/workflows/codeql-analysis.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e29585dd..622b5b33 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -24,14 +24,14 @@ jobs: language: [ 'python' ] steps: - - name: Checkout repository - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v3 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language } - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language } + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 From 79d38d2d6f41738045524415a1fd260d0f113834 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Mon, 15 Aug 2022 09:27:51 -0400 Subject: [PATCH 088/195] formated README --- README.md | 76 +++++++++++++++++------- examples/fine_tuning_with_csv_dataset.py | 8 ++- 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index ff5d7c34..90922b59 100644 --- a/README.md +++ b/README.md @@ -155,25 +155,36 @@ parsed_address = address_parser("350 rue des Lilas Ouest Québec Québec G1L 1B6 # or multiple addresses parsed_address = address_parser( - ["350 rue des Lilas Ouest Québec Québec G1L 1B6", - "350 rue des Lilas Ouest Québec Québec G1L 1B6"]) + [ + "350 rue des Lilas Ouest Québec Québec G1L 1B6", + "350 rue des Lilas Ouest Québec Québec G1L 1B6", + ] +) # or multinational addresses # Canada, US, Germany, UK and South Korea parsed_address = address_parser( - ["350 rue des Lilas Ouest Québec Québec G1L 1B6", "777 Brockton Avenue, Abington MA 2351", - "Ansgarstr. 4, Wallenhorst, 49134", "221 B Baker Street", "서울특별시 종로구 사직로3길 23"]) + [ + "350 rue des Lilas Ouest Québec Québec G1L 1B6", + "777 Brockton Avenue, Abington MA 2351", + "Ansgarstr. 4, Wallenhorst, 49134", + "221 B Baker Street", + "서울특별시 종로구 사직로3길 23", + ] +) # you can also get the probability of the predicted tags -parsed_address = address_parser("350 rue des Lilas Ouest Québec Québec G1L 1B6", - with_prob=True) +parsed_address = address_parser( + "350 rue des Lilas Ouest Québec Québec G1L 1B6", with_prob=True +) # Print the parsed address print(parsed_address) # or using one of our dataset container -addresses_to_parse = CSVDatasetContainer("./a_path.csv", column_names=["address_column_name"], - is_training_container=False) +addresses_to_parse = CSVDatasetContainer( + "./a_path.csv", column_names=["address_column_name"], is_training_container=False +) address_parser(addresses_to_parse) ``` @@ -203,7 +214,10 @@ You can also use our cli to parse addresses using: ```python address_parser = AddressParser( - model_type="bpemb", device=0, path_to_retrained_model="path/to/retrained/bpemb/model.p") + model_type="bpemb", + device=0, + path_to_retrained_model="path/to/retrained/bpemb/model.p", +) address_parser("350 rue des Lilas Ouest Québec Québec G1L 1B6") ``` @@ -219,20 +233,31 @@ for a complete example using CSV. address_parser = AddressParser(model_type="fasttext", device=0) address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size=8) - ``` One can also freeze some layers to speed up the training using the ``layers_to_freeze`` parameter. ```python -address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size=8, layers_to_freeze="seq2seq") +address_parser.retrain( + training_container, + train_ratio=0.8, + epochs=5, + batch_size=8, + layers_to_freeze="seq2seq", +) ``` Or you can also give a specific name to the retrained model. This name will be use as the model name (for print and class name) when reloading it. ```python -address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size=8, name_of_the_retrain_parser="MyNewParser") +address_parser.retrain( + training_container, + train_ratio=0.8, + epochs=5, + batch_size=8, + name_of_the_retrain_parser="MyNewParser", +) ``` ### Retrain a Model With an Attention Mechanism @@ -241,10 +266,11 @@ address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size ```python # We will retrain the fasttext version of our pretrained model. -address_parser = AddressParser(model_type="fasttext", device=0, attention_mechanism=True) +address_parser = AddressParser( + model_type="fasttext", device=0, attention_mechanism=True +) address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size=8) - ``` ### Retrain a Model With New Tags @@ -252,11 +278,14 @@ address_parser.retrain(training_container, train_ratio=0.8, epochs=5, batch_size > See [here](https://github.com/GRAAL-Research/deepparse/blob/master/examples/retrain_with_new_prediction_tags.py) for a complete example. ```python - address_components = {"ATag": 0, "AnotherTag": 1, "EOS": 2} -address_parser.retrain(training_container, train_ratio=0.8, epochs=1, batch_size=128, - prediction_tags=address_components) - +address_parser.retrain( + training_container, + train_ratio=0.8, + epochs=1, + batch_size=128, + prediction_tags=address_components, +) ``` ### Retrain a Seq2Seq Model From Scratch @@ -265,11 +294,14 @@ address_parser.retrain(training_container, train_ratio=0.8, epochs=1, batch_size > a complete example. ```python - seq2seq_params = {"encoder_hidden_size": 512, "decoder_hidden_size": 512} -address_parser.retrain(training_container, train_ratio=0.8, epochs=1, batch_size=128, - seq2seq_params=seq2seq_params) - +address_parser.retrain( + training_container, + train_ratio=0.8, + epochs=1, + batch_size=128, + seq2seq_params=seq2seq_params, +) ``` ### Download Our Models diff --git a/examples/fine_tuning_with_csv_dataset.py b/examples/fine_tuning_with_csv_dataset.py index c1c5b4d6..7291c27d 100644 --- a/examples/fine_tuning_with_csv_dataset.py +++ b/examples/fine_tuning_with_csv_dataset.py @@ -17,11 +17,13 @@ # Now let's create a training and test container. training_container = CSVDatasetContainer( os.path.join(saving_dir, training_dataset_name + "." + file_extension), - column_names=['Address', 'Tags'], - separator=',', + column_names=["Address", "Tags"], + separator=",", ) test_container = CSVDatasetContainer( - os.path.join(saving_dir, test_dataset_name + "." + file_extension), column_names=['Address', 'Tags'], separator=',' + os.path.join(saving_dir, test_dataset_name + "." + file_extension), + column_names=["Address", "Tags"], + separator=",", ) # We will retrain the fasttext version of our pretrained model. From 1763fdb7a7b17e0464d7e74ff993af7c05227c66 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 19 Aug 2022 10:40:24 -0400 Subject: [PATCH 089/195] update changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36742132..2b3117aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -256,4 +256,8 @@ - Add `save_model_weights` method to `AddressParser` to save model weights (PyTorch state dictionary) - Improve CI - Added verbose flag for test to activate or deactivate the test verbosity (it override the AddressParser verbosity) -- Add Docker image \ No newline at end of file +- Add Docker image +- Add `val_dataset` to retrain API to allow the use of a specific val dataset for training +- Remove deprecated `download_from_url` function +- Remove deprecated `dataset_container` argument +- Add Zenodo DOI \ No newline at end of file From 18be17e1ce83a349c243140154cf128090cfdc12 Mon Sep 17 00:00:00 2001 From: davebulaval Date: Fri, 19 Aug 2022 11:28:13 -0400 Subject: [PATCH 090/195] merge uk example and fixes to doc --- CHANGELOG.md | 4 +- docs/source/_static/img/address_parsing.png | Bin 0 -> 25759 bytes docs/source/examples/fine_tuning.rst | 14 +- .../examples/fine_tuning_with_csv_dataset.rst | 12 +- docs/source/examples/parse_addresses.rst | 6 +- .../examples/parse_addresses_with_cli.rst | 2 +- .../examples/retrain_attention_model.rst | 8 +- .../retrain_with_new_prediction_tags.rst | 8 +- .../retrain_with_new_seq2seq_params.rst | 8 +- .../examples/retrained_model_parsing.rst | 2 +- .../examples/single_country_retrain.rst | 162 +++++++ docs/source/index.rst | 1 + examples/fine_tuning.py | 4 +- examples/fine_tuning_with_csv_dataset.py | 4 +- examples/retrain_attention_model.py | 4 +- examples/retrain_with_new_prediction_tags.py | 4 +- examples/retrain_with_new_seq2seq_params.py | 4 +- examples/single_country_retrain.ipynb | 437 ++++++++++++++++++ 18 files changed, 643 insertions(+), 41 deletions(-) create mode 100644 docs/source/_static/img/address_parsing.png create mode 100644 docs/source/examples/single_country_retrain.rst create mode 100644 examples/single_country_retrain.ipynb diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b3117aa..7bfcb9c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -260,4 +260,6 @@ - Add `val_dataset` to retrain API to allow the use of a specific val dataset for training - Remove deprecated `download_from_url` function - Remove deprecated `dataset_container` argument -- Add Zenodo DOI \ No newline at end of file +- Add Zenodo DOI +- Fixed error and docs +- Added the UK retrain example \ No newline at end of file diff --git a/docs/source/_static/img/address_parsing.png b/docs/source/_static/img/address_parsing.png new file mode 100644 index 0000000000000000000000000000000000000000..93850ebab7842ce8f1781e5966270f28b5bdefee GIT binary patch literal 25759 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