Repository: France-Travail/words_n_fun (language: python)

Issue:
words_n_fun (update): changed package name to words_n_fun



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/wnf_publish.yaml b/.github/workflows/wnf_publish.yaml
new file mode 100644
index 0000000..9212f88
--- /dev/null
+++ b/.github/workflows/wnf_publish.yaml
@@ -0,0 +1,39 @@
+name: Publish to Pypi
+on:
+  workflow_dispatch:
+    inputs:
+      version:
+        description: 'Version to upload to pypi'
+        required: true
+      pypi_repo:
+        description: 'Repo to upload to (testpypi or pypi)'
+        default: 'testpypi'
+        required: true
+
+jobs:
+  publish:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v3
+    - uses: actions/setup-python@v3
+      with:
+        python-version: '3.8'
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install build
+    - name: Build package
+      run: python -m build
+    - name: Publish package to TestPyPI
+      uses: pypa/gh-action-pypi-publish@v1.4.1
+      with:
+        user: __token__
+        password: ${{ secrets.TEST_PYPI_TOKEN }}
+        repository_url: https://test.pypi.org/legacy/
+      if: ${{ github.event.inputs.pypi_repo == 'testpypi' }}
+    - name: Publish package to PyPI
+      uses: pypa/gh-action-pypi-publish@v1.4.1
+      with:
+        user: __token__
+        password: ${{ secrets.PYPI_TOKEN }}
+      if: ${{ github.event.inputs.pypi_repo == 'pypi' }}
diff --git a/Makefile b/Makefile
index 877fad1..b883cbb 100644
--- a/Makefile
+++ b/Makefile
@@ -1,16 +1,16 @@
 ## Makefile - Outils facilitant la gestion de l'environnement
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -20,11 +20,11 @@
 
 NAME_VENV=venv_wnf
 
-create-virtualenv: ## Création d'un environnement virtuel
+create-virtualenv: ## Creation of a virtual environment
 	pip install virtualenv;\
 	python -m venv $(NAME_VENV)
 
-init-local-env: ## Initialisation de l'environnement de dev en local
+init-local-env: ## Initialization of the local dev environment
 ifndef VIRTUAL_ENV
 	$(error Environnement virtuel python non positionné !)
 endif
@@ -35,42 +35,36 @@ endif
 # Tests
 ####################################################
 
-test:
-	nosetests -c nose_setup_coverage.cfg tests
+test: ## Launch python tests
+ifndef VIRTUAL_ENV
+	$(error Python virtual environment not activated !)
+endif
+	nosetests -c nose_setup_coverage.cfg tests --exe # https://stackoverflow.com/questions/1457104/nose-unable-to-find-tests-in-ubuntu
 
 
 ####################################################
 # Code quality
 ####################################################
 
-quality: black isort flake8 ## Lance les outils d'aide à la qualité du code
+quality: black isort flake8 ## Launch the code quality tools
 
-black: ## Formatter
+black: ## Code formatter
 ifndef VIRTUAL_ENV
-	$(error Environnement virtuel python non positionné !)
+	$(error Python virtual environment not activated !)
 endif
 	pip install black;\
-	black -l 140 -t py37 -S .
+	black -l 140 -t py38 -S .
 
-isort: ## Formatter pour les imports
+isort: ## Utility to automatically sort imports
 ifndef VIRTUAL_ENV
-	$(error Environnement virtuel python non positionné !)
+	$(error Python virtual environment not activated !)
 endif
 	pip install isort;\
-	isort --skip venv_words_n_fun -rc .
+	isort --skip venv_{{package_name}} -rc .
 
-flake8: ## Mesure la qualité du code
+flake8: ## Guide Enforcement tool
 ifndef VIRTUAL_ENV
-	$(error Environnement virtuel python non positionné !)
+	$(error Python virtual environment not activated !)
 endif
 	pip install flake8;\
-	flake8 --exclude=venv_words_n_fun .
-
-setup_git_pre_commit_hook: ## Initialise hook precommit quality
-ifndef VIRTUAL_ENV
-	$(error Environnement virtuel python non positionné !)
-endif
-	pip install pre-commit==2.3.0
-	pre-commit install
-	pip install seed-isort-config==2.1.1
-	-seed-isort-config
+	flake8 --exclude=venv_{{package_name}} . # add "|| exit 0" to avoid error
diff --git a/README.md b/README.md
index 6c7c671..863efa2 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,9 @@
 
 ---
 
-## Why ? 
+## Why ?
 
-The purpose of this project is two folds: 
+The purpose of this project is two folds:
 1. To normalize tools and how tos of semantic analysis projects
 2. To offer end to end pipelines to speed up the time to market of NLP services
 
@@ -15,8 +15,8 @@ The purpose of this project is two folds:
 
 This package used to contain two main parts:
 1. Preprocessing : to speed up the application of numerous preprocessing routines to text data
-2. Modelling : to speed up the developpement of NLP machine learning models 
-The later has been refactored into what is now called the NLP framework. 
+2. Modelling : to speed up the developpement of NLP machine learning models
+The later has been refactored into what is now called the NLP framework.
 
 ### Preprocessing
 
@@ -35,15 +35,15 @@ This feature is controled by the utils.data_agnostic function.
 
 ##### utils.py
 
-Contains utils functions and tools, mainly the data agnostic feature. 
+Contains utils functions and tools, mainly the data agnostic feature.
 
 ##### basic.py
 
-Contains the main data transformation functions. 
+Contains the main data transformation functions.
 
 ##### api.py
 
-Contains the main entry point of the package controlling how one could create an end to end transformation pipeline. 
+Contains the main entry point of the package controlling how one could create an end to end transformation pipeline.
 
 
 ---
@@ -56,7 +56,7 @@ Contains the main entry point of the package controlling how one could create an
 
 - Python 3.X (tests are ran on python 3.8)
 - Set up module dependencies : `pip install -r requirements.txt`
-- Set up the actual module : `pip install -e pe_semantic` or `python setup.py develop` 
+- Set up the actual module : `pip install -e words_n_fun` or `python setup.py develop`
 - Download nltk stopwords data:
 ```python
 import nltk
@@ -71,7 +71,7 @@ Roughly speaking, her is what a basic use case should look like:
 
 ```python
  # Module import
-from pe_semantic.preprocessing import api
+from words_n_fun.preprocessing import api
 
 # Definition of the desired transformation pipeline
 pipeline = ['remove_non_string', 'get_true_spaces', 'to_lower', 'remove_punct',
@@ -106,7 +106,7 @@ output_list = preprocessor.transform(input_list)
 
 ## Best Practices & guidelines
 
-Contributors must try their best to follow these mainstream guidelines : 
+Contributors must try their best to follow these mainstream guidelines :
 1. Module management https://docs.python.org/fr/2/tutorial/modules.html
 2. Python development https://github.com/google/styleguide/blob/gh-pages/pyguide.md, https://www.python.org/dev/peps/pep-0008/
 3. All the preprocessing functions must take pd.Series data as input. The built-in utils.data_agnostic decorator allows the user to send different types of data but the actual function carrying out these transformation should be built around pd.Series.
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..6234b31
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,13 @@
+# TODO list
+
+
+### Overall
+
+- Translate some French into English (check tests files)
+- Fix some flake8 warnings
+
+
+### Lemmatizer
+
+- Overall rework must be done
+- Users should be able to use any spacy model, not only a French one
diff --git a/nose_setup_coverage.cfg b/nose_setup_coverage.cfg
index 9665075..872f7a3 100644
--- a/nose_setup_coverage.cfg
+++ b/nose_setup_coverage.cfg
@@ -1,22 +1,22 @@
 [nosetests]
 verbosity=1
 detailed-errors=1
-with-xunit= 1
+with-xunit=1
 with-coverage=1
 
 # Folder for coverage
-cover-package=pe_semantic
+cover-package=words_n_fun
 
-# inclure tous les fichiers python
-# FIXME : Certains fichiers restent exclus du rapport de coverage
+# Include all python file
+# TODO: Some files are still excluded from coverage report
 cover-inclusive=1
 
-#effacer les rapports générés précédement
+# Remove previous reports
 cover-erase=1
 
-#générer le fichier html
-cover-html= 1
-cover-html-dir= output_coverage_tests/cover
+# Generate HTML report
+cover-html=1
+cover-html-dir=output_coverage_tests/cover
 
-#le minimum de pourcentage de couverture défini par utilisateur
+# Minimum coverage percentage
 cover-min-percentage=80
diff --git a/requirements.txt b/requirements.txt
index 41ab410..0b5f031 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,7 +12,6 @@ tqdm==4.62.2
 requests==2.23.0
 
 # Optionnals - code quality & cie
-mypy==0.782
 flake8==3.7.9
 black==19.10b0
 isort==4.3.21
@@ -24,4 +23,5 @@ coverage==5.3
 markupsafe==2.0.1  # BUG FIX -> https://github.com/aws/aws-sam-cli/issues/3661
 Cython==0.29.24
 spacy==3.2.4
-fr-core-news-sm==3.2.0
+# The following line downloads a spacy model. It can be commented, especially if you don't have any internet access.
+https://github.com/explosion/spacy-models/releases/download/fr_core_news_sm-3.2.0/fr_core_news_sm-3.2.0-py3-none-any.whl
diff --git a/setup.py b/setup.py
index 8de5f5d..62693b6 100644
--- a/setup.py
+++ b/setup.py
@@ -21,26 +21,26 @@
 with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'version.txt'), 'r') as version_file:
     version = version_file.read().strip()
 
