diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4a210d46633be4608625268995876ea1e50a1bed..de9b93d7a3dfb0040176f40c591df112bb2ec38f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,21 +1,2 @@
 include:
-  - project: "cdos-pub/pycode-quality"
-    ref: "main"
-    file:
-      - ".gitlab/ci/static-analysis.yml"
-      - ".gitlab/ci/pip.yml"
-
-stages:
-  - Static Analysis
-  - Test
-  - Pip
-
-Tests:
-  image: "registry.forgemia.inra.fr/cdos-pub/pycode-quality/python-venv:3.10"
-  stage: Test
-  except: [main, tags]
-  script:
-    - pip install pip --upgrade
-    - pip install .[test]
-    - python3 tests/extensions_test.py
-
+  - ".gitlab/ci/base.yml"
diff --git a/.gitlab/ci/base.yml b/.gitlab/ci/base.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bff04505e8bbc1e91cf35f5b09f0c9b2ed5350b4
--- /dev/null
+++ b/.gitlab/ci/base.yml
@@ -0,0 +1,23 @@
+include:
+  - project: "cdos-pub/pycode-quality"
+    ref: "main"
+    file:
+      - ".gitlab/ci/static-analysis.yml"
+      - ".gitlab/ci/pip.yml"
+
+variables:
+  PIP_EXTRA_INDEX_URL: https://forgemia.inra.fr/api/v4/projects/10919/packages/pypi/simple
+  PACKAGE_INSTALL_EXTRAS: "[test]"
+
+stages:
+  - Static Analysis
+  - Test
+  - Pip
+
+Tests:
+  extends: .static_analysis_with_pip_install
+  stage: Test
+  allow_failure: false
+  script:
+    - pytest tests/
+
diff --git a/LICENSE b/LICENSE
index a6b5cd48345ea32d498426b024f650df9d807fff..6c258c1acae69c5bb3e39f443b166f51bdd3c05a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
       same "printed page" as the copyright notice for easier
       identification within third-party archives.
 
-   Copyright 2024 INRAE
+   Copyright 2024-2025 INRAE
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index 71f0b5795d11d4e5d1f6867fc6a786a53ad95635..017b99cd60e5c0d7ad67779df93e8dbf4d6c689d 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,13 @@
 This module is a helper to build STAC extensions carrying metadata defined with 
 pydantic models.
 
+## Installation
+
+```
+PIP_EXTRA_INDEX_URL=https://forgemia.inra.fr/api/v4/projects/10919/packages/pypi/simple
+pip install stac-extension-genmeta
+```
+
 ## Example
 
 Simple example in 4 steps.
diff --git a/pyproject.toml b/pyproject.toml
index 105cdbdc163d0d595be3cb493328eb1bb4c92bfe..fa832e397d2636b37976454d237246d357b23e90 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,7 +8,7 @@ description = "Helper to build custom STAC extensions based on pydantic models"
 authors = [
     { name = "Rémi Cresson", email = "remi.cresson@inrae.fr" },
 ]
