Repository: statisticsnorway/ssb-klass-python

Issue:
Easier traversal to Variant
Can we find the IDs of the variants owned by a classification somehow?
.get_variant_by_name() Seems to return many more rows than KlassVariant...

Does this need to better documented as well?

Example code

```python
import klass

nus_til_kltrinn = klass.KlassVariant(2788)
print(nus_til_kltrinn)

nus = klass.KlassClassification(36)
print(nus)

search = nus.get_variant_by_name("Klassetrinn", from_date="2024-01-01")
print(search)

search.data

search.data.drop_duplicates()

nus_til_kltrinn.data

klass.requests.klass_requests.variant("36", "Klassetrinn", "2024-01-01")
```

Hints from issue comments:
@aecorn: Its possible to get versions from the classification, and variants from the version.
So it should be possible to do the same type of traversal as Codes. Where we have a method below Classification that traverses "through" Version to get to Variant.

```python
def get_latest_variant_by_name(classification_id, variant_name: str) -> klass.KlassVariant:
    classification = klass.KlassClassification(classification_id)
    version_ids = {x["version_id"]: x["name"] for x in classification.versions[::-1]}
    newest_version_id = list(nus_version_ids.keys())[0]
    variants = classification.get_version(newest_version_id).variants_simple()
    results = {}
    for k, v in variants.items():
        if variant_name.lower() in v.lower():
            results[k] = v
    if len(results) > 1:
        raise ValueError("Variant_name maybe not specific enough, getting multiple results.")
    if len(results) == 0:
        return None
    return klass.KlassVariant(list(results.keys())[0])
nus_til_klassetrinn = get_latest_variant_by_name(36, "Klassetrinn")
```

Solution patch:
diff --git a/.gitignore b/.gitignore
index e39e8c1..2c7d033 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,11 @@
 *.xlsx
 *.zip
 
+# Notebooks shall be stored in .py or .R-format.
+# See https://adr.ssb.no/0020-lagringsformat-for-jupyter-notebooks/
+*.ipynb
+
+
 # The section below is from the GitHub .gitignore template for Python:
 # https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore
 
@@ -131,24 +136,6 @@ venv.bak/
 # Spyder project settings
 .spyderproject
 .spyproject