-version = os.getenv('VERSION') or version+'-local'
+version = os.getenv('VERSION') or f"{version}-local"
 
 # Setup
 setup(
     name='words_n_fun',
     version=version,
-    packages=['pe_semantic', 'pe_semantic.preprocessing'],
+    packages=['words_n_fun', 'words_n_fun.preprocessing'],
     license='Aucune',
     author='Agence Data Services PE Nantes',
     description="Semantic library of the Data Services agency",
     platforms=['windows', 'linux'],
     package_data={
-        'pe_semantic': ['configs/*.json', 'nltk_data/corpora/stopwords/french']
+        'words_n_fun': ['configs/*.json', 'nltk_data/corpora/stopwords/french']
     },
     include_package_data=True,
     install_requires=[
         'pandas==1.3.5',
         'numpy==1.19.5',
         'nltk>=3.4.5,<3.6',
-        'tqdm==4.62.2',  #https://github.com/tqdm/tqdm/issues/780
+        'tqdm==4.62.2',  # https://github.com/tqdm/tqdm/issues/780
         'simplejson>=3.17.0,<3.17.3',
         'requests>=2.23.0,<2.25.1',
         'ftfy>=5.8,<6.0',
@@ -48,4 +48,5 @@
     extras_require={
         "lemmatizer": ["spacy==3.2.4", "markupsafe==2.0.1", "Cython==0.29.24", "fr-core-news-sm==3.2.0"]
     }
+    # pip install words_n_fun || pip install words_n_fun[lemmatizer]
 )
diff --git a/tutorial/Notebook_Preprocessing.ipynb b/tutorial/Notebook_Preprocessing.ipynb
index 751c811..cc96441 100644
--- a/tutorial/Notebook_Preprocessing.ipynb
+++ b/tutorial/Notebook_Preprocessing.ipynb
@@ -70,7 +70,7 @@
     "\n",
     "**GIT** : TO BE DEFINED\n",
     "\n",
-    "**Entrypoint** : pe_semantic.preprocessing.api"
+    "**Entrypoint** : words_n_fun.preprocessing.api"
    ]
   },
   {
@@ -100,7 +100,7 @@
     "import sys\n",
     "import os\n",
     "\n",
-    "from pe_semantic.preprocessing import api as preprocessing"
+    "from words_n_fun.preprocessing import api as preprocessing"
    ]
   },
   {
diff --git a/pe_semantic/__init__.py b/words_n_fun/__init__.py
similarity index 100%
rename from pe_semantic/__init__.py
rename to words_n_fun/__init__.py
diff --git a/pe_semantic/configs/pipeline_usage_order.json b/words_n_fun/configs/pipeline_usage_order.json
similarity index 100%
rename from pe_semantic/configs/pipeline_usage_order.json
rename to words_n_fun/configs/pipeline_usage_order.json
diff --git a/pe_semantic/nltk_data/corpora/stopwords/french b/words_n_fun/nltk_data/corpora/stopwords/french
similarity index 100%
rename from pe_semantic/nltk_data/corpora/stopwords/french
rename to words_n_fun/nltk_data/corpora/stopwords/french
diff --git a/pe_semantic/preprocessing/__init__.py b/words_n_fun/preprocessing/__init__.py
similarity index 100%
rename from pe_semantic/preprocessing/__init__.py
rename to words_n_fun/preprocessing/__init__.py
diff --git a/pe_semantic/preprocessing/api.py b/words_n_fun/preprocessing/api.py
similarity index 98%
rename from pe_semantic/preprocessing/api.py
rename to words_n_fun/preprocessing/api.py
index 290bbe4..e27f8c8 100644
--- a/pe_semantic/preprocessing/api.py
+++ b/words_n_fun/preprocessing/api.py
@@ -2,17 +2,17 @@
 
 ## Main API of the project
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -37,8 +37,8 @@
 import pandas as pd
 from typing import Union
 
-from pe_semantic import utils
-from pe_semantic.preprocessing import basic
+from words_n_fun import utils
+from words_n_fun.preprocessing import basic
 
 
 # Get logger
@@ -47,7 +47,7 @@
 logger = logging.getLogger(__name__)
 
 # Dict of all the available preprocessing features
-# All these transformations are one to one (one input will lead to only input) such that these 
+# All these transformations are one to one (one input will lead to only input) such that these
 # transformations can be chained while preserving the input dataset shape
 # Some advices are specified in configs/pipeline_usage_order.json about how to order these transformations
 # They also act as warnings when some sequences of transformations might have some unexpected results
@@ -55,17 +55,17 @@
     'notnull': basic.notnull,  # Replaces null values by an empty character
     'remove_non_string': basic.remove_non_string,  # Replaces all non strings by an empty character
     'get_true_spaces': basic.get_true_spaces,  # Replaces all whitespaces by a single space
-    'remove_accents': basic.remove_accents,  # Removes all accents and special characters (ç..) 
+    'remove_accents': basic.remove_accents,  # Removes all accents and special characters (ç..)
     'remove_stopwords': functools.partial(basic.remove_stopwords, opt='all'),  # Removes stopwords
     'trim_string': basic.trim_string,  # Trims spaces: multiple spaces become one
     'remove_leading_and_ending_spaces': basic.remove_leading_and_ending_spaces,  # Removes leading and trailing spaces
     'remove_punct': functools.partial(basic.remove_punct, del_parenthesis=True, replacement_char=' '),  # Replaces all non alpha-numeric characters by spaces
     'remove_punct_except_parenthesis': functools.partial(basic.remove_punct, del_parenthesis=False, replacement_char=' '),   # Replaces all non alpha-numeric characters by spaces EXCEPT parenthesis and slashes
-    'pe_matching': basic.pe_matching,  # Specific one-to-one tokens replacements 
+    'pe_matching': basic.pe_matching,  # Specific one-to-one tokens replacements
     'to_lower': functools.partial(basic.to_lower, threshold_nb_chars=0),  # Transforms the string to lower case
-    'to_lower_except_singleletters': functools.partial(basic.to_lower, threshold_nb_chars=2),  # Transforms longer than 2 characters string to lower cases 
+    'to_lower_except_singleletters': functools.partial(basic.to_lower, threshold_nb_chars=2),  # Transforms longer than 2 characters string to lower cases
     'remove_numeric': functools.partial(basic.remove_numeric, replacement_char=' '),  # Replaces numeric strings by a space
-    'remove_gender_synonyms': basic.remove_gender_synonyms,  # [French] Removes gendered synonyms 
+    'remove_gender_synonyms': basic.remove_gender_synonyms,  # [French] Removes gendered synonyms
     'lemmatize': basic.lemmatize,  # Lemmatizes the document
     'stemmatize': basic.stemmatize,  # Stemmatizes the words of the document
     'add_point': basic.add_point,  # Adds a dot at the end of each line
@@ -83,18 +83,18 @@
 
 
 class PreProcessor():
-    '''Class PreProcessor: 
+    '''Class PreProcessor:
     This class implements fit & transform methods and can therefore be used
     to insert a preprocessing pipeline into a Sklearn pipeline
     '''
 
-    def __init__(self, pipeline: Union[list, None] = DEFAULT_PIPELINE, prefered_column: str = 'docs', 
-                 modify_data: bool = True, chunksize: int = 0, first_row: str = 'header', 
+    def __init__(self, pipeline: Union[list, None] = DEFAULT_PIPELINE, prefered_column: str = 'docs',
+                 modify_data: bool = True, chunksize: int = 0, first_row: str = 'header',
                  columns: list = ['docs', 'tags'], sep: str = ',', nrows: int = 0, **pandas_args) -> None:
         '''Class constructor
         The purpose of a lot of these arguments are to handle the case when the input of the transform method is a path to
         a csv file. While handy, this use case is not advised.
-        
+
         Kwargs:
             pipeline (list): List of transformations to apply (from the USAGE dict) (default: DEFAULT_PIPELINE)
             prefered_column (str): Default column name to consider as the document container when working with a pandas dataframe or csv file (default: 'docs')
@@ -148,7 +148,7 @@ def transform(self, docs: Union[str, list, np.ndarray, pd.Series, pd.DataFrame])
                                    nrows=self.nrows, **self.pandas_args)
 
 