-requires-python = ">=3.7"
+requires-python = ">=3.8"
 dependencies = [
     "pydantic >= 2.0.0",
     "pystac",
@@ -23,14 +23,11 @@ classifiers = [
     "Operating System :: OS Independent",
 ]
 
-[project.scripts]
-genmeta_cli = "stac_extension_genmeta.cli:app"
-
 [tool.setuptools]
 packages = ["stac_extension_genmeta"]
 
 [project.optional-dependencies]
-test = ["requests", "pystac[validation]"]
+test = ["requests", "pystac[validation]", "pytest"]
 
 [tool.setuptools.dynamic]
 version = { attr = "stac_extension_genmeta.__version__" }
diff --git a/stac_extension_genmeta/__init__.py b/stac_extension_genmeta/__init__.py
index 55989ae2477f21ad87fd52cc857dcf7b75aa550d..9c021c3a4624d28fef4e38c76210996a27329395 100644
--- a/stac_extension_genmeta/__init__.py
+++ b/stac_extension_genmeta/__init__.py
@@ -2,4 +2,4 @@
 
 from .core import create_extension_cls, BaseExtensionModel  # noqa
 
-__version__ = "0.1.3-dev1"
+__version__ = "0.1.3-dev3"
diff --git a/stac_extension_genmeta/core.py b/stac_extension_genmeta/core.py
index 15ace43924468bdb9426a8048f33809a22810aee..2f53023a8970f6485b9453e8b0cc302d16001476 100644
--- a/stac_extension_genmeta/core.py
+++ b/stac_extension_genmeta/core.py
@@ -1,15 +1,13 @@
-"""
-Processing extension
-"""
+"""Generic metadata creation."""
 
+from collections.abc import Iterable
+import json
+import re
 from typing import Any, Generic, TypeVar, Union, cast
 from pystac.extensions.base import PropertiesExtension, ExtensionManagementMixin
 import pystac
 from pydantic import BaseModel, ConfigDict
-import re
-from collections.abc import Iterable
 from .schema import generate_schema
-import json
 
 
 class BaseExtensionModel(BaseModel):
@@ -19,19 +17,7 @@ class BaseExtensionModel(BaseModel):
 
 
 def create_extension_cls(model_cls: BaseModel, schema_uri: str) -> PropertiesExtension:
-    """
-    This method creates a pystac extension from a pydantic model.
-
-    Args:
-        model_cls: pydantic model class
-        schema_uri: schema URI
-
-    Returns:
-        pystac extension class
-
-    """
-
-    # check URI
+    """This method creates a pystac extension from a pydantic model."""
     if not re.findall(r"(?:(\/v\d\.(?:\d+\.)*\d+\/+))", schema_uri):
         raise ValueError(
             "The schema_uri must contain the version in the form 'vX.Y.Z'"
@@ -45,7 +31,10 @@ def create_extension_cls(model_cls: BaseModel, schema_uri: str) -> PropertiesExt
         PropertiesExtension,
         ExtensionManagementMixin[Union[pystac.Item, pystac.Collection]],
     ):
+        """Custom extension class."""
+
         def __init__(self, obj: T):
+            """Initializer."""
             if isinstance(obj, pystac.Item):
                 self.properties = obj.properties
             elif isinstance(obj, (pystac.Asset, pystac.Collection)):
@@ -65,10 +54,11 @@ def create_extension_cls(model_cls: BaseModel, schema_uri: str) -> PropertiesExt
             self.md = model_cls(**props) if props else None
 
         def __getattr__(self, item):
-            # forward getattr to self.md
+            """Forward getattr to self.md."""
             return getattr(self.md, item) if self.md else None
 
         def apply(self, md: model_cls = None, **kwargs) -> None:
+            """Apply the metadata."""
             if md is None and not kwargs:
                 raise ValueError("At least `md` or kwargs is required")
 
@@ -86,10 +76,12 @@ def create_extension_cls(model_cls: BaseModel, schema_uri: str) -> PropertiesExt
 
         @classmethod
         def get_schema_uri(cls) -> str:
+            """Get schema URI."""
             return schema_uri
 
         @classmethod
         def get_schema(cls) -> dict:
+            """Get schema as dict."""
             return generate_schema(
                 model_cls=model_cls,
                 title=f"STAC extension from {model_cls.__name__} model",
@@ -99,6 +91,7 @@ def create_extension_cls(model_cls: BaseModel, schema_uri: str) -> PropertiesExt
 
         @classmethod
         def print_schema(cls):
+            """Print schema."""
             print(
                 "\033[92mPlease copy/paste the schema below in the right place "
                 f"in the repository so it can be accessed from \033[94m"
@@ -107,18 +100,20 @@ def create_extension_cls(model_cls: BaseModel, schema_uri: str) -> PropertiesExt
 
         @classmethod
         def export_schema(cls, json_file):
-            with open(json_file, "w") as f:
+            """Export schema."""
+            with open(json_file, "w", encoding="utf-8") as f:
                 json.dump(cls.get_schema(), f, indent=2)
 
         @classmethod
         def ext(cls, obj: T, add_if_missing: bool = False) -> model_cls.__name__:
+            """Create the extension."""
             if isinstance(obj, pystac.Item):
                 cls.ensure_has_extension(obj, add_if_missing)
                 return cast(CustomExtension[T], ItemCustomExtension(obj))
-            elif isinstance(obj, pystac.Asset):
+            if isinstance(obj, pystac.Asset):
                 cls.ensure_owner_has_extension(obj, add_if_missing)
                 return cast(CustomExtension[T], AssetCustomExtension(obj))
-            elif isinstance(obj, pystac.Collection):
+            if isinstance(obj, pystac.Collection):
                 cls.ensure_has_extension(obj, add_if_missing)
                 return cast(CustomExtension[T], CollectionCustomExtension(obj))
             raise pystac.ExtensionTypeError(
@@ -126,23 +121,29 @@ def create_extension_cls(model_cls: BaseModel, schema_uri: str) -> PropertiesExt
             )
 
     class ItemCustomExtension(CustomExtension[pystac.Item]):
-        pass
+        """Item custom extension."""
 
     class AssetCustomExtension(CustomExtension[pystac.Asset]):
+        """Asset custom extension."""
+
         asset_href: str
         properties: dict[str, Any]
         additional_read_properties: Iterable[dict[str, Any]] | None = None
 
         def __init__(self, asset: pystac.Asset):
+            """Initializer."""
             self.asset_href = asset.href
             self.properties = asset.extra_fields
             if asset.owner and isinstance(asset.owner, pystac.Item):
                 self.additional_read_properties = [asset.owner.properties]
 
     class CollectionCustomExtension(CustomExtension[pystac.Collection]):
+        """Collection curstom extension."""
+
         properties: dict[str, Any]
 
         def __init__(self, collection: pystac.Collection):
+            """Initializer."""
             self.properties = collection.extra_fields
 
     CustomExtension.__name__ = f"CustomExtensionFrom{model_cls.__name__}"
diff --git a/stac_extension_genmeta/schema.py b/stac_extension_genmeta/schema.py
index ffe1cd7117fe7301b34fedb2f5ccc02b09c5392f..35d33f68d96089132edf3cde2b63334ee1479101 100644
--- a/stac_extension_genmeta/schema.py
+++ b/stac_extension_genmeta/schema.py
@@ -1,12 +1,12 @@
+"""Generate the json schema."""
+
 from pydantic import BaseModel
 
 
 def generate_schema(
-        model_cls: BaseModel,
-        title: str,
-        description: str,
-        schema_uri: str
+    model_cls: BaseModel, title: str, description: str, schema_uri: str
 ) -> dict:
+    """Generate the schema."""
     properties = model_cls.model_json_schema()
     # prune "required"
     properties.pop("required", None)
@@ -21,116 +21,66 @@ def generate_schema(
                 "allOf": [
                     {
                         "type": "object",
-                        "required": [
-                            "type",
-                            "properties",
-                            "assets",
-                            "links"
-                        ],
+                        "required": ["type", "properties", "assets", "links"],
                         "properties": {
-                            "type": {
-                                "const": "Feature"
-                            },
-                            "properties": {
-                                "$ref": "#/definitions/fields"
-                            },
-                            "assets": {
-                                "$ref": "#/definitions/assets"
-                            },
-                            "links": {
-                                "$ref": "#/definitions/links"
-                            }
-                        }
+                            "type": {"const": "Feature"},
+                            "properties": {"$ref": "#/definitions/fields"},
+                            "assets": {"$ref": "#/definitions/assets"},
+                            "links": {"$ref": "#/definitions/links"},
+                        },
                     },
-                    {
-                        "$ref": "#/definitions/stac_extensions"
-                    }
-                ]
+                    {"$ref": "#/definitions/stac_extensions"},
+                ],
             },
             {
                 "$comment": "This is the schema for STAC Collections.",
                 "allOf": [
                     {
                         "type": "object",
-                        "required": [
-                            "type"
-                        ],
+                        "required": ["type"],
                         "properties": {
-                            "type": {
-                                "const": "Collection"
-                            },
-                            "assets": {
-                                "$ref": "#/definitions/assets"
-                            },
-                            "item_assets": {
-                                "$ref": "#/definitions/assets"
-                            },
-                            "links": {
-                                "$ref": "#/definitions/links"
-                            }
-                        }
+                            "type": {"const": "Collection"},
+                            "assets": {"$ref": "#/definitions/assets"},
+                            "item_assets": {"$ref": "#/definitions/assets"},
+                            "links": {"$ref": "#/definitions/links"},
+                        },
                     },
-                    {
-                        "$ref": "#/definitions/fields"
-                    },
-                    {
-                        "$ref": "#/definitions/stac_extensions"
-                    }
-                ]
+                    {"$ref": "#/definitions/fields"},
+                    {"$ref": "#/definitions/stac_extensions"},
+                ],
             },
             {
                 "$comment": "This is the schema for STAC Catalogs.",
                 "allOf": [
                     {
                         "type": "object",
-                        "required": [
-                            "type"
-                        ],
+                        "required": ["type"],
                         "properties": {
-                            "type": {
-                                "const": "Catalog"
-                            },
-                            "links": {
-                                "$ref": "#/definitions/links"
-                            }
-                        }
+                            "type": {"const": "Catalog"},
+                            "links": {"$ref": "#/definitions/links"},
+                        },
                     },
-                    {
-                        "$ref": "#/definitions/fields"
-                    },
-                    {
-                        "$ref": "#/definitions/stac_extensions"
-                    }
-                ]
-            }
+                    {"$ref": "#/definitions/fields"},
+                    {"$ref": "#/definitions/stac_extensions"},
+                ],
+            },
         ],
         "definitions": {
             "stac_extensions": {
                 "type": "object",
-                "required": [
-                    "stac_extensions"
-                ],
+                "required": ["stac_extensions"],
                 "properties": {
                     "stac_extensions": {
                         "type": "array",
-                        "contains": {
-                            "const": schema_uri
-                        }
+                        "contains": {"const": schema_uri},
                     }
-                }
-            },
-            "links": {
-                "type": "array",
-                "items": {
-                    "$ref": "#/definitions/fields"
-                }
+                },
             },