-/.python-version
-
-/.pytype/
-/docs/_build/
-
-# Visual Studio Code
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
-!.vscode/*.code-snippets
-# Local History for Visual Studio Code
-.history/
-# Built Visual Studio Code Extensions
-*.vsix
-
-
 
 # Rope project settings
 .ropeproject
@@ -222,3 +209,7 @@ rsconnect/
 
 # ignore poetry in .config
 .poetry/**
+
+# Blaise specific ignores
+*.bdix
+*.bdbx
diff --git a/demo/classification.ipynb b/demo/classification.ipynb
deleted file mode 100644
index f4ffee4..0000000
--- a/demo/classification.ipynb
+++ /dev/null
@@ -1,144 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "880a3e4f-fd2d-4cf8-834f-61a0b6f23fad",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from klass import KlassClassification"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "d1fa783d-adce-4693-b37b-720a8fa490f3",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "# ID found from website: https://www.ssb.no/klass/\n",
-    "nus = KlassClassification(36, language=\"en\", include_future=True)\n",
-    "print(nus)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "8565efec-6750-4f53-8a3e-f4c267db4888",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "repr(nus)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "0269d81f-984b-470e-b4e5-c00179bf0752",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "# Reformatting for fun\n",
-    "nus.versions_dict()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "88774c9f-1d1a-4543-9a8d-ae300de37fca",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nuskoder = nus.get_codes(\"2023-01-01\")\n",
-    "print(nuskoder)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "20b6de68-e6a6-43e1-a5a7-52e9b65bc0c8",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nuskoder.pivot_level()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "db3ca621-2114-4e8a-8c84-c73d4f6f2a2f",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nuskoder.data"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "6cf1bf10-500b-407d-a436-30a3d39a2fb3",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "# The actual data is under the .data attribute\n",
-    "nuskoder.data"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "a16ce96c-d391-49e8-ace4-9222790c7235",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "# Pivots levels into seperate columns\n",
-    "nuskoder.pivot_level()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "4cf5d29d-0e3f-4ac2-b803-478361345776",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "# You can filter to a level when getting codes, and then make dict from that level\n",
-    "nus.get_codes(\"2023-01-01\", select_level=5).to_dict()"
-   ]
-  }
- ],
- "metadata": {
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython"
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/demo/classification.py b/demo/classification.py
new file mode 100644
index 0000000..a0bcb94
--- /dev/null
+++ b/demo/classification.py
@@ -0,0 +1,50 @@
+# ---
+# jupyter:
+#   jupytext:
+#     text_representation:
+#       extension: .py
+#       format_name: percent
+#       format_version: '1.3'
+#       jupytext_version: 1.16.6
+#   kernelspec:
+#     display_name: ssb-klass-python
+#     language: python
+#     name: ssb-klass-python
+# ---
+
+# %%
+from klass import KlassClassification
+
+# %%
+# ID found from website: https://www.ssb.no/klass/
+nus = KlassClassification(36, language="en", include_future=True)
+print(nus)
+
+# %%
+repr(nus)
+
+# %%
+# Reformatting for fun
+nus.versions_dict()
+
+# %%
+nuskoder = nus.get_codes("2023-01-01")
+print(nuskoder)
+
+# %%
+nuskoder.pivot_level()
+
+# %%
+nuskoder.data
+
+# %%
+# The actual data is under the .data attribute
+nuskoder.data
+
+# %%
+# Pivots levels into seperate columns
+nuskoder.pivot_level()
+
+# %%
+# You can filter to a level when getting codes, and then make dict from that level
+nus.get_codes("2023-01-01", select_level=5).to_dict()
diff --git a/demo/config.ipynb b/demo/config.ipynb
deleted file mode 100644
index 5d0966c..0000000
--- a/demo/config.ipynb
+++ /dev/null
@@ -1,85 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "22987e29-ea40-465f-8006-81fe6b26009a",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "import klass"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "af47514a-a060-4195-9d19-1c94206893b6",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "[x for x in dir(klass.config) if not x.startswith(\"_\")]"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "7528c978-1f36-4c67-955f-cf558cdd5b50",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klass.config.TESTING"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "a7f984ac-d53b-486d-8378-0b2839f96879",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klass.config.TESTING = True"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "febc3bc1-33b3-46c6-a2c1-5e57a3f281ff",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klass.classification_by_id(36)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "692233c2-0b39-4ffe-8673-6ebd933a41e4",
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython"
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/demo/config.py b/demo/config.py
new file mode 100644
index 0000000..5afbeda
--- /dev/null
+++ b/demo/config.py
@@ -0,0 +1,30 @@
+# ---
+# jupyter:
+#   jupytext:
+#     text_representation:
+#       extension: .py
+#       format_name: percent
+#       format_version: '1.3'
+#       jupytext_version: 1.16.6
+#   kernelspec:
+#     display_name: ssb-klass-python
+#     language: python
+#     name: ssb-klass-python
+# ---
+
+# %%
+import klass
+
+# %%
+[x for x in dir(klass.config) if not x.startswith("_")]
+
+# %%
+klass.config.TESTING
+
+# %%
+klass.config.TESTING = True
+
+# %%
+klass.classification_by_id(36)
+
+# %%
diff --git a/demo/correspondence.ipynb b/demo/correspondence.ipynb
deleted file mode 100644
index 6c5eb8f..0000000
--- a/demo/correspondence.ipynb
+++ /dev/null
@@ -1,81 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "5a1ddca7-1518-4b6c-8379-8937148838dd",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from klass import KlassCorrespondence"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "e68719de-60db-4940-9582-dd8689c386cd",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "fylke_komm = KlassCorrespondence(\n",
-    "    source_classification_id=\"131\",\n",
-    "    target_classification_id=\"104\",\n",
-    "    from_date=\"2023-01-01\",\n",
-    ")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "31ff5ae6-966b-4574-bfde-e52a7c8a3e7d",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "print(fylke_komm)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "146c0440-954c-4cbe-8905-378d11d655c2",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "fylke_komm.data"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "a3213aab-a343-4b1e-bf0e-8ea2c752ce20",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "fylke_komm.to_dict(\"sourceName\", \"targetName\")"
-   ]
-  }
- ],
- "metadata": {
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython"
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/demo/correspondence.py b/demo/correspondence.py
new file mode 100644
index 0000000..0d1fbe3
--- /dev/null
+++ b/demo/correspondence.py
@@ -0,0 +1,32 @@
+# ---
+# jupyter:
+#   jupytext:
+#     text_representation:
+#       extension: .py
+#       format_name: percent
+#       format_version: '1.3'
+#       jupytext_version: 1.16.6
+#   kernelspec:
+#     display_name: ssb-klass-python
+#     language: python
+#     name: ssb-klass-python
+# ---
+
+# %%
+from klass import KlassCorrespondence
+
+# %%
+fylke_komm = KlassCorrespondence(
+    source_classification_id="131",
+    target_classification_id="104",
+    from_date="2023-01-01",
+)
+
+# %%
+print(fylke_komm)
+
+# %%
+fylke_komm.data
+
+# %%
+fylke_komm.to_dict("sourceName", "targetName")
diff --git a/demo/family.ipynb b/demo/family.ipynb
deleted file mode 100644
index cd391dd..0000000
--- a/demo/family.ipynb
+++ /dev/null
@@ -1,101 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "c199fe3c-6bdb-49ea-a866-b309c3d1bccd",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from klass import KlassSearchFamilies"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "938a76f5-4e7b-4d2b-9651-9c3bccbeedc7",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "search = KlassSearchFamilies(\"360\")\n",
-    "print(search)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "0802663d-ff6d-4af8-8f53-19e6f588e6e9",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "utdanning = search.get_family(20)\n",
-    "print(utdanning)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "74e13d81-2f88-46b4-b1e6-f581a271d449",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nus = utdanning.get_classification(36)\n",
-    "print(nus)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "b3846714-35cf-4b4b-a596-d595e919080b",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nus_codes = nus.get_codes(\"2023-01-01\")\n",
-    "print(nus_codes)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "7cadbd45-5681-4da1-8c2b-1f019eab934c",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nus_codes.data"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "f263865f-ea91-477e-a2da-13d9e661ff2a",
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython"
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/demo/family.py b/demo/family.py
new file mode 100644
index 0000000..f0d70d4
--- /dev/null
+++ b/demo/family.py
@@ -0,0 +1,37 @@
+# ---
+# jupyter:
+#   jupytext:
+#     text_representation:
+#       extension: .py
+#       format_name: percent
+#       format_version: '1.3'
+#       jupytext_version: 1.16.6
+#   kernelspec:
+#     display_name: ssb-klass-python
+#     language: python
+#     name: ssb-klass-python
+# ---
+
+# %%
+from klass import KlassSearchFamilies
+
+# %%
+search = KlassSearchFamilies("360")
+print(search)
+
+# %%
+utdanning = search.get_family(20)
+print(utdanning)
+
+# %%
+nus = utdanning.get_classification(36)
+print(nus)
+
+# %%
+nus_codes = nus.get_codes("2023-01-01")
+print(nus_codes)
+
+# %%
+nus_codes.data
+
+# %%
diff --git a/demo/gui_ipywidgets.ipynb b/demo/gui_ipywidgets.ipynb
deleted file mode 100644
index b42c9a2..0000000
--- a/demo/gui_ipywidgets.ipynb
+++ /dev/null
@@ -1,182 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "30c0b2ea-3dbf-40ea-86e8-c99287e46fec",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "import klass\n",
-    "\n",
-    "klass.search_classification()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "ab1513eb-627f-453f-8350-888660d167a7",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from datetime import date\n",
-    "\n",
-    "import IPython.display.display\n",
-    "import ipywidgets as widgets\n",
-    "from IPython.display import HTML\n",
-    "\n",
-    "from klass.classes.search import KlassSearchClassifications\n",
-    "from klass.requests.sections import sections_dict\n",
-    "\n",
-    "DEFAULT_CHOICE = \"Choose...\"\n",
-    "\n",
-    "\n",
-    "def search_classification(no_dupes: bool = True) -> widgets.VBox:\n",
-    "    \"\"\"Open a GUI in Jupyter Notebooks using ipywidgets.\n",
-    "\n",
-    "    Lets you search for terms and copy sample code out,\n",
-    "    that'll let you get data from the classification.\n",
-    "\n",
-    "    Args:\n",
-    "        no_dupes (bool): To include duplicate results or not in the result.\n",
-    "                        Dupes are caused by multiple languages being returned.\n",
-    "\n",
-    "    Returns:\n",
-    "        widgets.VBox: Containing the nested ipywidgets-GUI. Jupyter will automatically display it.\n",
-    "    \"\"\"\n",
-    "    search_result: widgets.output = widgets.Output()\n",
-    "    search_term: widgets.Text = widgets.Text(\n",
-    "        value=\"\", placeholder=\"Searchterm\", description=\"Type searchterm:\"\n",
-    "    )\n",
-    "    section_dropdown: widgets.Dropdown = widgets.Dropdown(\n",
-    "        options=[DEFAULT_CHOICE],\n",
-    "        value=DEFAULT_CHOICE,\n",
-    "        description=\"Section:\",\n",
-    "        disabled=False,\n",
-    "    )\n",
-    "\n",
-    "    def do_search(btn: widgets.Button) -> None:\n",
-    "        nonlocal search_term\n",
-    "        nonlocal section_dropdown\n",
-    "        nonlocal search_result\n",
-    "\n",
-    "        with search_result:\n",
-    "            search_result.clear_output()\n",
-    "            IPython.display.display(\"Searching...\")  # type: ignore\n",
-    "            # Add loading-gif?\n",
-    "\n",
-    "        try:\n",
-    "            if section_dropdown.value != DEFAULT_CHOICE:\n",
-    "                search_class = KlassSearchClassifications(\n",
-    "                    search_term.value,\n",
-    "                    ssbsection=section_dropdown.value,\n",
-    "                    include_codelists=True,\n",
-    "                    no_dupes=no_dupes,\n",
-    "                )\n",
-    "            else:\n",
-    "                search_class = KlassSearchClassifications(\n",
-    "                    search_term.value, include_codelists=True, no_dupes=no_dupes\n",
-    "                )\n",
-    "            search_content = format_classification_text(search_class)\n",
-    "        except Exception as e:\n",
-    "            search_content = str(e)\n",
-    "\n",
-    "        with search_result:\n",
-    "            search_result.clear_output()\n",
-    "            IPython.display.display(HTML(search_content))  # type: ignore\n",
-    "\n",
-    "    sections = [DEFAULT_CHOICE, *list(sections_dict().keys())]\n",
-    "    section_dropdown = widgets.Dropdown(\n",
-    "        options=sections, value=sections[0], description=\"Section:\", disabled=False\n",
-    "    )\n",
-    "    search_button = widgets.Button(description=\"Search\")\n",
-    "    search_button.on_click(do_search)\n",
-    "    html_header = widgets.HTML(\n",
-    "        value=\"<style>button.classification_copy_code:active {opacity: 30%;} button.classification_copy_code:hover {opacity: 80%;}</style>\"\n",
-    "    )\n",
-    "\n",
-    "    return widgets.VBox(\n",
-    "        [\n",
-    "            widgets.HBox([html_header, search_term, section_dropdown, search_button]),\n",
-    "            widgets.HBox([search_result]),\n",
-    "        ]\n",
-    "    )\n",
-    "\n",
-    "\n",
-    "def format_classification_text(search_class: KlassSearchClassifications) -> str:\n",
-    "    \"\"\"Format the classifications from a search into a html-string that can be used in the widget results.\n",
-    "\n",
-    "    Args:\n",
-    "        search_class (KlassSearchClassifications): The search-results.\n",
-    "\n",
-    "    Returns:\n",
-    "        str: The formatted html-string.\n",
-    "    \"\"\"\n",
-    "    search_content = \"\"\n",
-    "    print(f\"{search_class.classifications=}\")\n",
-    "    if len(search_class.classifications):\n",
-    "        for cl in search_class.classifications:\n",
-    "            var_name = \"_\".join(\n",
-    "                cl[\"name\"]\n",
-    "                .split(\":\")[0]\n",
-    "                .lower()\n",
-    "                .replace(\"æ\", \"ae\")\n",
-    "                .replace(\"ø\", \"oe\")\n",
-    "                .replace(\"å\", \"aa\")\n",
-    "                .strip()\n",
-    "            )\n",
-    "            var_name_chars = [c if c.isalnum() else \"_\" for c in var_name]\n",
-    "            var_name_true = \"\".join([c for c in var_name_chars if c and c != \" \"])\n",
-    "            if len(var_name_true.split(\"_\")) > 3:\n",
-    "                var_name_true = \"_\".join(\n",
-    "                    [part for part in var_name_true.split(\"_\")[:3] if part]\n",
-    "                )\n",
-    "            text = f\"\"\"from klass import KlassClassification\\\\n{var_name_true} = KlassClassification({cl[\"classification_id\"]})\\\\n{var_name_true}.get_codes(\\\\'{date.today().strftime('%Y-%m-%d')}\\\\').data\"\"\"\n",
-    "            search_content += f\"\"\"<button class=\"classification_copy_code\" onclick=\"navigator.clipboard.writeText('{text}')\">Copy code</button> {cl[\"classification_id\"]} - {cl[\"name\"]}<br />\"\"\"\n",
-    "    else:\n",
-    "        search_content = \"Found no matching classifications.\"\n",
-    "    return search_content\n",
-    "\n",
-    "\n",
-    "search_classification()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "0622f63c-9c71-4e58-9ce5-4aa9d15c9de8",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "KlassSearchClassifications(\"Utdanning\", include_codelists=True, no_dupes=True)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "a66cf3c8-34c0-4431-a168-3efb04e6e085",
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython"
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/demo/gui_ipywidgets.py b/demo/gui_ipywidgets.py
new file mode 100644
index 0000000..0771f70
--- /dev/null
+++ b/demo/gui_ipywidgets.py
@@ -0,0 +1,145 @@
+# ---
+# jupyter:
+#   jupytext:
+#     text_representation:
+#       extension: .py
+#       format_name: percent
+#       format_version: '1.3'
+#       jupytext_version: 1.16.6
+#   kernelspec:
+#     display_name: ssb-klass-python
+#     language: python
+#     name: ssb-klass-python
+# ---
+
+# %%
+import klass
+
+klass.search_classification()
+
+# %%
+from datetime import date
+
+import IPython.display.display
+import ipywidgets as widgets
+from IPython.display import HTML
+
+from klass.classes.search import KlassSearchClassifications
+from klass.requests.sections import sections_dict
+
+DEFAULT_CHOICE = "Choose..."
+
+
+def search_classification(no_dupes: bool = True) -> widgets.VBox:
+    """Open a GUI in Jupyter Notebooks using ipywidgets.
+
+    Lets you search for terms and copy sample code out,
+    that'll let you get data from the classification.
+
+    Args:
+        no_dupes (bool): To include duplicate results or not in the result.
+                        Dupes are caused by multiple languages being returned.
+
+    Returns:
+        widgets.VBox: Containing the nested ipywidgets-GUI. Jupyter will automatically display it.
+    """
+    search_result: widgets.output = widgets.Output()
+    search_term: widgets.Text = widgets.Text(
+        value="", placeholder="Searchterm", description="Type searchterm:"
+    )
+    section_dropdown: widgets.Dropdown = widgets.Dropdown(
+        options=[DEFAULT_CHOICE],
+        value=DEFAULT_CHOICE,
+        description="Section:",
+        disabled=False,
+    )
+
+    def do_search(btn: widgets.Button) -> None:
+        nonlocal search_term
+        nonlocal section_dropdown
+        nonlocal search_result
+
+        with search_result:
+            search_result.clear_output()
+            IPython.display.display("Searching...")  # type: ignore
+            # Add loading-gif?
+
+        try:
+            if section_dropdown.value != DEFAULT_CHOICE:
+                search_class = KlassSearchClassifications(
+                    search_term.value,
+                    ssbsection=section_dropdown.value,
+                    include_codelists=True,
+                    no_dupes=no_dupes,
+                )
+            else:
+                search_class = KlassSearchClassifications(
+                    search_term.value, include_codelists=True, no_dupes=no_dupes
+                )
+            search_content = format_classification_text(search_class)
+        except Exception as e:
+            search_content = str(e)
+
+        with search_result:
+            search_result.clear_output()
+            IPython.display.display(HTML(search_content))  # type: ignore
+
+    sections = [DEFAULT_CHOICE, *list(sections_dict().keys())]
+    section_dropdown = widgets.Dropdown(
+        options=sections, value=sections[0], description="Section:", disabled=False
+    )
+    search_button = widgets.Button(description="Search")
+    search_button.on_click(do_search)
+    html_header = widgets.HTML(
+        value="<style>button.classification_copy_code:active {opacity: 30%;} button.classification_copy_code:hover {opacity: 80%;}</style>"
+    )
+
+    return widgets.VBox(
+        [
+            widgets.HBox([html_header, search_term, section_dropdown, search_button]),
+            widgets.HBox([search_result]),
+        ]
+    )
+
+
+def format_classification_text(search_class: KlassSearchClassifications) -> str:
+    """Format the classifications from a search into a html-string that can be used in the widget results.
+
+    Args:
+        search_class (KlassSearchClassifications): The search-results.
+
+    Returns:
+        str: The formatted html-string.
+    """
+    search_content = ""
+    print(f"{search_class.classifications=}")
+    if len(search_class.classifications):
+        for cl in search_class.classifications:
+            var_name = "_".join(
+                cl["name"]
+                .split(":")[0]
+                .lower()
+                .replace("æ", "ae")
+                .replace("ø", "oe")
+                .replace("å", "aa")
+                .strip()
+            )
+            var_name_chars = [c if c.isalnum() else "_" for c in var_name]
+            var_name_true = "".join([c for c in var_name_chars if c and c != " "])
+            if len(var_name_true.split("_")) > 3:
+                var_name_true = "_".join(
+                    [part for part in var_name_true.split("_")[:3] if part]
+                )
+            text = f"""from klass import KlassClassification\\n{var_name_true} = KlassClassification({cl["classification_id"]})\\n{var_name_true}.get_codes(\\'{date.today().strftime('%Y-%m-%d')}\\').data"""
+            search_content += f"""<button class="classification_copy_code" onclick="navigator.clipboard.writeText('{text}')">Copy code</button> {cl["classification_id"]} - {cl["name"]}<br />"""
+    else:
+        search_content = "Found no matching classifications."
+    return search_content
+
+
+search_classification()
+
+# %%
+KlassSearchClassifications("Utdanning", include_codelists=True, no_dupes=True)
+
+# %%
diff --git a/demo/join_kommune_fylke.ipynb b/demo/join_kommune_fylke.ipynb
deleted file mode 100644
index 3eac4d4..0000000
--- a/demo/join_kommune_fylke.ipynb
+++ /dev/null
@@ -1,76 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "3975e0cc-69ca-407d-a9bb-cbf00878283b",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "import pandas as pd\n",
-    "\n",
-    "from klass import KlassClassification"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "a3757644-c5df-4e4c-8652-0a426c5c79f8",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "kom = (\n",
-    "    KlassClassification(\"131\")\n",
-    "    .get_codes(from_date=\"2022-01-01\")\n",
-    "    .data[[\"code\", \"name\"]]\n",
-    "    .rename(columns={\"code\": \"kommunenummer\", \"name\": \"kommune\"})\n",
-    ")\n",
-    "fyl = (\n",
-    "    KlassClassification(\"104\")\n",
-    "    .get_codes(from_date=\"2022-01-01\")\n",
-    "    .data[[\"code\", \"name\"]]\n",
-    "    .rename(columns={\"code\": \"fylkesnummer\", \"name\": \"fylke\"})\n",
-    ")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "d885c9ac-1464-4505-9d52-fb016a0a67dc",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "kom[\"fylkesnummer\"] = kom[\"kommunenummer\"].str[:2]\n",
-    "komfyl = pd.merge(kom, fyl, how=\"left\", on=\"fylkesnummer\")\n",
-    "komfyl"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "50cbd555-494f-4945-a5f5-dd1b127769f5",
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython"
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/demo/join_kommune_fylke.py b/demo/join_kommune_fylke.py
new file mode 100644
index 0000000..6484152
--- /dev/null
+++ b/demo/join_kommune_fylke.py
@@ -0,0 +1,39 @@
+# ---
+# jupyter:
+#   jupytext:
+#     text_representation:
+#       extension: .py
+#       format_name: percent
+#       format_version: '1.3'
+#       jupytext_version: 1.16.6
+#   kernelspec:
+#     display_name: ssb-klass-python
+#     language: python
+#     name: ssb-klass-python
+# ---
+
+# %%
+import pandas as pd
+
+from klass import KlassClassification
+
+# %%
+kom = (
+    KlassClassification("131")
+    .get_codes(from_date="2022-01-01")
+    .data[["code", "name"]]
+    .rename(columns={"code": "kommunenummer", "name": "kommune"})
+)
+fyl = (
+    KlassClassification("104")
+    .get_codes(from_date="2022-01-01")
+    .data[["code", "name"]]
+    .rename(columns={"code": "fylkesnummer", "name": "fylke"})
+)
+
+# %%
+kom["fylkesnummer"] = kom["kommunenummer"].str[:2]
+komfyl = pd.merge(kom, fyl, how="left", on="fylkesnummer")
+komfyl
+
+# %%
diff --git a/demo/quarter_seb.ipynb b/demo/quarter_seb.ipynb
deleted file mode 100644
index 6a4fbd7..0000000
--- a/demo/quarter_seb.ipynb
+++ /dev/null
@@ -1,115 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "daf943c5-0f94-4b6d-a838-c6a5a15f93ed",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from klass import KlassCorrespondence"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "c7cac885-9876-45ca-a8c1-223876ca2790",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "bydel = KlassCorrespondence(\n",
-    "    source_classification_id=\"1\",\n",
-    "    target_classification_id=\"103\",\n",
-    "    from_date=\"2020-01-01\",\n",
-    "    contain_quarter=3,\n",
-    ")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "ed008bc0-355a-4c4b-8dbd-91955b17a331",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "bydel.from_date"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "9ffef1cc-48df-4793-bdba-1e447a559cb3",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "# Reset by parameter \"contain_quarter\" during initialization\n",
-    "bydel.to_date"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "13661253-56b4-40a4-b092-639f347f3916",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "bydel.data"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "257a98ff-0236-45de-b6ea-3c047c41169f",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "bydel.data[\"validFrom\"].min()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "40ed5094-ad31-494a-b885-8829ea26d1f8",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "bydel.data[\"validTo\"].max()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "b3e0c618-5960-4b47-879f-1cb7f85234c0",
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython"
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/demo/quarter_seb.py b/demo/quarter_seb.py
new file mode 100644
index 0000000..1de2b35
--- /dev/null
+++ b/demo/quarter_seb.py
@@ -0,0 +1,42 @@
+# ---
+# jupyter:
+#   jupytext:
+#     text_representation:
+#       extension: .py
+#       format_name: percent
+#       format_version: '1.3'
+#       jupytext_version: 1.16.6
+#   kernelspec:
+#     display_name: ssb-klass-python
+#     language: python
+#     name: ssb-klass-python
+# ---
+
+# %%
+from klass import KlassCorrespondence
+
+# %%
+bydel = KlassCorrespondence(
+    source_classification_id="1",
+    target_classification_id="103",
+    from_date="2020-01-01",
+    contain_quarter=3,
+)
+
+# %%
+bydel.from_date
+
+# %%
+# Reset by parameter "contain_quarter" during initialization
+bydel.to_date
+
+# %%
+bydel.data
+
+# %%
+bydel.data["validFrom"].min()
+
+# %%
+bydel.data["validTo"].max()
+
+# %%
diff --git a/demo/requestfunctions.ipynb b/demo/requestfunctions.ipynb
deleted file mode 100644
index 0001e6e..0000000
--- a/demo/requestfunctions.ipynb
+++ /dev/null
@@ -1,231 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "071f8747-c433-4052-bc08-eb66e1f66c5e",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "import klass"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "93fc0650-ed94-4f7b-a2ce-ee4d2236d576",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klassifikasjoner = klass.classifications(\n",
-    "    include_codelists=True, changed_since=\"2022-01-01\"\n",
-    ")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "e87639ad-f5b9-49f4-b3a5-8f5ec0a54d0a",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "# klassifikasjoner"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "d5def1fc-9038-4cbe-ac9d-a40eadded01d",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klass.classification_by_id(\"36\").keys()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "a38c487c-109a-48d6-9495-c421cfdc4072",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klass.codes(classification_id=\"36\", from_date=\"2020-01-01\", to_date=\"2023-01-02\")"
-   ]
-  },
-  {
-   "cell_type": "raw",
-   "id": "b1b0e8a1-b993-48a3-9a7e-faefa443bf62",
-   "metadata": {},
-   "source": []
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "602f0dcc-770a-4e38-9bae-cabe54416740",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klass.version_by_id(\"1954\")[\"classificationVariants\"]"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "60bb0f2c-7ba8-47d2-b51c-0f932c1c128b",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "# SELECT_LEVEL DOESNT WORK IN API?\n",
-    "klass.variant(\n",
-    "    \"36\",\n",
-    "    \"Studiepoeng ved fagskole\",\n",
-    "    \"2023-01-01\",\n",
-    "    # select_level=2,\n",
-    "    # return_type=\"json\"\n",
-    ")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "82a04a11-e0ce-4829-9e22-0d59c0d57277",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klass.variant_at(\"36\", \"Studiepoeng ved fagskole\", date=\"2022-01-01\")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "449f870a-e295-41e2-8b11-ee167e911e6f",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klass.codes_at(classification_id=\"36\", date=\"2023-01-01\", language=\"en\")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "71d7fe8a-4664-44eb-a16b-54d8d747cf25",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klass.corresponds(\n",
-    "    source_classification_id=104,\n",
-    "    target_classification_id=131,\n",
-    "    from_date=\"2023-01-01\",\n",
-    "    to_date=\"2024-01-01\",\n",
-    "    language=\"en\",\n",
-    "    include_future=True,\n",
-    ")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "32daf30f-0035-4cbf-9dad-5cbe85846d56",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klass.corresponds_at(\n",
-    "    source_classification_id=104,\n",
-    "    target_classification_id=131,\n",
-    "    date=\"2023-01-01\",\n",
-    ")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "14d17fbc-b766-47a6-a241-8ae267085a37",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klass.correspondence_table_by_id(\"1111\", language=\"en\")[\"name\"]"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "3f592794-920e-4a93-ba8d-ae8d3a2de057",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klass.changes(\"36\", \"2020-01-01\", \"2023-01-01\")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "210270fa-0181-4cd8-af29-38616bbc30a3",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klass.classificationfamilies(\"360\")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "873adae5-6d34-4eed-a613-0d660c88ec1f",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "klass.classificationfamilies_by_id(20)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "002ae05c-88ad-4811-aef3-fedb5021e426",
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython"
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/demo/requestfunctions.py b/demo/requestfunctions.py
new file mode 100644
index 0000000..3a155e4
--- /dev/null
+++ b/demo/requestfunctions.py
@@ -0,0 +1,83 @@
+# ---
+# jupyter:
+#   jupytext:
+#     text_representation:
+#       extension: .py
+#       format_name: percent
+#       format_version: '1.3'
+#       jupytext_version: 1.16.6
+#   kernelspec:
+#     display_name: ssb-klass-python
+#     language: python
+#     name: ssb-klass-python
+# ---
+
+# %%
+import klass
+
+# %%
+klassifikasjoner = klass.classifications(
+    include_codelists=True, changed_since="2022-01-01"
+)
+
+# %%
+# klassifikasjoner
+
+# %%
+klass.classification_by_id("36").keys()
+
+# %%
+klass.codes(classification_id="36", from_date="2020-01-01", to_date="2023-01-02")
+
+# %% [raw]
+#
+
+# %%
+klass.version_by_id("1954")["classificationVariants"]
+
+# %%
+# SELECT_LEVEL DOESNT WORK IN API?
+klass.variant(
+    "36",
+    "Studiepoeng ved fagskole",
+    "2023-01-01",
+    # select_level=2,
+    # return_type="json"
+)
+
+# %%
+klass.variant_at("36", "Studiepoeng ved fagskole", date="2022-01-01")
+
+# %%
+klass.codes_at(classification_id="36", date="2023-01-01", language="en")
+
+# %%
+klass.corresponds(
+    source_classification_id=104,
+    target_classification_id=131,
+    from_date="2023-01-01",
+    to_date="2024-01-01",
+    language="en",
+    include_future=True,
+)
+
+# %%
+klass.corresponds_at(
+    source_classification_id=104,
+    target_classification_id=131,
+    date="2023-01-01",
+)
+
+# %%
+klass.correspondence_table_by_id("1111", language="en")["name"]
+
+# %%
+klass.changes("36", "2020-01-01", "2023-01-01")
+
+# %%
+klass.classificationfamilies("360")
+
+# %%
+klass.classificationfamilies_by_id(20)
+
+# %%
diff --git a/demo/search.ipynb b/demo/search.ipynb
deleted file mode 100644
index 2219fe8..0000000
--- a/demo/search.ipynb
+++ /dev/null
@@ -1,136 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "c199fe3c-6bdb-49ea-a866-b309c3d1bccd",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from klass import KlassSearchClassifications\n",
-    "from klass import KlassSearchFamilies"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "69b64e0a-9ae6-42f3-9389-4cdb0f30c918",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "search = KlassSearchClassifications(\"land\", no_dupes=True)\n",
-    "print(search)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "2a925b14-733c-4cbb-b0d6-c5b310dd95e6",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "print(search.get_classification(106))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "4656bd9b-27b1-4b23-aebf-3e4e05711935",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "ours = KlassSearchFamilies(360)\n",
-    "print(ours)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "ace5f076-1c6e-4ba7-9736-9bd99c9d6e84",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "ours.families"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "12810f58-69bb-4825-ba8f-c0c0c49b4e63",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "utdanning = ours.get_family(20)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "9080ad9b-3220-4e62-ae69-9c079a8b4810",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "print(utdanning)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "1c802069-f8cc-47d9-9a3e-64b8a300332b",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "isced = utdanning.get_classification(66)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "a43e3313-5ef1-44ad-aa80-17f45df9a14d",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "print(isced)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "887aa2c7-6060-4f3a-ad94-7523ae000de7",
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython"
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/demo/search.py b/demo/search.py
new file mode 100644
index 0000000..73bddc9
--- /dev/null
+++ b/demo/search.py
@@ -0,0 +1,45 @@
+# ---
+# jupyter:
+#   jupytext:
+#     text_representation:
+#       extension: .py
+#       format_name: percent
+#       format_version: '1.3'
+#       jupytext_version: 1.16.6
+#   kernelspec:
+#     display_name: ssb-klass-python
+#     language: python
+#     name: ssb-klass-python
+# ---
+
+# %%
+from klass import KlassSearchClassifications
+from klass import KlassSearchFamilies
+
+# %%
+search = KlassSearchClassifications("land", no_dupes=True)
+print(search)
+
+# %%
+print(search.get_classification(106))
+
+# %%
+ours = KlassSearchFamilies(360)
+print(ours)
+
+# %%
+ours.families
+
+# %%
+utdanning = ours.get_family(20)
+
+# %%
+print(utdanning)
+
+# %%
+isced = utdanning.get_classification(66)
+
+# %%
+print(isced)
+
+# %%
diff --git a/demo/variant.ipynb b/demo/variant.ipynb
deleted file mode 100644
index bc50c7f..0000000
--- a/demo/variant.ipynb
+++ /dev/null
@@ -1,91 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "a9d38711-6712-40ae-84f3-e79b593144b7",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from klass import KlassVariant\n",
-    "from klass import KlassVariantSearchByName"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "959e5acc-1abe-47ef-9b71-b0e3e70bebbd",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "studpoeng = KlassVariantSearchByName(36, \"Studiepoeng ved fagskole\", \"2023-01-01\")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "02980ea6-7eee-4f22-878a-20996308cbd3",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "print(studpoeng)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "26ca3c3d-2698-419b-9b75-46ccd8dd93a2",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "studpoeng.get_variant()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "8bd295d2-e308-476c-8309-7f215ecec205",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "studpoeng2 = KlassVariant(1959)\n",
-    "print(studpoeng2)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "38de364a-a8b7-4db6-aa0b-8024bb0581eb",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "studpoeng2.data"
-   ]
-  }
- ],
- "metadata": {
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython"
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/demo/variant.py b/demo/variant.py
new file mode 100644
index 0000000..0441e3d
--- /dev/null
+++ b/demo/variant.py
@@ -0,0 +1,33 @@
+# ---
+# jupyter:
+#   jupytext:
+#     text_representation:
+#       extension: .py
+#       format_name: percent
+#       format_version: '1.3'
+#       jupytext_version: 1.16.6
+#   kernelspec:
+#     display_name: ssb-klass-python
+#     language: python
+#     name: ssb-klass-python
+# ---
+
+# %%
+from klass import KlassVariant
+from klass import KlassVariantSearchByName
+
+# %%
+studpoeng = KlassVariantSearchByName(36, "Studiepoeng ved fagskole", "2023-01-01")
+
+# %%
+print(studpoeng)
+
+# %%
+studpoeng.get_variant()
+
+# %%
+studpoeng2 = KlassVariant(1959)
+print(studpoeng2)
+
+# %%
+studpoeng2.data
diff --git a/demo/version.ipynb b/demo/version.ipynb
deleted file mode 100644
index 9023a58..0000000
--- a/demo/version.ipynb
+++ /dev/null
@@ -1,155 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "8dbbf425-6286-44ac-bb89-b2079a5e2064",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from klass import KlassVersion"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "d0d9ce4b-926a-47b6-87cc-26ae89960408",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nus2023 = KlassVersion(\"1954\")\n",
-    "print(nus2023)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "308c6a86-c586-40f7-aec0-1691d927df6a",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nus2023.variants_simple()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "06512dab-8ff4-4857-b962-0ecf53327b6d",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "studiepoeng = nus2023.get_variant(1959)\n",
-    "print(studiepoeng)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "29765629-0fb7-4e95-a97c-ee0846c91e29",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "{k: v[\"name\"] for k, v in nus2023.correspondences_simple().items()}"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "9ad9bcd1-0f36-40cf-a1a5-085875674817",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nus_isced = nus2023.get_correspondence(1112)\n",
-    "print(nus_isced)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "237e9e24-8ed5-45bb-b8c3-f3c4e53c9654",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "# Map directly from nus-code to isced-label using a dict, when specifying \"other\" you get a default-dict\n",
-    "nus_isced_map = nus_isced.to_dict(\n",
-    "    key=\"sourceCode\", value=\"targetName\", other=\"Unknown education programme\"\n",
-    ")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "8b3fdfac-0f16-420d-b920-52193f95b197",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nus_isced_map[\"899903\"]"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "dfeaa7f2-5fb9-476b-b2aa-f0868b7253f8",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nus_isced_map[\"0\"]"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "703640f7-2b66-41bf-9353-2360c1f0d31d",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "import pandas as pd\n",
-    "\n",
-    "df = pd.DataFrame({\"nus2000\": [\"099999\", \"899903\"]})\n",
-    "df[\"isced_label\"] = df[\"nus2000\"].map(nus_isced_map)\n",
-    "df"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "c78ae232-222a-416f-a57e-d284e02bf132",
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython"
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/demo/version.py b/demo/version.py
new file mode 100644
index 0000000..c7807ba
--- /dev/null
+++ b/demo/version.py
@@ -0,0 +1,55 @@
+# ---
+# jupyter:
+#   jupytext:
+#     text_representation:
+#       extension: .py
+#       format_name: percent
+#       format_version: '1.3'
+#       jupytext_version: 1.16.6
+#   kernelspec:
+#     display_name: ssb-klass-python
+#     language: python
+#     name: ssb-klass-python
+# ---
+
+# %%
+from klass import KlassVersion
+
+# %%
+nus2023 = KlassVersion("1954")
+print(nus2023)
+
+# %%
+nus2023.variants_simple()
+
+# %%
+studiepoeng = nus2023.get_variant(1959)
+print(studiepoeng)
+
+# %%
+{k: v["name"] for k, v in nus2023.correspondences_simple().items()}
+
+# %%
+nus_isced = nus2023.get_correspondence(1112)
+print(nus_isced)
+
+# %%
+# Map directly from nus-code to isced-label using a dict, when specifying "other" you get a default-dict
+nus_isced_map = nus_isced.to_dict(
+    key="sourceCode", value="targetName", other="Unknown education programme"
+)
+
+# %%
+nus_isced_map["899903"]
+
+# %%
+nus_isced_map["0"]
+
+# %%
+import pandas as pd
+
+df = pd.DataFrame({"nus2000": ["099999", "899903"]})
+df["isced_label"] = df["nus2000"].map(nus_isced_map)
+df
+
+# %%
diff --git a/demo/widget.ipynb b/demo/widget.ipynb
deleted file mode 100644
index ed7d2a0..0000000
--- a/demo/widget.ipynb
+++ /dev/null
@@ -1,127 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "b152dfcf-9c33-44f9-a567-77b20bb2b088",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "import klass\n",
-    "\n",
-    "klass.__version__"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "c7550888-d5ad-455f-b863-aad81abd6605",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from klass import search_classification\n",
-    "\n",
-    "search_classification()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "51cfbdbc-ca99-40fa-ac03-db2c17bc6610",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "# Paste code here, after pression \"copy code\" button\n",
-    "from klass import KlassClassification"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "018a30e0-17a9-4f33-a299-4424d6b03db7",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nus = KlassClassification(36)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "aa0cb798-1f2f-4f56-842b-77fd46cebbd3",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nus_codes = nus.get_codes(\"2023-01-01\")\n",
-    "print(nus_codes)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "89731026-e46b-4c22-bcc5-21b7e9e4dc99",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nus_codes.data.columns"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "9fb83897-46be-4b88-9de2-1c77ae372c1d",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "# nus_codes.to_dict(\"code\", \"parentCode\")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "161c357e-84d8-478f-b98d-7b21d0c0c5e4",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "nus_codes.pivot_level()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "7d725c10-a6e0-4eb1-83d2-f16f948babbc",
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython"
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/demo/widget.py b/demo/widget.py
new file mode 100644
index 0000000..d7d26b6
--- /dev/null
+++ b/demo/widget.py
@@ -0,0 +1,45 @@
+# ---
+# jupyter:
+#   jupytext:
+#     text_representation:
+#       extension: .py
+#       format_name: percent
+#       format_version: '1.3'
+#       jupytext_version: 1.16.6
+#   kernelspec:
+#     display_name: ssb-klass-python
+#     language: python
+#     name: ssb-klass-python
+# ---
+
+# %%
+import klass
+
+klass.__version__
+
+# %%
+from klass import search_classification
+
+search_classification()
+
+# %%
+# Paste code here, after pression "copy code" button
+from klass import KlassClassification
+
+# %%
+nus = KlassClassification(36)
+
+# %%
+nus_codes = nus.get_codes("2023-01-01")
+print(nus_codes)
+
+# %%
+nus_codes.data.columns
+
+# %%
+# nus_codes.to_dict("code", "parentCode")
+
+# %%
+nus_codes.pivot_level()
+
+# %%
diff --git a/pyproject.toml b/pyproject.toml
index 8c0191f..669a51f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "ssb-klass-python"
-version = "0.0.11"
+version = "1.0.0"
 description = "A Python package built on top of KLASS's API for retrieving classifications, codes, correspondences etc."
 authors = ["Carl Corneil, ssb-pythonistas <ssb-pythonistas@ssb.no>"]
 license = "MIT"
@@ -151,6 +151,7 @@ classmethod-decorators = ["classmethod", "validator", "root_validator", "pydanti
     "D100",
     "S101",    # asserts are encouraged in pytest
 ]
+"demo/*.py" = ["B018"]  # Ignore the useless expressions in the notebooks
 
 [build-system]
 requires = ["poetry-core>=1.0.0"]
diff --git a/src/klass/classes/classification.py b/src/klass/classes/classification.py
index b844957..b578de2 100644
--- a/src/klass/classes/classification.py
+++ b/src/klass/classes/classification.py
@@ -2,6 +2,7 @@
 
 from klass.classes.codes import KlassCodes
 from klass.classes.correspondence import KlassCorrespondence
+from klass.classes.variant import KlassVariant
 from klass.classes.variant import KlassVariantSearchByName
 from klass.classes.version import KlassVersion
 from klass.requests.klass_requests import changes
@@ -136,7 +137,9 @@ def get_version(
             KlassVersion: A KlassVersion object of the specified ID.
         """
         if not version_id:
-            version_id = self.versions[0]["version_id"]
+            version_id = sorted(self.versions, key=lambda x: x["validFrom"])[-1][
+                "version_id"
+            ]
         if language == "":
             language = self.language
         if include_future is None:
@@ -162,7 +165,7 @@ def get_variant_by_name(
         from_date: str,
         to_date: str = "",
         select_codes: str = "",
-        select_level: str = "",
+        select_level: int = 0,
         presentation_name_pattern: str = "",
         language: str = "nb",
         include_future: bool = False,
@@ -179,7 +182,7 @@ def get_variant_by_name(
             to_date (str): The end date of the time period. "YYYY-MM-DD".
             select_codes (str): Limit the result to codes matching this pattern.
                 See rules: https://data.ssb.no/api/klass/v1/api-guide.html#_selectcodes.
-            select_level (str): The level of the version to keep in the data.
+            select_level (int): The level of the version to keep in the data.
             presentation_name_pattern (str): Used to build an alternative presentation name for the codes.
                 See rules: https://data.ssb.no/api/klass/v1/api-guide.html#_presentationnamepattern.
             language (str): The language of the version. "nn", "nb" or "en".
@@ -242,7 +245,7 @@ def get_codes(
         from_date: str = "",
         to_date: str = "",
         select_codes: str = "",
-        select_level: str = "",
+        select_level: int = 0,
         presentation_name_pattern: str = "",
         language: str = "",
         include_future: bool | None = None,
@@ -254,7 +257,7 @@ def get_codes(
             to_date (str): The end date of the time period. "YYYY-MM-DD".
             select_codes (str): Limit the result to codes matching this pattern.
                 See rules: https://data.ssb.no/api/klass/v1/api-guide.html#_selectcodes.
-            select_level (str): The level of the version to keep in the data.
+            select_level (int): The level of the version to keep in the data.
             presentation_name_pattern (str): Used to build an alternative presentation name for the codes.
                 See rules: https://data.ssb.no/api/klass/v1/api-guide.html#_presentationnamepattern.
             language (str): The language of the version. "nn", "nb" or "en".
@@ -309,3 +312,64 @@ def get_changes(
             language=language,
             include_future=include_future,
         )
+
+    def get_latest_variant_by_name(self, variant_name: str) -> KlassVariant | None:
+        """Attempt to get a single variant from the classification using a search string.
+
+        Args:
+            variant_name: The string to search for amongst the variant names on this classification.
+
+        Raises:
+            ValueError: If the string is not specific enough, and zero, or more than a single variant is found.
+
+        Returns:
+            KlassVariant | None: The single variant we found with the search string. Or None if we found no matches.
+        """
+        version = self.get_version()
+        variants = version.variants_simple()
+
+        results: list[str] = []
+        for k, v in variants.items():
+            if variant_name.lower() in v.lower():
+                results.append(k)
+        if len(results) > 1:
+            raise ValueError(
+                "Variant_name maybe not specific enough, getting multiple results."
+            )
+        if len(results) == 0:
+            raise ValueError(
+                "No result found, here are the names of the variants:\n"
+                + ",\n".join(variants.values())
+            )
+        variant_id: str = results[0]
+        return KlassVariant(variant_id)
+
+    def join_all_variants_correspondences_on_data(
+        self,
+        version_id: int = 0,
+        shortname_len: int = 3,
+        data_left: pd.DataFrame | None = None,
+        code_col_name: str = "code",
+        include_cols: list[str] | None = None,
+    ) -> pd.DataFrame:
+        """Join both variants and correspondences onto the main code data of the version.
+
+        Can be quite slow, as it is doing a request to the KLASS-API for every variant and Correspondence.
+
+        Args:
+            version_id: If you want, specify the ID of the version. If 0, will get the "latest" version for the classification.
+            shortname_len: Amount of words from the correspondences that the new column names will be constructed from.
+            data_left: A dataframe containing a column to join all the correspondences on. If None will get data from the version.
+            code_col_name: The column in the data to join the code on.
+            include_cols: A list of the columns from the correspondences and variants you want to include when adding to the data.
+                The "targetCode" from correspondences and "parentCode" from variants is included by default, but you can add ["targetName", "name"] here to add the label of the correspondences and varians for example.
+
+        Returns:
+            pd.DataFrame: The data from the version, or from data sent to data_left, with the variants and correspondences joined on.
+        """
+        return self.get_version(version_id).join_all_variants_correspondences_on_data(
+            shortname_len,
+            data_left,
+            code_col_name,
+            include_cols,
+        )
diff --git a/src/klass/classes/codes.py b/src/klass/classes/codes.py
index 09d2285..5fcf916 100644
--- a/src/klass/classes/codes.py
+++ b/src/klass/classes/codes.py
@@ -24,7 +24,7 @@ class KlassCodes:
         from_date (str): The start date of the time period. "YYYY-MM-DD".
         to_date (str): The end date of the time period. "YYYY-MM-DD".
         select_codes (str): A list of codes to be selected.
-        select_level (str): A list of levels to be selected.
+        select_level (int): The level to keep in the data.
         presentation_name_pattern (str): A pattern for filtering the code names.
         language (str): The language of the code names. Defaults to "nb".
         include_future (bool): Whether to include future codes. Defaults to False.
@@ -44,7 +44,7 @@ def __init__(
         from_date: str = "",
         to_date: str = "",
         select_codes: str = "",
-        select_level: str = "",
+        select_level: int = 0,
         presentation_name_pattern: str = "",
         language: str = "nb",
         include_future: bool = False,
diff --git a/src/klass/classes/correspondence.py b/src/klass/classes/correspondence.py
index 718a0d3..81efbd7 100644
--- a/src/klass/classes/correspondence.py
+++ b/src/klass/classes/correspondence.py
@@ -181,6 +181,8 @@ def to_dict(
         key: str = "sourceCode",
         value: str = "targetCode",
         other: str = "",
+        remove_na: bool = True,
+        select_level: int = 0,
     ) -> dict[str, str | None] | defaultdict[str, str | None]:
         """Extract two columns from the data, turning them into a dict.
 
@@ -193,11 +195,23 @@ def to_dict(
             key (str): The name of the column with the values you want as keys.
             value (str): The name of the column with the values you want as values in your dict.
             other (str): The value to use for keys that don't exist in the data.
+            remove_na: Set to False if you want to keep empty mappings over the key and value columns. Empty is defined as empty strings or NA-types.
+            select_level: Keep only a specific level defines the variants codes / groups.
 
         Returns:
             dict | defaultdict: The dictionary of the correspondence.
         """
-        mapping = dict(zip(self.data[key], self.data[value], strict=False))
+        limit_data = self.data
+        if remove_na:
+            limit_data = limit_data[
+                (limit_data[[key, value]].notna().all(axis=1))
+                | (limit_data[[key, value]].astype("string") == "")
+            ]
+        if select_level:
+            limit_data = limit_data[
+                limit_data["level"].astype("string[pyarrow]") == str(select_level)
+            ]
+        mapping = dict(zip(limit_data[key], limit_data[value], strict=False))
         if other:
             mapping = defaultdict(lambda: other, mapping)
         return mapping
diff --git a/src/klass/classes/variant.py b/src/klass/classes/variant.py
index a22c8ea..b35f389 100644
--- a/src/klass/classes/variant.py
+++ b/src/klass/classes/variant.py
@@ -1,3 +1,5 @@
+from collections import defaultdict
+
 import pandas as pd
 
 from klass.requests.klass_requests import variant
@@ -55,9 +57,9 @@ def __init__(
         self.select_level = select_level
         self.language = language
 
-        self.get_classification_codes()
+        self.get_variant()
 
-    def get_classification_codes(self, select_level: int = 0) -> None:
+    def get_variant(self, select_level: int = 0) -> None:
         """Get the data from the API, setting it as attributes on the object.
 
         The codes are put into the .data attribute.
@@ -116,8 +118,45 @@ def __str__(self) -> str:
         result += f"\nPreview of the .data (5 first rows):\n{self.data[self.data.columns[:5]].head(5)}"
         return result
 
+    def to_dict(
+        self,
+        key: str = "code",
+        value: str = "parentCode",
+        other: str = "",
+        remove_na: bool = True,
+        select_level: int = 0,
+    ) -> dict[str, str] | defaultdict[str, str]:
+        """Extract two columns from the data, turning them into a dict.
+
+        If you specify a value for "other", returns a defaultdict instead.
+
+        Args:
+            key: The name of the column with the values you want as keys.
+            value: The name of the column with the values you want as values in your dict.
+            other: If key is missing from dict, return this value instead, if you specify an OTHER-value.
+            remove_na: Set to False if you want to keep empty mappings over the key and value columns. Empty is defined as empty strings or NA-types.
+            select_level: Usually you want level 2, not level 1, as level 1 just defines the variants codes / groups.
+
+        Returns:
+            dict | defaultdict: The extracted columns as a dict or defaultdict.
+        """
+        limit_data = self.data
+        if remove_na:
+            limit_data = limit_data[
+                (limit_data[[key, value]].notna().all(axis=1))
+                | (limit_data[[key, value]].astype("string") == "")
+            ]
+        if select_level:
+            limit_data = limit_data[
+                limit_data["level"].astype("string[pyarrow]") == str(select_level)
+            ]
+        mapping = dict(zip(limit_data[key], limit_data[value], strict=False))
+        if other:
+            mapping = defaultdict(lambda: other, mapping)
+        return mapping
+
 
-class KlassVariantSearchByName:
+class KlassVariantSearchByName(KlassVariant):
     """Look up a Variant based on the owning Classifications ID and the start of the Variants name.
 
     The name is put into a URL-parameter, so it might be sensitive to special characters,
@@ -165,7 +204,7 @@ def __init__(
         from_date: str,
         to_date: str = "",
         select_codes: str = "",
-        select_level: str = "",
+        select_level: int = 0,
         presentation_name_pattern: str = "",
         language: str = "nb",
         include_future: bool = False,
@@ -182,8 +221,11 @@ def __init__(
         self.include_future = include_future
         self.get_variant()
 
-    def get_variant(self) -> None:
+    def get_variant(self, select_level: int = 0) -> None:
         """Actually get the data from the API, called at the end of init."""
+        if select_level == 0:
+            select_level = self.select_level
+
         if self.to_date:
             self.data = variant(
                 classification_id=self.classification_id,
diff --git a/src/klass/classes/version.py b/src/klass/classes/version.py
index 5d082ab..ae040a0 100644
--- a/src/klass/classes/version.py
+++ b/src/klass/classes/version.py
@@ -6,6 +6,7 @@
 from klass.requests.klass_requests import version_by_id
 from klass.requests.klass_types import CorrespondenceTablesType
 from klass.requests.klass_types import VersionByIDType
+from klass.utility.naming import create_shortname
 
 
 class KlassVersion:
@@ -30,7 +31,7 @@ class KlassVersion:
 
     Args:
         version_id (str): The ID of the version.
-        select_level (int, optional): The level in the codelist-data to keep. Defaults to 0.
+        select_level (int): The level in the codelist-data to keep. Defaults to 0 (keep all).
         language (str, optional): The language of the version. Defaults to "nb", can be set to "en", or "nn".
         include_future (bool, optional): If the version should include future versions. Defaults to False.
     """
@@ -177,6 +178,68 @@ def get_variant(
         """
         return KlassVariant(variant_id, select_level, language)
 
+    def get_all_variants(self) -> list[KlassVariant]:
+        """Get all variants of version as a list of KlassVariants.
+
+        Can be quite slow, as it is doing a request to the KLASS-API for every variant.
+
+        Returns:
+            list[KlassVariant]: List of the variants we found.
+
+        """
+        return [KlassVariant(variant_id) for variant_id in self.variants_simple()]
+
+    def join_all_variants_on_data(
+        self,
+        shortname_len: int = 3,
+        data_left: pd.DataFrame | None = None,
+        code_col_name: str = "code",
+        include_cols: list[str] | None = None,
+    ) -> pd.DataFrame:
+        """Join the variants codes onto the main codes of the version.
+
+        Can be quite slow, as it is doing a request to the KLASS-API for every variant.
+
+        Args:
+            shortname_len: Amount of words from the variants that the new column names will be constructed from.
+            data_left: A dataframe containing a column to join all the variants on.
+            code_col_name: The column in the data to join the code on.
+            include_cols: A list of the columns from the variants you want to include when adding to the data.
+                The "parentCode" is included by default, but you can add ["name"] here to add the label of the variant code.
+
+        Returns:
+            pd.DataFrame: The joined pandas dataframe.
+
+        Raises:
+            ValueError: If similar column names show up, raises and error, and suggests using more elements to create the column names.
+        """
+        if isinstance(data_left, pd.DataFrame):
+            data = data_left.copy()
+        else:
+            data = self.data.copy()
+
+        # Remove all empty columns
+        data.isna().all()
+        all_variants = self.get_all_variants()
+        col_seen = list(data.columns)
+        for variant in all_variants:
+            shortname = create_shortname(variant, shortname_len=shortname_len)
+            if shortname in col_seen:
+                raise ValueError(
+                    f"Colname {shortname} already seen, increase the shortname_len?"
+                )
+            else:
+                col_seen += [shortname]
+
+            data[shortname] = data[code_col_name].map(variant.to_dict(remove_na=True))
+            if include_cols:
+                for col in include_cols:
+                    if col in variant.data.columns:
+                        data[f"{shortname}_{col}"] = data[code_col_name].map(
+                            variant.to_dict(value=col, remove_na=True)
+                        )
+        return data
+
     def correspondences_simple(self) -> dict[str, dict[str, str]]:
         """Get a simple dictionary of the correspondences.
 
@@ -235,3 +298,95 @@ def get_correspondence(
             language=language,
             include_future=include_future,
         )
+
+    def get_all_correspondences(self) -> list[KlassCorrespondence]:
+        """Get all correspondences of version as a list of KlassCorrespondences.
+
+        Returns:
+            list[KlassCorrespondence]: List of the correspondences we found.
+
+        """
+        return [
+            KlassCorrespondence(correspondence_id)
+            for correspondence_id in self.correspondences_simple()
+        ]
+
+    def join_all_correspondences_on_data(
+        self,
+        shortname_len: int = 3,
+        data_left: pd.DataFrame | None = None,
+        code_col_name: str = "code",
+        include_cols: list[str] | None = None,
+    ) -> pd.DataFrame:
+        """Join the correspondences codes onto the main codes of the version.
+
+        Can be quite slow, as it is doing a request to the KLASS-API for every correspondence.
+
+        Args:
+            shortname_len: Amount of words from the correspondences that the new column names will be constructed from.
+            data_left: A dataframe containing a column to join all the correspondences on.
+            code_col_name: The column in the data to join the code on.
+            include_cols: A list of the columns from the correspondences you want to include when adding to the data.
+                The "targetCode" is included by default, but you can add ["targetName"] here to add the label of the correspondence code for example.
+
+        Returns:
+            pd.DataFrame: The joined pandas dataframe.
+
+        Raises:
+            ValueError: If similar column names show up, raises and error, and suggests using more elements to create the column names.
+        """
+        if isinstance(data_left, pd.DataFrame):
+            data = data_left.copy()
+        else:
+            data = self.data.copy()
+        # Remove all empty columns
+        data.isna().all()
+        all_correspondences = self.get_all_correspondences()
+        col_seen = list(data.columns)
+        for correspondence in all_correspondences:
+            shortname = create_shortname(correspondence, shortname_len=shortname_len)
+            if shortname in col_seen:
+                raise ValueError(
+                    f"Colname {shortname} already seen, increase the shortname_len?"
+                )
+            else:
+                col_seen += [shortname]
+            data[shortname] = data[code_col_name].map(
+                correspondence.to_dict(remove_na=True)
+            )
+            if include_cols:
+                for col in include_cols:
+                    if col in correspondence.data.columns:
+                        data[f"{shortname}_{col}"] = data[code_col_name].map(
+                            correspondence.to_dict(value=col, remove_na=True)
+                        )
+
+        return data
+
+    def join_all_variants_correspondences_on_data(
+        self,
+        shortname_len: int = 3,
+        data_left: pd.DataFrame | None = None,
+        code_col_name: str = "code",
+        include_cols: list[str] | None = None,
+    ) -> pd.DataFrame:
+        """Join both variants and correspondences onto the main code data of the version.
+
+        Can be quite slow, as it is doing a request to the KLASS-API for every correspondence and variant.
+
+        Args:
+            shortname_len: Amount of words from the correspondences that the new column names will be constructed from.
+            data_left: A dataframe containing a column to join all the correspondences on. If None will get data from the version.
+            code_col_name: The column in the data to join the code on.
+            include_cols: A list of the columns from the correspondences and variants you want to include when adding to the data.
+                The "targetCode" from correspondences and "parentCode" from variants is included by default, but you can add ["targetName", "name"] here to add the label of the correspondences and varians for example.
+
+        Returns:
+            pd.DataFrame: The data from the version, or from data sent to data_left, with the variants and correspondences joined on.
+        """
+        data = self.join_all_variants_on_data(
+            shortname_len, data_left, code_col_name, include_cols
+        )
+        return self.join_all_correspondences_on_data(
+            shortname_len, data, code_col_name, include_cols
+        )
diff --git a/src/klass/requests/klass_requests.py b/src/klass/requests/klass_requests.py
index 5a3277a..e2afed1 100644
--- a/src/klass/requests/klass_requests.py
+++ b/src/klass/requests/klass_requests.py
@@ -148,7 +148,7 @@ def codes(
     from_date: str,
     to_date: str = "",
     select_codes: str = "",
-    select_level: str = "",
+    select_level: int = 0,
     presentation_name_pattern: str = "",
     language: str = "nb",
     include_future: bool = False,
@@ -164,7 +164,7 @@ def codes(
     if select_codes:
         params["selectCodes"] = select_codes
     if select_level:
-        params["selectLevel"] = select_level
+        params["selectLevel"] = str(select_level)
     if presentation_name_pattern:
         params["presentationNamePattern"] = presentation_name_pattern
     if language:
@@ -179,7 +179,7 @@ def codes_at(
     classification_id: str,
     date: str,
     select_codes: str = "",
-    select_level: str = "",
+    select_level: int = 0,
     presentation_name_pattern: str = "",
     language: str = "nb",
     include_future: bool = False,
@@ -191,7 +191,7 @@ def codes_at(
     if select_codes:
         params["selectCodes"] = select_codes
     if select_level:
-        params["selectLevel"] = select_level
+        params["selectLevel"] = str(select_level)
     if presentation_name_pattern:
         params["presentationNamePattern"] = presentation_name_pattern
     if language:
@@ -225,7 +225,7 @@ def variant(
     from_date: str,
     to_date: str = "",
     select_codes: str = "",
-    select_level: str = "",
+    select_level: int = 0,
     presentation_name_pattern: str = "",
     language: str = "nb",
     include_future: bool = False,
@@ -242,7 +242,7 @@ def variant(
     if select_codes:
         params["selectCodes"] = select_codes
     if select_level:
-        params["selectLevel"] = select_level
+        params["selectLevel"] = str(select_level)
     if presentation_name_pattern:
         params["presentationNamePattern"] = presentation_name_pattern
     if language:
@@ -259,7 +259,7 @@ def variant_at(
     variant_name: str,
     date: str,
     select_codes: str = "",
-    select_level: str = "",
+    select_level: int = 0,
     presentation_name_pattern: str = "",
     language: str = "nb",
     include_future: bool = False,
@@ -274,7 +274,7 @@ def variant_at(
     if select_codes:
         params["selectCodes"] = select_codes
     if select_level:
-        params["selectLevel"] = select_level
+        params["selectLevel"] = str(select_level)
     if presentation_name_pattern:
         params["presentationNamePattern"] = presentation_name_pattern
     if language:
diff --git a/src/klass/utility/cleanup.py b/src/klass/utility/cleanup.py
new file mode 100644
index 0000000..f923f44
--- /dev/null
+++ b/src/klass/utility/cleanup.py
@@ -0,0 +1,19 @@
+import pandas as pd
+
+
+def drop_empty_columns(data: pd.DataFrame) -> pd.DataFrame:
+    """Drop columns in dataset that are empty strings, or all NA.
+
+    Args:
+        data: The dataframe to check for columns to drop.
+
+    Returns:
+        pd.DataFrame: A modified dataframe with the empty columns removed.
+    """
+    cols_na = data.isna().all()
+    drop_cols = list(cols_na[cols_na].index)
+    string_cols = data.select_dtypes(["object", "string"])
+    for col in string_cols.columns:
+        if ((string_cols[col] == "") | string_cols[col].isna()).all():
+            drop_cols.append(col)
+    return data.drop(columns=drop_cols)
diff --git a/src/klass/utility/naming.py b/src/klass/utility/naming.py
new file mode 100644
index 0000000..0f65763
--- /dev/null
+++ b/src/klass/utility/naming.py
@@ -0,0 +1,44 @@
+from typing import Any
+
+
+def create_shortname(elem: Any, shortname_len: int = 3) -> str:
+    """Create a column name from an object that has a target or name attribute.
+
+    Args:
+        elem: The object that has the target/name attribute.
+        shortname_len: The amount of elements from the name attribute to use in the shortname.
+
+    Returns:
+        str: A constructed column name containing no spaces.
+
+    Raises:
+        ValueError: If the object is missing a name attribute.
+    """
+    if hasattr(elem, "target") and elem.target:
+        name = elem.target
+    elif not hasattr(elem, "name") or not elem.name:
+        raise ValueError("Object is missing valid target/name attribute.")
+    else:
+        name = elem.name
+    print(name)
+    name = name.lower()
+    replace = {
+        k: ""
+        for k in [
+            chr(i) for i in range(33, 127) if not chr(i).isalnum() and chr(i) != "-"
+        ]
+    } | {
+        "og ": "",
+        "and ": "",
+        "æ": "ae",
+        "ø": "oe",
+        "å": "aa",
+        "-": "_",  # Overloads the one coming from the first dict
+    }
+    for k, v in replace.items():
+        name = name.replace(k, v)
+
+    parts = name.split(" ")
+    if len(parts) < shortname_len:
+        shortname_len = len(parts)
+    return "_".join(parts[:shortname_len]).lower()


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 8788c08..6bdeb44 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -58,7 +58,7 @@ def klass_version_success(test_version_success):
     test_version_success.return_value = mock_returns.version_by_id_success()
     return klass.KlassVersion(
         "1954",
-        select_level="1",
+        select_level=1,
         language="en",
         include_future=True,
     )
diff --git a/tests/mock_response_data.py b/tests/mock_response_data.py
index b7c41f0..89ff8ed 100644
--- a/tests/mock_response_data.py
+++ b/tests/mock_response_data.py
@@ -167,7 +167,17 @@ def version_by_id_fake_content():
         '"_links":{"self":{"href":"https://data.ssb.no/api/klass/v1/correspondencetables/447"},'
         '"source":{"href":"https://data.ssb.no/api/klass/v1/versions/50"},'
         '"target":{"href":"https://data.ssb.no/api/klass/v1/versions/51"}}}],'
-        '"classificationVariants":[],'
+        '"classificationVariants":['
+        '{"name": "Studiepoeng ved fagskole",'
+        '"id": 1959,'
+        '"contactPerson": {"name": "Blergherson",'
+        '"email": "Blergherson@ssb.no",'
+        '"phone": "999999999"},'
+        '"owningSection": "360 - Seksjon for Blerghermergerson",'
+        '"lastModified": "2023-09-14T10:36:29.000+0000",'
+        '"published": ["nb", "nn", "en"],'
+        '"_links": {"self": {"href": "https://data.ssb.no/api/klass/v1/variants/1959"}}}'
+        "],"
         '"changelogs":[],'
         '"levels":[{"levelNumber":1,"levelName":"Sivilstand"}],'
         '"classificationItems":[{"code":"1","parentCode":"","level":"1","name":"Ugift","shortName":null,"notes":null},'
diff --git a/tests/test_classifications.py b/tests/test_classifications.py
index 6f6a981..ffee7f9 100644
--- a/tests/test_classifications.py
+++ b/tests/test_classifications.py
@@ -42,3 +42,19 @@ def test_get_version_from_classification(
     assert isinstance(version, klass.KlassVersion)
     assert isinstance(version.data, pd.DataFrame)
     assert len(version.data)
+
+
+@mock.patch("klass.KlassVariant")
+@mock.patch.object(klass.KlassClassification, "get_version")
+def test_get_variant_with_only_search_string(
+    mock_get_version,
+    klassvariant_mock,
+    klass_classification_success,
+    klass_version_success,
+    klass_variant_success,
+):
+    mock_get_version.return_value = klass_version_success
+    klassvariant_mock.return_value = klass_variant_success
+    variant = klass_classification_success.get_latest_variant_by_name("fagskole")
+    assert isinstance(variant.data, pd.DataFrame)
+    assert len(variant.data) > 0
diff --git a/tests/test_cleanup.py b/tests/test_cleanup.py
new file mode 100644
index 0000000..8c579ee
--- /dev/null
+++ b/tests/test_cleanup.py
@@ -0,0 +1,29 @@
+import pandas as pd
+import pytest
+
+from klass.utility.cleanup import drop_empty_columns
+
+
+@pytest.mark.parametrize(
+    "input_data, expected_data",
+    [
+        # Case: Drops columns that are all NaN
+        (pd.DataFrame({"A": [None, None], "B": [1, 2]}), pd.DataFrame({"B": [1, 2]})),
+        # Case: Drops columns that are empty strings
+        (
+            pd.DataFrame({"A": ["", ""], "B": ["data", "values"]}),
+            pd.DataFrame({"B": ["data", "values"]}),
+        ),
+        # Case: Mixed empty strings and NaNs
+        (pd.DataFrame({"A": ["", None], "B": [1, 2]}), pd.DataFrame({"B": [1, 2]})),
+        # Case: No columns dropped (all valid data)
+        (
+            pd.DataFrame({"A": ["valid", "data"], "B": [1, 2]}),
+            pd.DataFrame({"A": ["valid", "data"], "B": [1, 2]}),
+        ),
+    ],
+)
+def test_drop_empty_columns(input_data, expected_data):
+    """Tests that drop_empty_columns correctly removes empty columns."""
+    result = drop_empty_columns(input_data)
+    pd.testing.assert_frame_equal(result, expected_data)
diff --git a/tests/test_naming.py b/tests/test_naming.py
new file mode 100644
index 0000000..07a501e
--- /dev/null
+++ b/tests/test_naming.py
@@ -0,0 +1,45 @@
+"""Thanks chatgippity."""
+
+import pytest
+
+from klass.utility.naming import create_shortname
+
+
+class TestNamingObject:
+    def __init__(self, name=None, target=None):
+        """Mock class for testing naming-function."""
+        self.name = name
+        self.target = target
+
+
+@pytest.mark.parametrize(
+    "obj,shortname_len,expected",
+    [
+        (TestNamingObject(name="example name"), 2, "example_name"),
+        (TestNamingObject(target="custom target"), 2, "custom_target"),
+        (TestNamingObject(name="Hello World Python"), 3, "hello_world_python"),
+        (TestNamingObject(name="Hello World Python"), 2, "hello_world"),
+        (TestNamingObject(name="Hello World Python"), 5, "hello_world_python"),
+        (TestNamingObject(name="æ ø å"), 3, "ae_oe_aa"),
+        (TestNamingObject(name="This&That!More"), 3, "thisthatmore"),
+        (TestNamingObject(name="hyphen-ated words"), 3, "hyphen_ated_words"),
+        (TestNamingObject(name="og apple and banana"), 3, "apple_banana"),
+        (
+            TestNamingObject(name="Test! Case? With@ Special# Chars$"),
+            3,
+            "test_case_with",
+        ),
+        (TestNamingObject(name="MixedÆØÅCharacters"), 3, "mixedaeoeaacharacters"),
+        (TestNamingObject(name="Short"), 3, "short"),
+    ],
+)
+def test_create_shortname(obj, shortname_len, expected):
+    assert create_shortname(obj, shortname_len) == expected
+
+
+def test_create_shortname_missing_attribute():
+    obj = TestNamingObject()  # No name or target attribute set
+    with pytest.raises(
+        ValueError, match="Object is missing valid target/name attribute."
+    ):
+        create_shortname(obj)
diff --git a/tests/test_request_functions.py b/tests/test_request_functions.py
index 9f1ab02..b1143e2 100644
--- a/tests/test_request_functions.py
+++ b/tests/test_request_functions.py
@@ -73,7 +73,7 @@ def test_codes(mock_response):
     from_date = "2022-01-01"
     to_date = "2022-12-31"
     select_codes = "1"
-    select_level = "1"
+    select_level = 1
     presentation_name_pattern = "{name} - {code}"
     language = "nn"
     include_future = True
@@ -103,7 +103,7 @@ def test_codes_at(mock_response):
     classification_id = "36"
     date = "2022-01-01"
     select_codes = "1"
-    select_level = "1"
+    select_level = 1
     presentation_name_pattern = "{name} - {code}"
     language = "nn"
     include_future = True
@@ -151,7 +151,7 @@ def test_variant(mock_response):
     from_date = "2022-01-01"
     to_date = "2022-12-31"
     select_codes = "1"
-    select_level = "1"
+    select_level = 1
     presentation_name_pattern = "{name} - {code}"
     language = "nn"
     include_future = True
@@ -182,7 +182,7 @@ def test_variant_at(mock_response):
     variant_name = "Variant name"
     date = "2022-01-01"
     select_codes = "1"
-    select_level = "1"
+    select_level = 1
     presentation_name_pattern = "{name} - {code}"
     language = "nn"
     include_future = True
diff --git a/tests/test_variant.py b/tests/test_variant.py
index a63ee90..b4b9ff6 100644
--- a/tests/test_variant.py
+++ b/tests/test_variant.py
@@ -11,7 +11,7 @@ def test_variant_classificationitems_has_expected_content(
 def test_variant_has_str_repr(klass_variant_success):
     klass_variant_success.to_date = "2023-01-01"
     klass_variant_success.select_codes = "1"
-    klass_variant_success.select_level = "1"
+    klass_variant_success.select_level = 1
     klass_variant_success.presentation_name_pattern = r"{name} - {code}"
     klass_variant_success.language = "nn"
     klass_variant_success.include_future = True
@@ -21,3 +21,11 @@ def test_variant_has_str_repr(klass_variant_success):
     assert len(klass_variant_success.__repr__())
     print(klass_variant_success)
     repr(klass_variant_success)
+
+
+def test_variant_to_dict(klass_variant_success):
+    dict_check = klass_variant_success.to_dict()
+    default_dict_check = klass_variant_success.to_dict(other="other")
+    assert len(dict_check)
+    assert len(default_dict_check)
+    assert default_dict_check["missing_key"] == "other"
diff --git a/tests/test_variant_search.py b/tests/test_variant_search.py
index 27e884a..739fef1 100644
--- a/tests/test_variant_search.py
+++ b/tests/test_variant_search.py
@@ -16,3 +16,11 @@ def test_variant_has_str_repr(klass_variant_search_success):
     assert len(klass_variant_search_success.__repr__())
     print(klass_variant_search_success)
     repr(klass_variant_search_success)
+
+
+def test_variant_to_dict(klass_variant_search_success):
+    dict_check = klass_variant_search_success.to_dict()
+    default_dict_check = klass_variant_search_success.to_dict(other="other")
+    assert len(dict_check)
+    assert len(default_dict_check)
+    assert default_dict_check["missing_key"] == "other"
diff --git a/tests/test_version.py b/tests/test_version.py
index 7f3b87a..0f9cd05 100644
--- a/tests/test_version.py
+++ b/tests/test_version.py
@@ -7,7 +7,7 @@ def test_version_initialization(klass_version_success):
 
 
 def test_version_has_str_repr(klass_version_success):
-    klass_version_success.select_level = "1"
+    klass_version_success.select_level = 1
     klass_version_success.language = "nn"
     klass_version_success.include_future = True
     assert klass_version_success.__str__()