Repository: asreview/template-extension-new-model
Pull request #10: Update code for 2.0
Author: login: jteijema
Created: 2025-05-08T12:23:05Z | Merged: 2025-05-14T18:28:22Z

Updates to a pyproject.toml and add some tests


Diff:
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
new file mode 100644
index 0000000..298d798
--- /dev/null
+++ b/.github/workflows/pytest.yml
@@ -0,0 +1,27 @@
+name: Pytest
+
+on: [push, pull_request]
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-latest, windows-latest]
+        python-version: ["3.10", "3.12", "3.13"]
+        architecture: [x64]
+    name: Python ${{ matrix.python-version }} on ${{ matrix.os }} with architecture ${{ matrix.architecture }}
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: ${{ matrix.python-version }}
+          architecture: ${{ matrix.architecture }}
+
+      - name: Install package and run tests
+        run: |
+          pip install .
+          pip install pytest
+          pytest tests -v
diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml
new file mode 100644
index 0000000..34286e7
--- /dev/null
+++ b/.github/workflows/ruff.yml
@@ -0,0 +1,19 @@
+name: Ruff
+
+on: [push]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v4
+    - uses: actions/setup-python@v5
+      with:
+        python-version: '3.x'
+        architecture: 'x64'
+    - name: Install ruff
+      run: |
+        pip install ruff
+    - name: Lint python with ruff
+      run: |
+        ruff check .
diff --git a/.gitignore b/.gitignore
index b6e4761..8c70122 100644
--- a/.gitignore
+++ b/.gitignore
@@ -127,3 +127,4 @@ dmypy.json
 
 # Pyre type checker
 .pyre/
+asreviewcontrib/models/_version.py
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..9b38853
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+    "python.testing.pytestArgs": [
+        "tests"
+    ],
+    "python.testing.unittestEnabled": false,
+    "python.testing.pytestEnabled": true
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index c4172a4..bbcd210 100644
--- a/README.md
+++ b/README.md
@@ -3,14 +3,17 @@
 ASReview has support for extensions, which enable you to seamlessly integrate
 your own programs with the ASReview framework. These extensions can extend the
 software with new classifiers, query strategies, balance strategies, and feature
-extraction techniques. This template can be used to write such an extension
-(add a new SKLearn naive Bayes classifier with default settings in this case).
+extraction techniques. This template can be used to write such an extension (add
+a new SKLearn naive Bayes classifier with default settings in this case).
 