+            "links": {"type": "array", "items": {"$ref": "#/definitions/fields"}},
             "assets": {
                 "type": "object",
-                "additionalProperties": {
-                    "$ref": "#/definitions/fields"
-                }
+                "additionalProperties": {"$ref": "#/definitions/fields"},
             },
-            "fields": properties
-        }
+            "fields": properties,
+        },
     }
diff --git a/stac_extension_genmeta/testing.py b/stac_extension_genmeta/testing.py
index f134f7c78253c3797b413f172e654cdf23061557..319ae1614f9c4bf03e50697111fe6ffff573c70b 100644
--- a/stac_extension_genmeta/testing.py
+++ b/stac_extension_genmeta/testing.py
@@ -1,12 +1,16 @@
-import pystac
-from datetime import datetime
+"""Testing module."""
+
+import os
 import random
 import json
-import requests
 import difflib
+from datetime import datetime
+import requests
+import pystac
 
 
 def create_dummy_item(date=None):
+    """Create dummy item."""
     if not date:
         date = datetime.now().replace(year=1999)
 
@@ -17,21 +21,19 @@ def create_dummy_item(date=None):
     geom = {
         "type": "Polygon",
         "coordinates": [
-            [[4.032730583418401, 43.547450099338604],
-             [4.036414917971517, 43.75162726634343],
-             [3.698685718905037, 43.75431706444037],
-             [3.6962018175925073, 43.55012996681564],
-             [4.032730583418401, 43.547450099338604]]
-        ]
+            [
+                [4.032730583418401, 43.547450099338604],
+                [4.036414917971517, 43.75162726634343],
+                [3.698685718905037, 43.75431706444037],
+                [3.6962018175925073, 43.55012996681564],
+                [4.032730583418401, 43.547450099338604],
+            ]
+        ],
     }