-def get_preprocessor(pipeline: list = DEFAULT_PIPELINE, prefered_column: str = 'docs', modify_data: bool = True, 
+def get_preprocessor(pipeline: list = DEFAULT_PIPELINE, prefered_column: str = 'docs', modify_data: bool = True,
                      chunksize: int = 0, first_row: str = 'header', columns: list = ['docs', 'tags'], sep: str = ',',
                      nrows: int = 0, **pandas_args) -> PreProcessor:
     '''Retourne une instance de PreProcessor
@@ -172,7 +172,7 @@ def get_preprocessor(pipeline: list = DEFAULT_PIPELINE, prefered_column: str = '
                         nrows=nrows, **pandas_args)
 
 
-def preprocess_pipeline(docs: Union[str, list, np.ndarray, pd.Series, pd.DataFrame], 
+def preprocess_pipeline(docs: Union[str, list, np.ndarray, pd.Series, pd.DataFrame],
                         pipeline: list = DEFAULT_PIPELINE, prefered_column: str = 'docs',
                         modify_data: bool = True, chunksize: int = 0, first_row: str = 'header',
                         columns: list = ['docs', 'tags'], sep: str = ',', nrows: int = 0,
diff --git a/pe_semantic/preprocessing/basic.py b/words_n_fun/preprocessing/basic.py
similarity index 94%
rename from pe_semantic/preprocessing/basic.py
rename to words_n_fun/preprocessing/basic.py
index 9b573a0..e4cf2d8 100644
--- a/pe_semantic/preprocessing/basic.py
+++ b/words_n_fun/preprocessing/basic.py
@@ -2,17 +2,17 @@
 
 ## Main funtions of the preprocessing API
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -22,13 +22,13 @@
 # - remove_non_string -> Replaces all non strings by an empty character
 # - get_true_spaces -> Replaces all whitespaces by a single space
 # - to_lower -> Transforms the string to lower case
-# - pe_matching -> Specific one-to-one tokens replacements 
+# - pe_matching -> Specific one-to-one tokens replacements
 # - remove_punct -> Replaces all non alpha-numeric characters by spaces
 # - trim_string -> Trims spaces at the beginning and ending of the string (multiple spaces become one)
 # - remove_numeric -> Replaces numeric strings by a space
 # - remove_stopwords -> Removes stopwords
-# - remove_accents -> Removes all accents and special characters (ç..) 
-# - remove_gender_synonyms -> [French] Removes gendered synonyms 
+# - remove_accents -> Removes all accents and special characters (ç..)
+# - remove_gender_synonyms -> [French] Removes gendered synonyms
 # - lemmatize -> Lemmatizes the document
 # - stemmatize -> Stemmatizes the words of the document
 # - add_point -> Adds a dot at the end of each line
@@ -44,11 +44,11 @@
 from typing import Union, List
 from nltk.stem.snowball import FrenchStemmer
 
-from pe_semantic import utils
-from pe_semantic import CustomTqdm as tqdm
-from pe_semantic.preprocessing import stopwords
-from pe_semantic.preprocessing import lemmatizer
-from pe_semantic.preprocessing import synonym_malefemale_replacement
+from words_n_fun import utils
+from words_n_fun import CustomTqdm as tqdm
+from words_n_fun.preprocessing import stopwords
+from words_n_fun.preprocessing import lemmatizer
+from words_n_fun.preprocessing import synonym_malefemale_replacement
 
 
 tqdm.pandas()
@@ -65,7 +65,7 @@ def notnull(docs: pd.Series) -> pd.Series:
 
     Args:
         docs (pd.Series): Documents to process
-    
+
     Returns:
         pd.Series: Modified documents
     '''
@@ -79,7 +79,7 @@ def remove_non_string(docs: pd.Series) -> pd.Series:
 
     Args:
         docs (pd.Series): Documents to process
-    
+
     Returns:
         pd.Series: Modified documents
     '''
@@ -94,7 +94,7 @@ def get_true_spaces(docs: pd.Series) -> pd.Series:
 
     Args:
         docs (pd.Series): Documents to process
-    
+
     Returns:
         pd.Series: Modified documents
     '''
@@ -111,7 +111,7 @@ def to_lower(docs: pd.Series, threshold_nb_chars: int = 0) -> pd.Series:
         docs (pd.Series): Documents to process
     Kwargs:
         threshold_nb_chars (int): Minimum number of characters for a token to be transformed to lowercase (def=0).
-    
+
     Returns:
         pd.Series: Modified documents
     '''
@@ -126,18 +126,18 @@ def to_lower(docs: pd.Series, threshold_nb_chars: int = 0) -> pd.Series:
 @utils.data_agnostic
 @utils.regroup_data_series
 def pe_matching(docs: pd.Series) -> pd.Series:
-    '''Specific one-to-one tokens replacements 
+    '''Specific one-to-one tokens replacements
     For instance 'Permis b' => 'Permisb'
 
     Args:
         docs (pd.Series): Documents to process
-    
+
     Returns:
         pd.Series: Modified documents
     '''
     logger.debug('Calling basic.pe_matching')
     # One can add more rules here
-    regex = utils.get_regex_match_words(['(permis)\s+(b)'], case_insensitive=True, words_as_regex=True) 
+    regex = utils.get_regex_match_words(['(permis)\s+(b)'], case_insensitive=True, words_as_regex=True)
     docs = docs.str.replace(regex, r'\2\3')
     return docs
 
@@ -170,13 +170,13 @@ def trim_string(docs: pd.Series) -> pd.Series:
 
     Args:
         docs (pd.Series): Documents to process
-    
+
     Returns:
         pd.Series: Modified documents
     '''
     logger.debug('Calling basic.trim_string')
     # TODO: better way ?
-    docs = docs.str.replace(r'[\t\f\v ]{2,}', ' ') 
+    docs = docs.str.replace(r'[\t\f\v ]{2,}', ' ')
     docs = remove_leading_and_ending_spaces(docs)
     return docs
 
@@ -188,7 +188,7 @@ def remove_leading_and_ending_spaces(docs: pd.Series) -> pd.Series:
 
     Args:
         docs (pd.Series): Documents to process
-    
+
     Returns:
         pd.Series: Modified documents
     '''
@@ -239,13 +239,13 @@ def remove_stopwords(docs: pd.Series, opt: str = 'all', set_to_add: Union[list,
 @utils.data_agnostic
 @utils.regroup_data_series
 def remove_accents(docs: pd.Series, use_tqdm: bool = True) -> pd.Series:
-    '''Removes all accents and special characters (ç..) 
+    '''Removes all accents and special characters (ç..)
 
     Args:
         docs (pd.Series): Documents to process
     Kwargs:
         use_tqdm (bool): Whether tqdm should be used (default: True)
-    
+
     Returns:
         pd.Series: Modified documents
     '''
@@ -259,17 +259,17 @@ def remove_accents(docs: pd.Series, use_tqdm: bool = True) -> pd.Series:
 @utils.data_agnostic
 @utils.regroup_data_series
 def remove_gender_synonyms(docs: pd.Series) -> pd.Series:
-    '''[French] Removes gendered synonyms 
+    '''[French] Removes gendered synonyms
     # Find occurences such as "male version / female version" (eg: Coiffeur / Coiffeuse)
     # By convention, the male version is kept (in accordance with the lemmatizer)
 
     Args:
         docs (pd.Series): Documents to process
-    
+
     Returns:
         pd.Series: Modified documents
     '''
-    logger.debug('Calling basic.remove_gender_synonyms') 
+    logger.debug('Calling basic.remove_gender_synonyms')
     return synonym_malefemale_replacement.remove_gender_synonyms(docs)
 
 
@@ -281,7 +281,7 @@ def lemmatize(docs: pd.Series) -> pd.Series:
 
     Args:
         docs (pd.Series): Documents to process
-    
+
     Returns:
         pd.Series: Modified documents
     '''
@@ -293,11 +293,11 @@ def lemmatize(docs: pd.Series) -> pd.Series:
 @utils.data_agnostic
 @utils.regroup_data_series
 def stemmatize(docs: pd.Series) -> pd.Series:
-    '''Stemmatizes words in the documents 
+    '''Stemmatizes words in the documents
 
     Args:
         docs (pd.Series): Documents to process
-    
+
     Returns:
         pd.Series: Modified documents
     '''
@@ -314,7 +314,7 @@ def add_point(docs: pd.Series) -> pd.Series:
 
     Args:
         docs (pd.Series): Documents to process
-    
+
     Returns:
         pd.Series: Modified documents
     '''
@@ -345,7 +345,7 @@ def replace_urls(docs: pd.Series, replacement_char: str = ' ', replace_with_doma
         docs (pd.Series): Documents to process
     Kwargs:
         replacement_char (str): Replacement character (def= ' ')
-        replace_with_domain (bool): Replacement_char is overriden and the url is replaced by its domain (def= False) 
+        replace_with_domain (bool): Replacement_char is overriden and the url is replaced by its domain (def= False)
     Returns:
         pd.Series: Modified documents
     '''
@@ -397,7 +397,7 @@ def fix_text(docs: pd.Series, **ftfy_kwargs) -> pd.Series:
         docs (pd.Series): Documents to process
     Kwargs:
         ftfy_kwargs (dict): Kwargs forwarded to ftfy
-    
+
     Returns:
         pd.Series: Modified documents
     '''
diff --git a/pe_semantic/preprocessing/lemmatizer.py b/words_n_fun/preprocessing/lemmatizer.py
similarity index 98%
rename from pe_semantic/preprocessing/lemmatizer.py
rename to words_n_fun/preprocessing/lemmatizer.py
index d0920c8..15edbd3 100644
--- a/pe_semantic/preprocessing/lemmatizer.py
+++ b/words_n_fun/preprocessing/lemmatizer.py
@@ -3,17 +3,17 @@
 ## Functions linked to the lemmatizer
 #  It used to be a custom made french lemmatizer but has been depcrecated and replaced by spacy
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -26,7 +26,7 @@
 import requests
 import pandas as pd
 import simplejson as json
-from pe_semantic import utils
+from words_n_fun import utils
 
 # Get logger
 import logging
diff --git a/pe_semantic/preprocessing/split_sentences.py b/words_n_fun/preprocessing/split_sentences.py
similarity index 96%
rename from pe_semantic/preprocessing/split_sentences.py
rename to words_n_fun/preprocessing/split_sentences.py
index 57b1388..dea6adc 100644
--- a/pe_semantic/preprocessing/split_sentences.py
+++ b/words_n_fun/preprocessing/split_sentences.py
@@ -2,17 +2,17 @@
 
 ## Functions to split texts into sentences
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -27,7 +27,7 @@
 from itertools import chain
 from typing import Union, List
 
-from pe_semantic import CustomTqdm as tqdm
+from words_n_fun import CustomTqdm as tqdm
 
 
 tqdm.pandas()
@@ -46,7 +46,7 @@ def split_sentences(text: str) -> List[str]:
     Returns:
         list<str> : List of sentences
     '''
-    
+
     # We split around \n after singling them out and removing trailing spaces.
     text = re.compile("\s*\n+").sub('\n', text)
     text = re.compile("\s+$").sub('', text)
@@ -84,7 +84,7 @@ def split_sentences_df(df: pd.DataFrame, col: Union[str, int]) -> pd.DataFrame:
 
     Args:
         df (pd.DataFrame): DataFrame containing the texts
-        col (str ou int): Column name where the text is 
+        col (str ou int): Column name where the text is
     Returns:
         pd.DataFrame: New DataFrame with the text split into sentences
     '''
diff --git a/pe_semantic/preprocessing/stopwords.py b/words_n_fun/preprocessing/stopwords.py
similarity index 99%
rename from pe_semantic/preprocessing/stopwords.py
rename to words_n_fun/preprocessing/stopwords.py
index 2b81a0a..9ac6c77 100644
--- a/pe_semantic/preprocessing/stopwords.py
+++ b/words_n_fun/preprocessing/stopwords.py
@@ -2,17 +2,17 @@
 
 ## Stopwords management functions
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -29,8 +29,8 @@
 import pandas as pd
 from typing import Union
 
-from pe_semantic import utils
-from pe_semantic.preprocessing import basic
+from words_n_fun import utils
+from words_n_fun.preprocessing import basic
 
 # Get logger
 import logging
@@ -147,7 +147,7 @@ def remove_stopwords(docs: pd.Series, opt: str = 'all', set_to_add: Union[list,
         'offres_pe': set().union(STOPWORDS_OFFRES_1, STOPWORDS_OFFRES_2),
         'all': set().union(STOPWORDS, stopwords_ascii(), stopwords_nltk(), stopwords_nltk_ascii()),
     }
-    
+
     if opt in usage.keys():
         stopwords_list = list(usage.get(opt))
     else:
@@ -168,7 +168,7 @@ def remove_stopwords(docs: pd.Series, opt: str = 'all', set_to_add: Union[list,
         logger.warning("Stopwords_list is empty.")
         logger.warning("Non strings entries are still replaced by None.")
         return docs.apply(lambda x: x if isinstance(x, str) else None)
-    
+
     regex = utils.get_regex_match_words(stopwords_list)
     return docs.str.replace(regex, '')
 
diff --git a/pe_semantic/preprocessing/synonym_malefemale_replacement.py b/words_n_fun/preprocessing/synonym_malefemale_replacement.py
similarity index 93%
rename from pe_semantic/preprocessing/synonym_malefemale_replacement.py
rename to words_n_fun/preprocessing/synonym_malefemale_replacement.py
index e856c5a..6d5d523 100644
--- a/pe_semantic/preprocessing/synonym_malefemale_replacement.py
+++ b/words_n_fun/preprocessing/synonym_malefemale_replacement.py
@@ -3,17 +3,17 @@
 ## Functions to remove gendered synonyms: incredibly specific to the french language
 # TODO : To remove?
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -28,7 +28,7 @@
 import pandas as pd
 from typing import Tuple
 
-from pe_semantic import utils
+from words_n_fun import utils
 
 # Get logger
 import logging
@@ -37,23 +37,23 @@
 
 
 SYNONYM_DICT = {
-    'eur': ['euse','se','rice','e','eure'],
-    "ien" : ['ne','nne','e'],
-    "ier" : ['ere','re','e'],
-    "ial" : ['e'],
-    "l" : ['le'],
-    "nt" : ['e'],
-    "at" : ['e'],
-    "f" : ['ve'],
-    "o" : ['a'],
-    "er" : ['ere','ère','euse'],
-    "i" : ['e'],
-    "d" : ['e'],
-    "é" : ['e'],
-    "e" : ['e'],
-    "on" : ['onne'],
-    "is" : ['ise'],
-    "s" : ['s']
+    'eur': ['euse', 'se', 'rice', 'e', 'eure'],
+    "ien": ['ne', 'nne', 'e'],
+    "ier": ['ere', 're', 'e'],
+    "ial": ['e'],
+    "l": ['le'],
+    "nt": ['e'],
+    "at": ['e'],
+    "f": ['ve'],
+    "o": ['a'],
+    "er": ['ere', 'ère', 'euse'],
+    "i": ['e'],
+    "d": ['e'],
+    "é": ['e'],
+    "e": ['e'],
+    "on": ['onne'],
+    "is": ['ise'],
+    "s": ['s']
 }
 
 
@@ -102,7 +102,7 @@ def remove_gender_synonyms(docs: pd.Series) -> pd.Series:
             match_slash_pattern = re.findall(slash_pattern, text)
             match_slash_pattern_BiWords = re.findall(slash_pattern_BiWords, text)
             match_slash_pattern_TriWords = re.findall(slash_pattern_TriWords, text)
-            
+
             # Parenthesis
             if len(match_parenthesis_pattern) != 0:
                 for (word1, word2, word3) in match_parenthesis_pattern:
@@ -137,7 +137,7 @@ def matching_words(word1: str, word2: str) -> Tuple[str, str, str]:
     Args:
         word1 (str)
         word2 (str)
-    
+
     Raises:
         TypeError : If word1 is empty
         TypeError : If word2 is empty
@@ -195,7 +195,7 @@ def update_synonyms_set(synonyms_set: dict, match: list, numligne: int) -> dict:
                 combi = matching_words(word1, word2)
             if combi is not None:
                 if (list(combi)[2]) != "unknown" and combi not in synonyms_set.keys():
-                    synonyms_set[combi] = numligne  
+                    synonyms_set[combi] = numligne
     return synonyms_set
 
 
diff --git a/pe_semantic/preprocessing/vectorization_tokenization.py b/words_n_fun/preprocessing/vectorization_tokenization.py
similarity index 98%
rename from pe_semantic/preprocessing/vectorization_tokenization.py
rename to words_n_fun/preprocessing/vectorization_tokenization.py
index 50163d2..59a8ce2 100644
--- a/pe_semantic/preprocessing/vectorization_tokenization.py
+++ b/words_n_fun/preprocessing/vectorization_tokenization.py
@@ -2,17 +2,17 @@
 
 ## Vectorization and tokenization functions
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -22,7 +22,7 @@
 
 from typing import Tuple
 import pandas as pd
-from pe_semantic import utils
+from words_n_fun import utils
 
 # Get logger
 import logging
@@ -60,17 +60,17 @@ def split_text_into_tokens(docs: pd.Series, nbech: int = 10, seq_size: int = 3,
         raise ValueError("Step must be > 0")
     if granularity not in ['word', 'char']:
         raise ValueError("granularity must either be word or char")
-    
+
     sequences = pd.Series([None] * docs.shape[0])
     next_items = pd.Series([None] * docs.shape[0])
     for i in range(docs.shape[0]):
         text = docs.iloc[i]
         sequence = []
         next_item = []
-    
+
         if not isinstance(text, str):
             continue
-    
+
         if granularity == "char":
             for j in range(0, len(text) - seq_size, step):
                 if "." not in text[j : j + seq_size]:
diff --git a/pe_semantic/utils.py b/words_n_fun/utils.py
similarity index 97%
rename from pe_semantic/utils.py
rename to words_n_fun/utils.py
index ab8606d..54bbe46 100644
--- a/pe_semantic/utils.py
+++ b/words_n_fun/utils.py
@@ -2,24 +2,24 @@
 
 ## Utils
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
 #
 # Fonctions :
 # - timer -> Decorator to monitor the execution time of a function
-# - data_agnostic -> Decorator to manage type casting from and to pd.Series 
+# - data_agnostic -> Decorator to manage type casting from and to pd.Series
 # - data_agnostic_input -> Decorator to manage type casting to pd.Series
 # - get_docs_type -> Returns the type of a list
 # - get_docs_length -> Returns the number of elements within a set of documents
@@ -52,6 +52,7 @@
 
 logger = logging.getLogger(__name__)
 
+
 def timer(function: Callable) -> Callable:
     '''Decorator to monitor the execution time of a function
 
@@ -73,6 +74,7 @@ def wrapper(*args, **kwargs):
 
     return wrapper
 
+
 def data_agnostic(function: Callable, prefered_column: str = "docs", sep: str = ',') -> Callable:
     '''Decorator to manage type casting from and to pd.Series
 
@@ -283,17 +285,17 @@ def get_docs_type(docs: Union[str, list, np.ndarray, pd.Series, pd.DataFrame]) -
     return docs_type
 
 
-def get_docs_length(docs: Union[str, list, np.ndarray, pd.Series, pd.DataFrame], 
+def get_docs_length(docs: Union[str, list, np.ndarray, pd.Series, pd.DataFrame],
                     first_row: str = 'header', sep: str = ',', nrows: int = 0) -> int:
     '''Returns the number of elements within a set of documents
 
     Args:
         docs (?): Arbitrary document list (Supported types : str ending by .csv, str, list, np.ndarray, pd.Series, pd.DataFrame)
     Kwargs:
-        first_row (str): When working with a pandas dataframe or csv file, specifies how the first line is handled 
+        first_row (str): When working with a pandas dataframe or csv file, specifies how the first line is handled
             -'header', 'data' or 'skip' (default : 'header')
         sep: Separator to use if loading from a csv file
-        nrows (int) : When working with a pandas dataframe or csv file, specifies the maximum number of lines to read 
+        nrows (int) : When working with a pandas dataframe or csv file, specifies the maximum number of lines to read
             (default: 0 we take it all)
     Raises:
         ValueError: if nrows < 0
@@ -384,7 +386,7 @@ def get_new_csv_name(filename: str) -> str:
         raise FileExistsError('Can not find new file name (tried 1000 different names)')
 
 
-def get_generator(docs: Union[str, list, np.ndarray, pd.Series, pd.DataFrame], chunksize: int =0, 
+def get_generator(docs: Union[str, list, np.ndarray, pd.Series, pd.DataFrame], chunksize: int =0,
                   first_row: str = 'header', columns: List[str] = ['docs', 'tags'], sep: str = ',', nrows: int = 0, **pandas_args):
     '''Returns a generator given the type of document to process and the chunksize
 
@@ -392,13 +394,13 @@ def get_generator(docs: Union[str, list, np.ndarray, pd.Series, pd.DataFrame], c
         docs (?): Arbitrary document list (Supported types : str ending by .csv, str, list, np.ndarray, pd.Series, pd.DataFrame)
     Kwargs:
         chunksize (int): if > 0 data is processed by chunks of chunksize size (by default : 0)
-        first_row (str): When working with a pandas dataframe or csv file, specifies how the first line is handled 
+        first_row (str): When working with a pandas dataframe or csv file, specifies how the first line is handled
             -'header', 'data' or 'skip' (default : 'header')
-        columns (list<str>) : When working with a pandas dataframe or csv file, specifies the columns to use, 
-            if first_row != 'header'. Truncate the data if there is too much columns & add some if they are missing 
+        columns (list<str>) : When working with a pandas dataframe or csv file, specifies the columns to use,
+            if first_row != 'header'. Truncate the data if there is too much columns & add some if they are missing
             (default : ['docs', 'tags'])
         sep (str): When working with a pandas dataframe or csv file, specifies the csv separator (default: ',')
-        nrows (int) : When working with a pandas dataframe or csv file, specifies the maximum number of lines to read 
+        nrows (int) : When working with a pandas dataframe or csv file, specifies the maximum number of lines to read
             (default: 0 we take it all)
         pandas_args : When working with a pandas dataframe or csv file, specifies arguments to pass to pandas
     Raises:
@@ -457,7 +459,7 @@ def get_generator(docs: Union[str, list, np.ndarray, pd.Series, pd.DataFrame], c
     return gen
 
 
-def get_df_generator_from_csv(filename: str, chunksize: int = 0, first_row: str = 'header', 
+def get_df_generator_from_csv(filename: str, chunksize: int = 0, first_row: str = 'header',
                               columns: List[str] = ['docs', 'tags'], sep: str = ',', nrows: int = 0, **pandas_args):
     '''Returns a dataFrame generator by chunk over a file
     If chunksize is 0 -> A one item generator is still returned
@@ -466,13 +468,13 @@ def get_df_generator_from_csv(filename: str, chunksize: int = 0, first_row: str
         filename (str): Path to the csv file
     Kwargs:
         chunksize (int): If not 0 the pipeline is processed chunkwise and this parameter specifies the chunksize (default : 0)
-        first_row (str): When working with a pandas dataframe or csv file, specifies how the first line is handled 
+        first_row (str): When working with a pandas dataframe or csv file, specifies how the first line is handled
             -'header', 'data' or 'skip' (default : 'header')
-        columns (list<str>) : When working with a pandas dataframe or csv file, specifies the columns to use, 
-            if first_row != 'header'. Truncate the data if there is too much columns & add some if they are missing 
+        columns (list<str>) : When working with a pandas dataframe or csv file, specifies the columns to use,
+            if first_row != 'header'. Truncate the data if there is too much columns & add some if they are missing
             (default : ['docs', 'tags'])
         sep (str): When working with a pandas dataframe or csv file, specifies the csv separator (default: ',')
-        nrows (int) : When working with a pandas dataframe or csv file, specifies the maximum number of lines to read 
+        nrows (int) : When working with a pandas dataframe or csv file, specifies the maximum number of lines to read
             (default: 0 we take it all)
         pandas_args : When working with a pandas dataframe or csv file, specifies arguments to pass to pandas
     Raises:
@@ -546,10 +548,10 @@ def get_columns_to_use(filename: str, first_row: str = 'header', columns: List[s
     Args:
         filename (str): Path to the csv file
     Kwargs:
-        first_row (str): When working with a pandas dataframe or csv file, specifies how the first line is handled 
+        first_row (str): When working with a pandas dataframe or csv file, specifies how the first line is handled
             -'header', 'data' or 'skip' (default : 'header')
         columns (list<str>) : When working with a pandas dataframe or csv file, specifies the columns to use,
-            if first_row != 'header'. Truncate the data if there is too much columns & add some if they are missing 
+            if first_row != 'header'. Truncate the data if there is too much columns & add some if they are missing
             (default : ['docs', 'tags'])
         sep (str): When working with a pandas dataframe or csv file, specifies the csv separator (default: ',')
         file_length (int): Number of lines in the file (can speed up calculations by avoiding a call to get_file_length)
@@ -581,7 +583,7 @@ def get_columns_to_use(filename: str, first_row: str = 'header', columns: List[s
     # Get number of columns
     number_of_columns = len(first_line.split(sep))
 
-    # Manages how the columns are handled 
+    # Manages how the columns are handled
     if first_row == 'header':
         columns_to_use = first_line.split(sep)
     elif number_of_columns > len(columns):
@@ -624,19 +626,19 @@ def get_column_to_be_processed(docs: Union[str, list, np.ndarray, pd.Series, pd.
     Args:
         docs (?): Arbitrary document list (Supported types : str ending by .csv, str, list, np.ndarray, pd.Series, pd.DataFrame)
     Kwargs:
-        prefered_column (str): Default column name to consider as the document container when working 
+        prefered_column (str): Default column name to consider as the document container when working
             with a pandas dataframe or csv file (default: 'docs')
-        first_row (str): When working with a pandas dataframe or csv file, specifies how the first line is handled 
+        first_row (str): When working with a pandas dataframe or csv file, specifies how the first line is handled
             -'header', 'data' or 'skip' (default : 'header')
         columns (list<str>) : When working with a pandas dataframe or csv file, specifies the columns to use,
-            if first_row != 'header'. Truncate the data if there is too much columns & add some if they are missing 
+            if first_row != 'header'. Truncate the data if there is too much columns & add some if they are missing
             (default : ['docs', 'tags'])
         sep (str): When working with a pandas dataframe or csv file, specifies the csv separator (default: ',')
     Returns:
         (str ou int): Index or name of the column to process
     '''
     logger.debug('Calling utils.get_column_to_be_processed')
-    
+
     docs_type = get_docs_type(docs)
 
     if docs_type == 'pd.DataFrame':
@@ -662,7 +664,7 @@ def regroup_data_series(function: Callable, min_nb_data: int = 1000, prefix_text
         function (Callable): Function to which this wrapper is wrapped around /!\ intputs and outputs must be pd.Series, 1 to 1 /!\
     Kwargs:
         min_nb_data (int): Minimum number of rows within the document required to apply this wrapper (default : 1000)
-        prefix_text (str): Prefix to add 
+        prefix_text (str): Prefix to add
     Returns:
         function: Decorated function
     '''
@@ -728,7 +730,7 @@ def regroup_data_df(function: Callable, columns_to_be_processed: Union[list, Non
         min_nb_data (int): Minimum number of rows within the document required to apply this wrapper (default : 1000)
         prefix_text (str): Prefix to add
     Raises:
-        ValueError: If min_nb_data < 0 
+        ValueError: If min_nb_data < 0
     Returns:
         function: Decorated function
     '''
@@ -837,7 +839,7 @@ def get_regex_match_words(words: List[str], case_insensitive: bool = False,
         (str) : Newly generated regex
     '''
     logger.debug('Calling utils.get_regex_match_words')
-    
+
     # Adding characters that are allowed after and before 'words'
     accepted_char_ahead = re.escape(accepted_char_ahead) + '\s'
     accepted_char_behind = re.escape(accepted_char_behind) + '\s'


Test patch:
diff --git a/.github/workflows/wnf_tests.yaml b/.github/workflows/wnf_tests.yaml
new file mode 100644
index 0000000..9a94ec6
--- /dev/null
+++ b/.github/workflows/wnf_tests.yaml
@@ -0,0 +1,68 @@
+name: Build & Test - Words'n Fun
+on:
+  push:
+    branches:
+      - 'feature/main'
+      - 'release/v*'
+  pull_request:
+    branches:
+      - 'feature/main'
+      - 'release/v*'
+
+jobs:
+  build:
+    strategy:
+      matrix:
+        python-version: ["3.7", "3.8"]
+        os: [ubuntu-latest, windows-latest]
+    runs-on: ${{ matrix.os }}
+
+    steps:
+      - uses: actions/checkout@v3
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v3
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install package & dependencies
+        run: |
+          python -m pip install --upgrade pip
+          python setup.py install
+          pip install nose==1.3.7 nose-exclude==0.5.0 coverage==5.3
+          pip install flake8
+      - name: Lint with flake8
+        run: |
+          # Stop the build if there are Python syntax errors or undefined names
+          flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+          # Exit-zero treats all errors as warnings.
+          flake8 . --count --exit-zero --max-complexity=10 --ignore=E501 --statistics
+      - name: Test WNF
+        run: nosetests -c nose_setup_coverage.cfg tests
+
+  build-spacy:
+    strategy:
+      matrix:
+        python-version: ["3.7", "3.8"]
+        os: [ubuntu-latest, windows-latest]
+    runs-on: ${{ matrix.os }}
+
+    steps:
+      - uses: actions/checkout@v3
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v3
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install package & dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install https://github.com/explosion/spacy-models/releases/download/fr_core_news_sm-3.2.0/fr_core_news_sm-3.2.0-py3-none-any.whl
+          pip install .[lemmatizer]
+          pip install nose==1.3.7 nose-exclude==0.5.0 coverage==5.3
+          pip install flake8
+      - name: Lint with flake8
+        run: |
+          # Stop the build if there are Python syntax errors or undefined names
+          flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+          # Exit-zero treats all errors as warnings.
+          flake8 . --count --exit-zero --max-complexity=10 --ignore=E501 --statistics
+      - name: Test WNF
+        run: nosetests -c nose_setup_coverage.cfg tests
diff --git a/tests/test_1_utils.py b/tests/test_1_utils.py
index 5c98de3..2ef5ae4 100644
--- a/tests/test_1_utils.py
+++ b/tests/test_1_utils.py
@@ -2,17 +2,17 @@
 
 ## Tests - unit test of utils functions
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -30,14 +30,14 @@
 import numpy as np
 import pandas as pd
 
-import pe_semantic
-from pe_semantic import utils
+import words_n_fun as wnf
+from words_n_fun import utils
 
 # Pour ces tests, on garde le logger pour certains tests
 # Du coup, default to critical
 import logging
 logging.disable(logging.CRITICAL)
-logger = pe_semantic.logger
+logger = wnf.logger
 
 
 class UtilsTests(unittest.TestCase):
@@ -55,7 +55,7 @@ def setUp(self):
     # TODO : dissocier les deux ?
     @patch('logging.Logger._log')
     def test_timer(self, PrintMockLog):
-        '''Test de la fonction utils.timer'''
+        '''Testing function utils.timer'''
         # Definition d'une fonction à tester
         def test(x):
             return x**2
@@ -91,7 +91,7 @@ def test(x):
 
 
     def test_data_agnostic(self):
-        '''Test de la fonction utils.data_agnostic'''
+        '''Testing function utils.data_agnostic'''
         # Definition d'une fonction à décorer
         def test_function(docs):
             if type(docs) != pd.Series: raise TypeError('')
@@ -156,7 +156,7 @@ def test_function(docs):
 
 
     def test_data_agnostic_input(self):
-        '''Test de la fonction utils.data_agnostic_input'''
+        '''Testing function utils.data_agnostic_input'''
         # Definition d'une fonction à décorer
         def test_function(docs):
             if type(docs) != pd.Series: raise TypeError('')
@@ -196,7 +196,7 @@ def test_function(docs):
 
 
     def test_get_docs_type(self):
-        '''Test de la fonction utils.get_docs_type'''
+        '''Testing function utils.get_docs_type'''
         test_str = "ceci est un test"
         test_list = ["ceci est un test", 5, None]
         test_np_array = np.array(["ceci est un test", 5, None])
@@ -219,7 +219,7 @@ def test_get_docs_type(self):
 
 
     def test_get_docs_length(self):
-        '''Test de la fonction utils.get_docs_length'''
+        '''Testing function utils.get_docs_length'''
         test_str = "ceci est un test"
         test_list = ["ceci est un test", 5, None]
         test_np_array = np.array(["ceci est un test", 5, None])
@@ -247,7 +247,7 @@ def test_get_docs_length(self):
         self.assertEqual(utils.get_docs_length(test_file_comma, first_row='skip', sep=',', nrows=30), 9)
 
     def test_get_file_length(self):
-        '''Test de la fonction utils.file_length'''
+        '''Testing function utils.file_length'''
         input_file_1 = "./testing_file.csv"
         expected_result_1 = 10
         input_file_2 = "./testing_file6.csv"
@@ -263,7 +263,7 @@ def test_get_file_length(self):
 
 
     def test_get_new_csv_name(self):
-        '''Test de la fonction utils.get_new_csv_name'''
+        '''Testing function utils.get_new_csv_name'''
         input_file = "./testing_file.csv"
 
         # Vérification du fonctionnement type
@@ -273,7 +273,7 @@ def test_get_new_csv_name(self):
 
 
     def test_get_generator(self):
-        '''Test de la fonction utils.get_generator'''
+        '''Testing function utils.get_generator'''
         test_str = 'test'
         result_str = 'test'
         test_list = ['test', 'test 2']
@@ -317,7 +317,7 @@ def test_get_generator(self):
 
 
     def test_get_df_generator_from_csv(self):
-        '''Test de la fonction utils.get_df_generator_from_csv'''
+        '''Testing function utils.get_df_generator_from_csv'''
         input_file =  "./testing_file.csv"
         expected_result = [pd.DataFrame( [["col 1 line 1", "col 2 line 1"]], columns=['col 1', 'col 2'], index=[0]),
                            pd.DataFrame( [["col 1 line 2", "col 2 line 2"]], columns=['col 1', 'col 2'], index=[1]),
@@ -423,7 +423,7 @@ def test_get_df_generator_from_csv(self):
 
 
     def test_get_columns_to_use(self):
-        '''Test de la fonction utils.get_columns_to_use'''
+        '''Testing function utils.get_columns_to_use'''
         test_file =  "./testing_file.csv"
         test_file_one_row =  "./testing_file2.csv"
         empty_file = "./testing_file3.csv"
@@ -451,7 +451,7 @@ def test_get_columns_to_use(self):
 
 
     def test_get_new_column_name(self):
-        '''Test de la fonction utils.get_new_column_name'''
+        '''Testing function utils.get_new_column_name'''
         docs_column = ['test', 'test2']
         processed_column = 'test2'
         new_column = 'test2_processed'
@@ -465,7 +465,7 @@ def test_get_new_column_name(self):
 
 
     def test_get_column_to_be_processed(self):
-        '''Test de la fonction utils.get_column_to_be_processed'''
+        '''Testing function utils.get_column_to_be_processed'''
         test_str = 'test'
         result_str = 'docs'
         test_list = ['test', 'test 2']
@@ -500,7 +500,7 @@ def test_get_column_to_be_processed(self):
 
 
     def test_regroup_data_series(self):
-        '''Test de la fonction utils.regroup_data_series'''
+        '''Testing function utils.regroup_data_series'''
         # Definition d'une fonction à décorer
         def test_function(docs):
             if type(docs) != pd.Series: raise TypeError('')
@@ -524,7 +524,7 @@ def test_function(docs):
 
 
     def test_regroup_data_df(self):
-        '''Test de la fonction utils.regroup_data_df'''
+        '''Testing function utils.regroup_data_df'''
         # Definition d'une fonction à wrapper
         def test_function_1(df):
             if type(df) != pd.DataFrame: raise TypeError('')
@@ -555,7 +555,7 @@ def test_function_2(df):
 
 
     def test_get_regex_match_words(self):
-        '''Test de la fonction utils.get_regex_match_words'''
+        '''Testing function utils.get_regex_match_words'''
         words = ['test?', 'toto']
         expected_result = '(?:^|(?<=[\\.\\?!,;:\\(\\)"\'/<>=\\[\\]\\{\\}\\~\\*\\s]))(test\\?|toto)(?=[\\.\\?!,;:\\(\\)"\'/<>=\\[\\]\\{\\}\\~\\*\\s]|$)'
         expected_result_2 = '(?i)(?:^|(?<=[\\.\\?!,;:\\(\\)"\'/<>=\\[\\]\\{\\}\\~\\*\\s]))(test\\?|toto)(?=[\\.\\?!,;:\\(\\)"\'/<>=\\[\\]\\{\\}\\~\\*\\s]|$)'
diff --git a/tests/test_2_basic.py b/tests/test_2_basic.py
index a3d7948..454dda9 100644
--- a/tests/test_2_basic.py
+++ b/tests/test_2_basic.py
@@ -2,17 +2,17 @@
 
 ## Tests - unit test of basic functions
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -24,13 +24,17 @@
 
 # Utils libs
 import os
-import spacy
+import sys
+try:
+    import spacy
+except ModuleNotFoundError:
+    pass
 import importlib
 import numpy as np
 import pandas as pd
-from pe_semantic import utils
-from pe_semantic.preprocessing import basic
-from pe_semantic.preprocessing import stopwords
+from words_n_fun import utils
+from words_n_fun.preprocessing import basic
+from words_n_fun.preprocessing import stopwords
 
 # Disable logging
 import logging
@@ -65,7 +69,7 @@ def setUp(self):
 
 
     def test_notnull(self):
-        '''Test de la fonction basic.notnull'''
+        '''Testing function basic.notnull'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", 5, None]
         docs_series = pd.Series(docs)
         docs_series_copy = pd.Series(docs)
@@ -79,7 +83,7 @@ def test_notnull(self):
 
 
     def test_remove_non_string(self):
-        '''Test de la fonction basic.remove_non_string'''
+        '''Testing function basic.remove_non_string'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", 5, None]
         docs_processed = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", '', '']
 
@@ -88,7 +92,7 @@ def test_remove_non_string(self):
 
 
     def test_get_true_spaces(self):
-        '''Test de la fonction basic.get_true_spaces'''
+        '''Testing function basic.get_true_spaces'''
         docs = ["Ceci est un test", "Ceci est	un autre test 	", "Ceci est encore un	autre test.\n Avec un retour à la ligne :)", 5, None]
         docs_processed = ["Ceci est un test", "Ceci est un autre test  ", "Ceci est encore un autre test.  Avec un retour à la ligne :)", None, None]
 
@@ -98,7 +102,7 @@ def test_get_true_spaces(self):
 
 
     def test_to_lower(self):
-        '''Test de la fonction basic.to_lower'''
+        '''Testing function basic.to_lower'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", 5, None]
         docs_lowered = ["chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "je maîtrise 12 langages informatiques dont le c & j'ai le permis b", "coordinateur d'equipe d'action territoriale ", None, None]
         docs_lowered_except_singleLetters = ["chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "je maîtrise 12 langages informatiques dont le C & j'ai le permis B", "coordinateur d'equipe d'action territoriale ", None, None]
@@ -110,7 +114,7 @@ def test_to_lower(self):
 
 
     def test_pe_matching(self):
-        '''Test de la fonction basic.pe_matching'''
+        '''Testing function basic.pe_matching'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", 5, None]
         docs_series = pd.Series(docs)
         docs_series_copy = pd.Series(docs)
@@ -125,7 +129,7 @@ def test_pe_matching(self):
 
 
     def test_remove_punct(self):
-        '''Test de la fonction basic.remove_punct'''
+        '''Testing function basic.remove_punct'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d_Equipe d'Action Territoriale ", 5, None]
         docs_punct_replaced = ["Chauffeur se   accompagnateur trice  pers à mob   5 ans de expérience ", "Je maîtrise 12 langages informatiques dont le C   j ai le Permis B", "Coordinateur d Equipe d Action Territoriale ", None, None]
         docs_punct_replaced_except_parentesis = ["Chauffeur(se)  accompagnateur(trice) pers à mob   5 ans de expérience ", "Je maîtrise 12 langages informatiques dont le C   j ai le Permis B", "Coordinateur d Equipe d Action Territoriale ", None, None]
@@ -139,7 +143,7 @@ def test_remove_punct(self):
 
 
     def test_trim_string(self):
-        '''Test de la fonction basic.trim_string'''
+        '''Testing function basic.trim_string'''
         docs = ["    Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques \n\n dont le C & j'ai le Permis B\n", "Coordinateur d'Equipe d'Action Territoriale ", 5, None]
         docs_processed = ["Chauffeur(se) accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques \n\n dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale", None, None]
 
@@ -149,7 +153,7 @@ def test_trim_string(self):
 
 
     def test_remove_leading_and_ending_spaces(self):
-        '''Test de la fonction basic.remove_leading_and_ending_spaces'''
+        '''Testing function basic.remove_leading_and_ending_spaces'''
         docs = [" )test test toto(", "             \t  test \t", 'test avec nouvelle ligne\n', 5, None]
         docs_processed = [")test test toto(", "test", 'test avec nouvelle ligne', None, None]
 
@@ -159,7 +163,7 @@ def test_remove_leading_and_ending_spaces(self):
 
 
     def test_remove_numeric(self):
-        '''Test de la fonction basic.remove_numeric'''
+        '''Testing function basic.remove_numeric'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", 5, None]
         docs_numeric_replaced = ["Chauffeur(se)  accompagnateur(trice) pers à mob -   ans de expérience.", "Je maîtrise   langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", None, None]
         docs_numeric_removed = ["Chauffeur(se)  accompagnateur(trice) pers à mob -  ans de expérience.", "Je maîtrise  langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", None, None]
@@ -171,7 +175,7 @@ def test_remove_numeric(self):
 
 
     def test_remove_accents(self):
-        '''Test de la fonction basic.remove_accents'''
+        '''Testing function basic.remove_accents'''
         docs = ["tést têst tèst", 5, None]
         docs_accents_removed = ["test test test", None, None]
 
@@ -180,9 +184,9 @@ def test_remove_accents(self):
         self.assertEqual(list(basic.remove_accents(pd.Series(docs), use_tqdm=False).replace({np.nan: None})), docs_accents_removed)
 
 
-    @patch('pe_semantic.preprocessing.basic.remove_accents', side_effect=nominal_remove_accents)
+    @patch('words_n_fun.preprocessing.basic.remove_accents', side_effect=nominal_remove_accents)
     def test_remove_stopwords(self, RemoveAccentMock):
-        '''Test de la fonction basic.remove_stopwords'''
+        '''Testing function basic.remove_stopwords'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", 5, None]
         docs_stopwords_removed = ["Chauffeur()  accompagnateur(trice) pers  mob - 5 ans  expérience.", "Je maîtrise 12 langages informatiques   C & '  Permis B", "Coordinateur 'Equipe 'Action Territoriale ", None, None]
         docs_stopwords_removed_default = ["Chauffeur()  accompagnateur(trice) pers  mob - 5 ans  expérience.", "Je maîtrise 12 langages informatiques   C & '  Permis B", "Coordinateur 'Equipe 'Action Territoriale ", None, None]
@@ -213,7 +217,7 @@ def test_remove_stopwords(self, RemoveAccentMock):
 
 
     def test_remove_gender_synonyms(self):
-        '''Test de la fonction basic.remove_gender_synonyms'''
+        '''Testing function basic.remove_gender_synonyms'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", 5, None, "serveur/serveur(se), agriculteur (trice) blabla ouvrier/ ouvrière blabla aide apprenti boucher /aide apprentie bouchere"]
         docs_gender_syn_removed = ['Chauffeur   accompagnateur  pers à mob - 5 ans de expérience.', "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", None, None, 'serveur , agriculteur  blabla ouvrier blabla aide apprenti boucher']
 
@@ -221,8 +225,9 @@ def test_remove_gender_synonyms(self):
         self.assertEqual(list(basic.remove_gender_synonyms(pd.Series(docs)).replace({np.nan: None})), docs_gender_syn_removed)
 
 
+    @unittest.skipIf('spacy' not in sys.modules, "Skipping all lemmatizer tests as spacy can't be imported.")
     def test_lemmatize(self):
-        '''Test de la fonction basic.lemmatize'''
+        '''Testing function basic.lemmatize'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "^Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Coordinateur d'Equipe d'Action Territoriale ", 5, None]
         docs_lemmatized = ['chauffeur se accompagnateur trice pers à mob 5 an de expérience', 'je maîtris 12 langage informatique dont le c j avoir le permis b', 'chauffeur se accompagnateur trice pers à mob 5 an de expérience', 'coordinateur d equipe d action territorial', None, None]
 
@@ -234,7 +239,7 @@ def test_lemmatize(self):
 
 
     def test_stemmatize(self):
-        '''Test de la fonction basic.stemmatize'''
+        '''Testing function basic.stemmatize'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", 5, None]
         docs_stemmatized = ['chauffeur(se)  accompagnateur(trice) per à mob - 5 an de expérience.', "je maîtris 12 langag informat dont le c & j'ai le perm b", "coordin d'equip d'action territorial ", None, None]
 
@@ -244,7 +249,7 @@ def test_stemmatize(self):
 
 
     def test_add_point(self):
-        '''Test de la fonction basic.add_point'''
+        '''Testing function basic.add_point'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", 5, None]
         docs_processed = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B.", "Coordinateur d'Equipe d'Action Territoriale .", None, None]
 
@@ -254,7 +259,7 @@ def test_add_point(self):
 
 
     def test_deal_with_specific_characters(self):
-        '''Test de la fonction basic.deal_with_specific_characters'''
+        '''Testing function basic.deal_with_specific_characters'''
         docs = [".Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", " .Je maîtrise,12 langages, informatiques , dont le C & j'ai le Permis B. ", " . Coordinateur d'Equipe d'Action Territoriale . ", 5, None]
         docs_processed = [" . Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience . ", " . Je maîtrise , 12 langages , informatiques , dont le C & j ' ai le Permis B . ", " . Coordinateur d ' Equipe d ' Action Territoriale . ", None, None]
 
@@ -264,7 +269,7 @@ def test_deal_with_specific_characters(self):
 
 
     def test_replace_urls(self):
-        '''Test de la fonction basic.replace_urls'''
+        '''Testing function basic.replace_urls'''
         docs = ["ceci est un text avec une URL:http://www.test.com/zaeaze?pd==sqd", "ahttp://ftp.test.com", "HTTP://toto.fr/dfsdsfo?pfp=zaeazpo",
                 "www.test", "www.test.com/toto", "toto.titi@gmail.com", 5, None]
         docs_url_replaced = ["ceci est un text avec une URL: ", "ahttp://ftp.test.com", " ",
@@ -282,7 +287,7 @@ def test_replace_urls(self):
 
 
     def test_remove_words(self):
-        '''Test de la fonction basic.remove_words'''
+        '''Testing function basic.remove_words'''
         docs = ["ceci est un test de la fonction remove_words OK", "il s agit d une fonction qui compte les mots dans une serie pandas", "test compte j'ok test", 5, None]
         words_to_remove = ['test', 'toto', 'fonction', 'ok']
         docs_processed = ["ceci est un  de la  remove_words OK", "il s agit d une  qui compte les mots dans une serie pandas", " compte j' ", None, None]
@@ -295,7 +300,7 @@ def test_remove_words(self):
 
 
     def test_fix_text(self):
-        '''Test de la fonction basic.fix_text'''
+        '''Testing function basic.fix_text'''
         docs = ["Ãºnico", "là entités HTML &lt;3", "ＬＯＵＤ　ＮＯＩＳＥＳ", 5, None]
         results = ["único", "là entités HTML <3", "LOUD NOISES", None, None]
         results_2 = ["Ãºnico", "là entités HTML <3", "LOUD NOISES", None, None]
diff --git a/tests/test_3_api.py b/tests/test_3_api.py
index cf1a895..84e449a 100644
--- a/tests/test_3_api.py
+++ b/tests/test_3_api.py
@@ -2,17 +2,17 @@
 
 ## Tests - unit test of api functions
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -28,9 +28,9 @@
 import importlib
 import numpy as np
 import pandas as pd
-from pe_semantic import utils
-from pe_semantic.preprocessing import api
-from pe_semantic.preprocessing import basic
+from words_n_fun import utils
+from words_n_fun.preprocessing import api
+from words_n_fun.preprocessing import basic
 
 # Disable logging
 import logging
@@ -56,7 +56,7 @@ def setUp(self):
 
 
     def test_get_preprocessor(self):
-        '''Test de la fonction api.get_preprocessor'''
+        '''Testing function api.get_preprocessor'''
         # Vérification du fonctionnement type
         self.assertEqual(api.get_preprocessor().__class__, api.PreProcessor)
         self.assertEqual(api.get_preprocessor(pipeline=api.DEFAULT_PIPELINE, prefered_column='docs', modify_data=True, chunksize=0, first_row='header', columns=['docs', 'tags'], sep=',', nrows=0).__class__, api.PreProcessor)
@@ -70,7 +70,7 @@ def test_get_preprocessor(self):
 
 
     def test_preprocess_pipeline(self):
-        '''Test de la fonction api.preprocess_pipeline'''
+        '''Testing function api.preprocess_pipeline'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", 5, None]
         docs_def_pipeline = ['chauffeur accompagn per mob an experient', 'maitris langag informat c permisb', 'coordin equip action territorial', '', '']
         docs_series = pd.Series(["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", 5, None])
@@ -260,7 +260,7 @@ def test_PreProcessor(self):
 
     @patch('logging.Logger._log')
     def test_check_pipeline_order(self, PrintMockLog):
-        '''Test de la fonction api.check_pipeline_order'''
+        '''Testing function api.check_pipeline_order'''
         def test(docs):
             return docs
 
@@ -288,7 +288,7 @@ def test(docs):
 
 
     def test_listing_count_words(self):
-        '''Test de la fonction api.listing_count_words'''
+        '''Testing function api.listing_count_words'''
         docs = ["ceci est un test de la fonction listing_count_words", "il s agit d une fonction qui compte les mots dans une serie pandas", "test compte ok test"]
         wanted_result = pd.DataFrame([['agit', 1], ['ceci', 1], ['compte', 2], ['d', 1], ['dans', 1], ['de', 1], ['est', 1],
                                       ['fonction', 2], ['il', 1], ['la', 1], ['les', 1], ['listing_count_words', 1], ['mots', 1],
@@ -301,7 +301,7 @@ def test_listing_count_words(self):
 
 
     def test_list_one_appearance_word(self):
-        '''Test de la fonction api.list_one_appearance_word'''
+        '''Testing function api.list_one_appearance_word'''
         docs = ["ceci est un test de la fonction list_one_appearance_word", "il s agit d une fonction qui compte les mots dans une serie pandas", "test compte ok test"]
         wanted_result = pd.Series(['agit', 'ceci', 'd', 'dans', 'de', 'est', 'il', 'la', 'les', 'list_one_appearance_word', 'mots',
                                    'ok', 'pandas', 'qui', 's', 'serie', 'un'], name='word')
diff --git a/tests/test_4_vectorizationTokenization.py b/tests/test_4_vectorizationTokenization.py
index 4d41a77..f4eed80 100644
--- a/tests/test_4_vectorizationTokenization.py
+++ b/tests/test_4_vectorizationTokenization.py
@@ -2,17 +2,17 @@
 
 ## Tests - unit test of vectorization_tokenization functions
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -27,8 +27,8 @@
 import importlib
 import numpy as np
 import pandas as pd
-from pe_semantic import utils
-from pe_semantic.preprocessing import vectorization_tokenization
+from words_n_fun import utils
+from words_n_fun.preprocessing import vectorization_tokenization
 
 # Disable logging
 import logging
@@ -56,7 +56,7 @@ def setUp(self):
 
 
     def test_split_text_into_tokens(self):
-        '''Test de la fonction vectorization_tokenization.split_text_into_tokens'''
+        '''Testing function vectorization_tokenization.split_text_into_tokens'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", 5, None]
         words_sequences = [[['Chauffeur(se)', 'accompagnateur(trice)', 'pers'], ['pers', 'à', 'mob'], ['mob', '-', '5']],
                             [['Je', 'maîtrise', '12'], ['le', 'Permis', 'B'], ['12', 'langages', 'informatiques']],
diff --git a/tests/test_5_synonym_malefemale_replacement.py b/tests/test_5_synonym_malefemale_replacement.py
index 2e00628..f5a07f7 100644
--- a/tests/test_5_synonym_malefemale_replacement.py
+++ b/tests/test_5_synonym_malefemale_replacement.py
@@ -2,12 +2,12 @@
 
 ## Test - unit test of synonym_malefemale_replacement functions
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
@@ -27,8 +27,8 @@
 import importlib
 import numpy as np
 import pandas as pd
-from pe_semantic import utils
-from pe_semantic.preprocessing import synonym_malefemale_replacement
+from words_n_fun import utils
+from words_n_fun.preprocessing import synonym_malefemale_replacement
 
 # Disable logging
 import logging
@@ -56,7 +56,7 @@ def setUp(self):
 
 
     def test_remove_gender_synonyms(self):
-        '''Test de la fonction synonym_malefemale_replacement.remove_gender_synonyms'''
+        '''Testing function synonym_malefemale_replacement.remove_gender_synonyms'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", 5, None, "serveur/serveur(se), agriculteur (trice) blabla ouvrier/ ouvrière blabla aide apprenti boucher /aide apprentie bouchere"]
         docs_gender_syn_removed = ['Chauffeur   accompagnateur  pers à mob - 5 ans de expérience.', "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", None, None, 'serveur , agriculteur  blabla ouvrier blabla aide apprenti boucher']
 
@@ -65,7 +65,7 @@ def test_remove_gender_synonyms(self):
 
 
     def test_matching_words(self):
-        '''Test de la fonction synonym_malefemale_replacement.matching_words'''
+        '''Testing function synonym_malefemale_replacement.matching_words'''
         word1 = 'serveur'
         word2 = 'serveuse'
         result =  ('serveur', 'serveuse', 'serveur')
@@ -75,7 +75,7 @@ def test_matching_words(self):
 
 
     def test_update_synonyms_set(self):
-        '''Test de la fonction synonym_malefemale_replacement.update_synonyms_set'''
+        '''Testing function synonym_malefemale_replacement.update_synonyms_set'''
         synonyms_set = {}
         match = [('serveur', 'serveuse', ''),('boucher','ere',''),('boucher','bouchere','')]
         numligne = 1
diff --git a/tests/test_6_lemmatizer.py b/tests/test_6_lemmatizer.py
index 84c2c1c..02c04d1 100644
--- a/tests/test_6_lemmatizer.py
+++ b/tests/test_6_lemmatizer.py
@@ -2,17 +2,17 @@
 
 ## Test - unit test of lemmatizer (deprecated) functions
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -24,12 +24,15 @@
 
 # Utils libs
 import os
-import spacy
+try:
+    import spacy
+except ModuleNotFoundError:
+    raise unittest.SkipTest("Skipping all lemmatizer tests as spacy can't be imported.")
 import importlib
 import numpy as np
 import pandas as pd
-from pe_semantic import utils
-from pe_semantic.preprocessing import lemmatizer
+from words_n_fun import utils
+from words_n_fun.preprocessing import lemmatizer
 
 # Disable logging
 import logging
@@ -57,7 +60,7 @@ def setUp(self):
 
 
     def test_lemmatize(self):
-        '''Test de la fonction lemmatizer.lemmatize'''
+        '''Testing function lemmatizer.lemmatize'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "^Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Coordinateur d'Equipe d'Action Territoriale ", 5, None]
         docs_lemmatized = ['chauffeur se accompagnateur trice pers à mob 5 an de expérience', 'je maîtris 12 langage informatique dont le c j avoir le permis b', 'chauffeur se accompagnateur trice pers à mob 5 an de expérience', 'coordinateur d equipe d action territorial', None, None]
 
diff --git a/tests/test_7_stopwords.py b/tests/test_7_stopwords.py
index eb65de3..1acde12 100644
--- a/tests/test_7_stopwords.py
+++ b/tests/test_7_stopwords.py
@@ -2,17 +2,17 @@
 
 ## Test - unit test of stopwords functions
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -27,8 +27,8 @@
 import importlib
 import numpy as np
 import pandas as pd
-from pe_semantic import utils
-from pe_semantic.preprocessing import stopwords
+from words_n_fun import utils
+from words_n_fun.preprocessing import stopwords
 
 # Disable logging
 import logging
@@ -56,7 +56,7 @@ def setUp(self):
 
 
     def test_remove_stopwords(self):
-        '''Test de la fonction stopwords.remove_stopwords'''
+        '''Testing function stopwords.remove_stopwords'''
         docs = ["Chauffeur(se)  accompagnateur(trice) pers à mob - 5 ans de expérience.", "Je maîtrise 12 langages informatiques dont le C & j'ai le Permis B", "Coordinateur d'Equipe d'Action Territoriale ", 5, None]
         docs_stopwords_removed = ["Chauffeur()  accompagnateur(trice) pers  mob - 5 ans  expérience.", "Je maîtrise 12 langages informatiques   C & '  Permis B", "Coordinateur 'Equipe 'Action Territoriale ", None, None]
         docs_stopwords_removed_default = ["Chauffeur()  accompagnateur(trice) pers  mob - 5 ans  expérience.", "Je maîtrise 12 langages informatiques   C & '  Permis B", "Coordinateur 'Equipe 'Action Territoriale ", None, None]
@@ -85,19 +85,19 @@ def test_remove_stopwords(self):
 
 
     def test_stopwords_ascii(self):
-        '''Test de la fonction stopwords.stopwords_ascii'''
+        '''Testing function stopwords.stopwords_ascii'''
         # Vérification du fonctionnement type
         self.assertEqual(type(stopwords.stopwords_ascii()), list)
 
 
     def test_stopwords_nltk(self):
-        '''Test de la fonction stopwords.stopwords_nltk'''
+        '''Testing function stopwords.stopwords_nltk'''
         # Vérification du fonctionnement type
         self.assertEqual(type(stopwords.stopwords_nltk()), list)
         self.assertEqual(type(stopwords.stopwords_nltk(try_update=True)), list)
 
     def test_stopwords_nltk_ascii(self):
-        '''Test de la fonction stopwords.stopwords_nltk_ascii'''
+        '''Testing function stopwords.stopwords_nltk_ascii'''
         # Vérification du fonctionnement type
         self.assertEqual(type(stopwords.stopwords_nltk_ascii()), list)
 
diff --git a/tests/test_8_split_sentences.py b/tests/test_8_split_sentences.py
index f25cb8a..ba4a4d0 100644
--- a/tests/test_8_split_sentences.py
+++ b/tests/test_8_split_sentences.py
@@ -2,17 +2,17 @@
 
 ## Test - unit test of split sentences functions
 # Copyright (C) <2018-2022>  <Agence Data Services, DSI Pôle Emploi>
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #
@@ -26,7 +26,7 @@
 import os
 import numpy as np
 import pandas as pd
-from pe_semantic.preprocessing import split_sentences
+from words_n_fun.preprocessing import split_sentences
 
 # Disable logging
 import logging