-See the section [Extensions](https://asreview.readthedocs.io/en/latest/extensions_dev.html) on ReadTheDocs for more information on writing extensions.
+See the section
+[Extensions](https://asreview.readthedocs.io/en/latest/extensions_dev.html) on
+ReadTheDocs for more information on writing extensions.
 
 ## Getting started
 
-Click the `Use this template` button and add/modify the algorithms. Install your new classifier with
+Click the `Use this template` button and add/modify the algorithms. Install your
+new classifier with
 
 ```bash
 pip install .
@@ -27,10 +30,11 @@ and replace `{USER_NAME}` and `{REPO_NAME}` by your own details.
 ## Usage
 
 The new classifier `nb_example` is defined in
-[`asreviewcontrib/models/nb_default_param.py`](asreviewcontrib/models/nb_default_param.py) and can be used in a simulation.
+[`asreviewcontrib/models/example_model.py`](asreviewcontrib/models/example_model.py)
+and can be used in a simulation.
 
 ```bash
-asreview simulate example_data_file.csv -m nb_example
+asreview simulate synergy:donners_2021 -c nb_example -q max -e tfidf
 ```
 
 ## License
diff --git a/asreviewcontrib/models/__init__.py b/asreviewcontrib/models/__init__.py
index 81d3ebe..e69de29 100644
--- a/asreviewcontrib/models/__init__.py
+++ b/asreviewcontrib/models/__init__.py
@@ -1 +0,0 @@
-from asreviewcontrib.models.nb_default_param import NaiveBayesDefaultParamsModel
diff --git a/asreviewcontrib/models/example_model.py b/asreviewcontrib/models/example_model.py
new file mode 100644
index 0000000..1d8c989
--- /dev/null
+++ b/asreviewcontrib/models/example_model.py
@@ -0,0 +1,40 @@
+__all__ = ["NaiveBayesDefaultParamsModel"]
+
+from sklearn.naive_bayes import MultinomialNB
+
+class NaiveBayesDefaultParamsModel:
+    """Naive Bayes classifier
+
+    The Naive Bayes classifier with the default SKLearn parameters.
+    """
+
+    name = "nb_example"
+    label = "The Naive Bayes classifier"
+
+    def __init__(self):
+        super().__init__()
+        self._model = MultinomialNB()
+
+    def fit(self, X, y, sample_weight=None):
+        """Fit the model to the data."""
+        return self._model.fit(X, y, sample_weight=sample_weight)
+
+    def predict_proba(self, X):
+        """Get the inclusion probability for each sample."""
+        return self._model.predict_proba(X)
+
+
+class NaiveBayesDefaultParamsModelAlternative(MultinomialNB):
+    """Naive Bayes classifier
+
+    The Naive Bayes classifier with the default SKLearn parameters. Note how
+    this class extends the MultinomialNB class from SKLearn. Since this class
+    already has the fit and predict_proba methods, we can simply use it as is.
+    This is a good example of how to create a model that is a wrapper around an
+    existing SKLearn model. This is useful if you want to create a model that
+    has the same interface as the other models in ASReview, but you want to use
+    an existing SKLearn model.
+    """
+
+    name = "nb_example_alternative"
+    label = "The alternative implementation of the Naive Bayes classifier"
diff --git a/asreviewcontrib/models/nb_default_param.py b/asreviewcontrib/models/nb_default_param.py
deleted file mode 100644
index cdc960c..0000000
--- a/asreviewcontrib/models/nb_default_param.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from sklearn.naive_bayes import MultinomialNB
-
-from asreview.models.classifiers.base import BaseTrainClassifier
-
-
-class NaiveBayesDefaultParamsModel(BaseTrainClassifier):
-    """Naive Bayes classifier
-
-    The Naive Bayes classifier with the default SKLearn parameters.
-    """
-
-    name = "nb_example"
-
-    def __init__(self):
-
-        super().__init__()
-        self._model = MultinomialNB()
-
-        def fit(self, X, y):
-            """Fit the model to the data."""
-            return self._model.fit(X, y)
-
-        def predict_proba(self, X):
-            """Get the inclusion probability for each sample."""
-            return self._model.predict_proba(X)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..d658cdd
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,47 @@
+[project]
+name = "asreview-template-model-extension"
+description = "Example model extension"
+readme = "README.md"
+authors = [
+    { name = "ASReview LAB developers", email = "asreview@uu.nl" }
+]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "License :: OSI Approved :: MIT License",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12"
+]
+keywords = ["systematic review"]
+requires-python = ">=3.10"
+dynamic = ["version"]
+dependencies = ["asreview>=2,<3"]
+
+[project.urls]
+Issues = "https://github.com/asreview/template-extension-new-model/issues"
+Repository = "https://github.com/asreview/template-extension-new-model/"
+
+[build-system]
+build-backend = 'setuptools.build_meta'
+requires = ["setuptools>=45", "setuptools_scm[toml]>=6.2"]
+
+[tool.setuptools]
+packages = ["asreviewcontrib", "asreviewcontrib.models",]
+
+[tool.setuptools_scm]
+write_to = "asreviewcontrib/models/_version.py"
+
+[project.entry-points."asreview.models.classifiers"]
+"nb_example" = "asreviewcontrib.models.example_model:NaiveBayesDefaultParamsModel"
+"nb_example_alternative" = "asreviewcontrib.models.example_model:NaiveBayesDefaultParamsModelAlternative"
+
+
+[tool.ruff]
+select = ["E", "F", "UP", "B"]
+
+[tool.ruff.isort]
+force-single-line = true
+
+[tool.pytest.ini_options]
+minversion = "6.0"
+testpaths = ["tests"]
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 9ae4012..0000000
--- a/setup.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from setuptools import setup
-from setuptools import find_namespace_packages
-
-setup(
-    name='asreview-template-model-extension',
-    version='1.0',
-    description='Example classifier extension',
-    url='https://github.com/asreview/asreview',
-    author='ASReview team',
-    author_email='asreview@uu.nl',
-    classifiers=[
-        'Development Status :: 4 - Beta',
-        'License :: OSI Approved :: MIT License',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.6',
-        'Programming Language :: Python :: 3.7',
-    ],
-    keywords='systematic review',
-    packages=find_namespace_packages(include=['asreviewcontrib.*']),
-    python_requires='~=3.6',
-    install_requires=[
-        'asreview>=1.0'
-    ],
-    entry_points={
-        'asreview.models.classifiers': [
-            'nb_example = asreviewcontrib.models:NaiveBayesDefaultParamsModel',
-        ],
-        'asreview.models.feature_extraction': [
-            # define feature_extraction algorithms
-        ],
-        'asreview.models.balance': [
-            # define balance strategy algorithms
-        ],
-        'asreview.models.query': [
-            # define query strategy algorithms
-        ]
-    },
-    project_urls={
-        'Bug Reports': 'https://github.com/asreview/asreview/issues',
-        'Source': 'https://github.com/asreview/asreview/',
-    },
-)
diff --git a/tests/data/generic_labels.csv b/tests/data/generic_labels.csv
new file mode 100644
index 0000000..3a627c4
--- /dev/null
+++ b/tests/data/generic_labels.csv
@@ -0,0 +1,7 @@
+abstract,alternate_title2,alternate_title3,file_attachments2,authors,id,issn,keywords,note,number,place_published,title,publication_year,publisher,secondary_authors,start_page,type_of_reference,label_included,url,volume
+"BACKGROUND: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.  RESULTS: Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. CONCLUSIONS: Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium.",lorem,Lorem,http://example.com,"['Marx, Karl', 'Lindgren, Astrid']",12345,1932-6208,"['Pippi', 'Nordwind', 'Piraten']",1008150341,3,United States,Title of reference,2014//,Fun Factory,"['Glattauer, Daniel']",e0815,JOUR,1,http://example_url.com,9
+"BACKGROUND: Lorem dammed ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.  RESULTS: Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. CONCLUSIONS: Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium.",lorem,Lorem,http://example2.com,"['Marxus, Karlus', 'Lindgren, Astrid']",12345,1732-4208,"['Pippi Langstrumpf', 'Nordwind', 'Piraten']",1228150341,3,Germany,The title of the reference,2006//,Dark Factory,"['Glattauer, Daniel']",e0815341,JOUR,0,http://example_url.com,6
+"BACKGROUND: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.  RESULTS: Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. CONCLUSIONS: Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium.",lorem,Lorem,http://example.com,"['Marx, Karl', 'Lindgren, Astrid']",12345,1932-6208,"['Pippi', 'Nordwind', 'Piraten']",1008150341,3,United States,Title of reference,2014//,Fun Factory,"['Glattauer, Daniel']",e0815,JOUR,1,http://example_url.com,9
+"BACKGROUND: Lorem dammed ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.  RESULTS: Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. CONCLUSIONS: Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium.",lorem,Lorem,http://example2.com,"['Marxus, Karlus', 'Lindgren, Astrid']",12345,1732-4208,"['Pippi Langstrumpf', 'Nordwind', 'Piraten']",1228150341,3,Germany,The title of the reference,2006//,Dark Factory,"['Glattauer, Daniel']",e0815341,JOUR,0,http://example_url.com,6
+"BACKGROUND: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.  RESULTS: Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. CONCLUSIONS: Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium.",lorem,Lorem,http://example.com,"['Marx, Karl', 'Lindgren, Astrid']",12345,1932-6208,"['Pippi', 'Nordwind', 'Piraten']",1008150341,3,United States,Title of reference,2014//,Fun Factory,"['Glattauer, Daniel']",e0815,JOUR,1,http://example_url.com,9
+"BACKGROUND: Lorem dammed ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.  RESULTS: Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. CONCLUSIONS: Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium.",lorem,Lorem,http://example2.com,"['Marxus, Karlus', 'Lindgren, Astrid']",12345,1732-4208,"['Pippi Langstrumpf', 'Nordwind', 'Piraten']",1228150341,3,Germany,The title of the reference,2006//,Dark Factory,"['Glattauer, Daniel']",e0815341,JOUR,0,http://example_url.com,6
diff --git a/tests/test_model.py b/tests/test_model.py
new file mode 100644
index 0000000..80431d4
--- /dev/null
+++ b/tests/test_model.py
@@ -0,0 +1,41 @@
+import pandas as pd
+from pathlib import Path
+
+import asreview as asr
+from asreview.models.queriers import Max
+from asreview.models.feature_extractors import Tfidf
+from asreviewcontrib.models.example_model import NaiveBayesDefaultParamsModel
+from asreviewcontrib.models.example_model import NaiveBayesDefaultParamsModelAlternative
+
+def test_single_nb_example():
+    # Load dataset
+    dataset_path = Path("tests/data/generic_labels.csv")
+    data = asr.load_dataset(dataset_path)
+
+    first_active_learning_cycle = asr.ActiveLearningCycle(
+        classifier=NaiveBayesDefaultParamsModel(),
+        feature_extractor=Tfidf(),
+        balancer=None,
+        querier=Max(),
+    )
+
+    second_active_learning_cycle = asr.ActiveLearningCycle(
+        classifier=NaiveBayesDefaultParamsModelAlternative(),
+        feature_extractor=Tfidf(),
+        balancer=None,
+        querier=Max(),
+    )
+
+    # Run simulation
+    simulate = asr.Simulate(
+        X=data,
+        labels=data["included"],
+        cycles=[first_active_learning_cycle, second_active_learning_cycle],
+    )
+    simulate.label([0, 1])
+    simulate.review()
+
+    # Simple checks
+    assert isinstance(simulate._results, pd.DataFrame)
+    assert simulate._results.shape[0] > 2 and simulate._results.shape[0] <= 6
+    assert "nb_example" in simulate._results["classifier"].unique()


Reviews:
user: login: J535D165
body: Thanks!
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T13:56:38Z
user: login: J535D165
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T18:22:41Z
user: login: J535D165
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T18:27:11Z
user: login: J535D165
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T18:27:40Z
user: login: J535D165
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-14T18:28:06Z

Review comments:
path: asreviewcontrib/models/_version.py
user: login: J535D165
body: This should be ignored https://github.com/asreview/asreview/blob/abeea2ef4fffca74e8d3e49340553027ce723e0a/.gitignore#L72-L73
created_at: 2025-05-08T13:56:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,49 @@
+[project]
+name = "asreview-template-model-extension"
+description = "Example model extension"
+readme = "README.md"
+authors = [
+    { name = "ASReview LAB developers", email = "asreview@uu.nl" }
+]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "License :: OSI Approved :: MIT License",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12"
+]
+keywords = ["systematic review"]
+requires-python = ">=3.10"
+dynamic = ["version"]
+dependencies = [
+    "asreview>=2.0rc4",
path: pyproject.toml
user: login: J535D165
body: ```suggestion
    "asreview>=2.0",
```
created_at: 2025-05-14T18:22:41Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,49 @@
+[project]
+name = "asreview-template-model-extension"
+description = "Example model extension"
+readme = "README.md"
+authors = [
+    { name = "ASReview LAB developers", email = "asreview@uu.nl" }
+]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "License :: OSI Approved :: MIT License",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12"
+]
+keywords = ["systematic review"]
+requires-python = ">=3.10"
+dynamic = ["version"]
+dependencies = [
+    "asreview>=2.0",
+]
path: pyproject.toml
user: login: J535D165
body: ```suggestion
dependencies = [asreview>=2,<3]
```
created_at: 2025-05-14T18:27:11Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,47 @@
+[project]
+name = "asreview-template-model-extension"
+description = "Example model extension"
+readme = "README.md"
+authors = [
+    { name = "ASReview LAB developers", email = "asreview@uu.nl" }
+]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "License :: OSI Approved :: MIT License",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12"
+]
+keywords = ["systematic review"]
+requires-python = ">=3.10"
+dynamic = ["version"]
+dependencies = [asreview>=2,<3]
path: pyproject.toml
user: login: J535D165
body: ```suggestion
dependencies = ["asreview>=2,<3"]
```
created_at: 2025-05-14T18:27:40Z
author_association: MEMBER

PR comments:
user: login: jteijema
created_at: 2025-05-08T13:33:43Z
body: ![image](https://github.com/user-attachments/assets/d845efca-c334-4cf2-87ac-c6273d98bafa)

author_association: MEMBER
user: login: jteijema
created_at: 2025-05-08T13:34:27Z
body: ![image](https://github.com/user-attachments/assets/ff237023-26f0-46d8-bb71-18c1c83bd582)

author_association: MEMBER
user: login: jteijema
created_at: 2025-05-08T13:37:56Z
body: I've added new tests, but this will require a repo manager to enable actions @J535D165 
author_association: MEMBER
user: login: J535D165
created_at: 2025-05-14T18:28:29Z
body: Thanks a lot
author_association: MEMBER