-    asset = pystac.Asset(
-        href="https://example.com/SP67_FR_subset_1.tif"
-    )
+    asset = pystac.Asset(href="https://example.com/SP67_FR_subset_1.tif")
     val = f"item_{random.uniform(10000, 80000)}"
     spat_extent = pystac.SpatialExtent([[0, 0, 2, 3]])
-    temp_extent = pystac.TemporalExtent(
-        intervals=[(None, None)]
-    )
+    temp_extent = pystac.TemporalExtent(intervals=[(None, None)])
 
     item = pystac.Item(
         id=val,
@@ -41,7 +43,7 @@ def create_dummy_item(date=None):
         properties={},
         assets={"ndvi": asset},
         href="https://example.com/collections/collection-test3/items/{val}",
-        collection="collection-test3"
+        collection="collection-test3",
     )
 
     col = pystac.Collection(
@@ -59,23 +61,19 @@ METHODS = ["arg", "md", "dict"]
 
 
 def basic_test(
-        ext_md,
-        ext_cls,
-        item_test: bool = True,
-        asset_test: bool = True,
-        collection_test: bool = True,
-        validate: bool = True
+    ext_md,
+    ext_cls,
+    asset_test: bool = True,
+    collection_test: bool = True,
+    validate: bool = True,
 ):
-    print(
-        f"Extension metadata model: \n{ext_md.__class__.model_json_schema()}"
-    )
+    """Perform the basic testing of the extension class."""
+    print(f"Extension metadata model: \n{ext_md.__class__.model_json_schema()}")
 
     ext_cls.print_schema()
 
     def apply(stac_obj, method="arg"):
-        """
-        Apply the extension to the item
-        """
+        """Apply the extension to the item."""
         print(f"Check extension applied to {stac_obj.__class__.__name__}")
         ext = ext_cls.ext(stac_obj, add_if_missing=True)
         if method == "arg":
@@ -83,23 +81,16 @@ def basic_test(
         elif method == "md":
             ext.apply(md=ext_md)
         elif method == "dict":
-            d = {
-                name: getattr(ext_md, name)
-                for name in ext_md.model_fields
-            }
+            d = {name: getattr(ext_md, name) for name in ext_md.model_fields}
             print(f"Passing kwargs: {d}")
             ext.apply(**d)
 
     def print_item(item):
-        """
-        Print item as JSON
-        """
+        """Print item as JSON."""
         print(json.dumps(item.to_dict(), indent=2))
 
     def comp(stac_obj):
-        """
-        Compare the metadata carried by the stac object with the expected metadata.
-        """
+        """Compare the metadata carried by the stac object with the expected metadata."""
         read_ext = ext_cls(stac_obj)
         for field in ext_md.__class__.model_fields:
             ref = getattr(ext_md, field)
@@ -107,9 +98,7 @@ def basic_test(
             assert got == ref, f"'{field}': values differ: {got} (expected {ref})"
 
     def test_item(method):
-        """
-        Test extension against item
-        """
+        """Test extension against item."""
         item, _ = create_dummy_item()
         apply(item, method)
         print_item(item)
@@ -119,9 +108,7 @@ def basic_test(
         comp(item)
 
     def test_asset(method):
-        """
-        Test extension against asset
-        """
+        """Test extension against asset."""
         item, _ = create_dummy_item()
         apply(item.assets["ndvi"], method)
         print_item(item)
@@ -131,10 +118,8 @@ def basic_test(
         comp(item.assets["ndvi"])
 
     def test_collection(method):
-        """
-        Test extension against collection
-        """
-        item, col = create_dummy_item()
+        """Test extension against collection."""
+        _, col = create_dummy_item()
         print_item(col)
         apply(col, method)
         print_item(col)
@@ -144,9 +129,8 @@ def basic_test(
         comp(col)
 
     for method in METHODS:
-        if item_test:
-            print(f"Test item with {method} args passing strategy")
-            test_item(method)
+        print(f"Test item with {method} args passing strategy")
+        test_item(method)
         if asset_test:
             print(f"Test asset with {method} args passing strategy")
             test_asset(method)
@@ -155,10 +139,19 @@ def basic_test(
             test_collection(method)
 
 
+CI_COMMIT_REF_NAME = os.environ.get("CI_COMMIT_REF_NAME")
+
+
 def is_schema_url_synced(cls):
+    """Check if the schema is in sync with the repository."""
     local_schema = cls.get_schema()
     url = cls.get_schema_uri()
-    remote_schema = requests.get(url).json()
+    url = (
+        url.replace("/-/raw/main/", f"/-/raw/{CI_COMMIT_REF_NAME}/")
+        if CI_COMMIT_REF_NAME
+        else url
+    )
+    remote_schema = requests.get(url, timeout=10).json()
     print(
         f"Local schema is :\n"
         f"{local_schema}\n"
@@ -168,14 +161,10 @@ def is_schema_url_synced(cls):
     )
     if local_schema != remote_schema:
         print("Schema differs:")
+
         def _json2str(dic):
             return json.dumps(dic, indent=2).split("\n")
 
-        diff = difflib.unified_diff(
-            _json2str(local_schema),
-            _json2str(remote_schema)
-        )
+        diff = difflib.unified_diff(_json2str(local_schema), _json2str(remote_schema))
         print("\n".join(diff))
-        raise ValueError(
-            f"Please update the schema located in {url}"
-        )
+        raise ValueError(f"Please update the schema located in {url}")
diff --git a/tests/extensions_test.py b/tests/extensions_test.py
index 85ec7019c7a29c8de763b55692336a0773c24d04..bbae3cb66f82800fbc50624ab0c0d3bfaa04d82e 100644
--- a/tests/extensions_test.py
+++ b/tests/extensions_test.py
@@ -1,39 +1,36 @@
-from stac_extension_genmeta import create_extension_cls
+"""Tests example."""
+
+from typing import List, Final
+from pydantic import Field
+from stac_extension_genmeta import create_extension_cls, BaseExtensionModel
 from stac_extension_genmeta.testing import basic_test
-from pydantic import BaseModel, Field, ConfigDict
-from typing import List
 
 # Extension parameters
-SCHEMA_URI: str = "https://example.com/image-process/v1.0.0/schema.json"
-PREFIX: str = "some_prefix"
-
-
-# Extension metadata model
-class MyExtensionMetadataModel(BaseModel):
-    # Required so that one model can be instantiated with the attribute name
-    # rather than the alias
-    model_config = ConfigDict(populate_by_name=True)
-
-    # Metadata fields
-    name: str = Field(title="Process name", alias=f"{PREFIX}:name")
-    authors: List[str] = Field(title="Authors", alias=f"{PREFIX}:authors")
-    version: str = Field(title="Process version", alias=f"{PREFIX}:version")
-    opt_field: str | None = Field(title="Some optional field", alias=f"{PREFIX}:opt_field", default=None)
-
-
-# Create the extension class
-MyExtension = create_extension_cls(
-    model_cls=MyExtensionMetadataModel,
-    schema_uri=SCHEMA_URI
-)
-
-# Metadata fields
-ext_md = MyExtensionMetadataModel(
-    name="test",
-    authors=["michel", "denis"],
-    version="alpha"
-)
-
-basic_test(ext_md, MyExtension, validate=False)
-
-MyExtension.print_schema()
+SCHEMA_URI: Final = "https://example.com/image-process/v1.0.0/schema.json"
+PREFIX: Final = "some_prefix:"
+NAME: Final = PREFIX + "name"
+AUTHORS: Final = PREFIX + "authors"
+VERSION: Final = PREFIX + "version"
+OPT_FIELD: Final = PREFIX + "opt_field"
+
+
+class MyExtensionMetadataModel(BaseExtensionModel):
+    """Extension metadata model example."""
+
+    name: str = Field(title="Process name", alias=NAME)
+    authors: List[str] = Field(title="Authors", alias=AUTHORS)
+    version: str = Field(title="Process version", alias=VERSION)
+    opt_field: str | None = Field(
+        title="Some optional field", alias=OPT_FIELD, default=None
+    )
+
+
+def test_example():
+    """Test example function."""
+    MyExtension = create_extension_cls(
+        model_cls=MyExtensionMetadataModel, schema_uri=SCHEMA_URI
+    )
+    ext_md = MyExtensionMetadataModel(
+        name="test", authors=["michel", "denis"], version="alpha"
+    )
+    basic_test(ext_md, MyExtension, validate=False)