From c78967c2d42c4f5904ae2914aed66851e3f831c5 Mon Sep 17 00:00:00 2001 From: Michael Aydinbas Date: Sat, 4 Nov 2023 22:35:10 +0100 Subject: [PATCH] Feat/8 handle multiple databases and users (#20) * change config module to handle multiple databases * finalize work on config module to handle multiple databases; significantly reduced lines of code by getting rid of the settings.ini * add a new db module that serves as a layer between the user and the config. Can set the current active database and get the settings from the config * simplify config module * refactor code to implement new config; correct tests * fix all remaining tests * fix all text issues * update notebooks according to latest changes in config * drop support for Python 3.9 due to pipe operator for types and set supported versions to 3.10 and 3.11 * fix problem with config dir creation during setup * fix isort * Improve clear_cache output for full wipe, remove unused import * Address all non global-related pylint issues #20 * because of complexity get rid of the current support of custom config dir and always use the default config dir under user home. * fix all tests; get rid of settings.ini and functionality for user to define own config path; pystatis supports only default config path but custom data cache path * fix all tests; get rid of settings.ini and functionality for user to define own config path; pystatis supports only default config path but custom data cache path * refactor config module to work with a ConfigParser global config object instead of overwriting the config variable within the functions using global (bad style according to pylint) * address pylint issues * fix mypy issues * fix pylint issues --------- Co-authored-by: MarcoHuebner --- .github/workflows/run-tests.yaml | 42 +- mypy.ini | 2 +- nb/00_Setup.ipynb | 135 +++++ nb/01_Databases.ipynb | 123 +++++ nb/{helloworld.ipynb => 02_helloworld.ipynb} | 27 +- nb/cache.ipynb | 21 +- nb/cube.ipynb | 17 +- nb/init_config.ipynb | 126 ----- nb/table.ipynb | 486 +----------------- poetry.lock | 150 ++---- pyproject.toml | 11 +- src/pystatis/__init__.py | 11 +- src/pystatis/cache.py | 47 +- src/pystatis/config.py | 259 ++++++---- src/pystatis/db.py | 61 +++ .../{custom_exceptions.py => exception.py} | 6 + src/pystatis/helloworld.py | 13 +- src/pystatis/http_helper.py | 16 +- src/pystatis/profile.py | 19 +- tests/test_cache.py | 49 +- tests/test_config.py | 165 +++--- tests/test_db.py | 55 ++ tests/test_helloworld.py | 25 +- tests/test_http_helper.py | 11 +- tests/test_profile.py | 59 +-- tests/test_version.py | 9 +- 26 files changed, 846 insertions(+), 1099 deletions(-) create mode 100644 nb/00_Setup.ipynb create mode 100644 nb/01_Databases.ipynb rename nb/{helloworld.ipynb => 02_helloworld.ipynb} (67%) delete mode 100644 nb/init_config.ipynb create mode 100644 src/pystatis/db.py rename src/pystatis/{custom_exceptions.py => exception.py} (63%) create mode 100644 tests/test_db.py diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 59bb3d9..655ea09 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -6,12 +6,12 @@ name: Run tests on: push: branches: - - main - - dev + - main + - dev pull_request: branches: - - main - - dev + - main + - dev workflow_dispatch: env: @@ -24,31 +24,31 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10"] - os: [ubuntu-22.04, macOS-latest, windows-latest] + python-version: ["3.10", "3.11"] + os: [ubuntu-latest, macOS-latest, windows-latest] steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Run poetry image - uses: abatilo/actions-poetry@v2.0.0 - with: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Run poetry image + uses: abatilo/actions-poetry@v2.0.0 + with: poetry-version: $POETRY_VERSION - - name: Install dependencies - run: | - poetry install --with dev - - name: Run tests - run: | - poetry run pytest --cov=pystatis tests + - name: Install dependencies + run: | + poetry install --with dev + - name: Run tests + run: | + poetry run pytest --cov=pystatis tests code-quality: strategy: fail-fast: false matrix: # only support specific python version, as guidelines differ beween (minor) versions - python-version: ["3.10.13"] + python-version: ["3.11.6"] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: diff --git a/mypy.ini b/mypy.ini index dd2284f..614f660 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,7 +1,7 @@ # Global options: [mypy] -python_version = 3.9 +python_version = 3.10 warn_return_any = True warn_unused_configs = True show_error_codes=True diff --git a/nb/00_Setup.ipynb b/nb/00_Setup.ipynb new file mode 100644 index 0000000..f7d8335 --- /dev/null +++ b/nb/00_Setup.ipynb @@ -0,0 +1,135 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Setup `pystatis`\n", + "\n", + "You don't need to do much to use `pystatis`. Basically, the first time you import the package, it will create a `config.ini` file under `~/.pystatis`. This file is used for storing settings, for example your credentials fpr the supported databases.\n", + "\n", + "To set up your credentials, we need to ask you for your username and password. This is done by the `setup_credentials()` function. It will ask you interactivly for the credentials, or you can set the following environmental variables:\n", + "- `PYSTATIS_GENESIS_API_USERNAME`\n", + "- `PYSTATIS_GENESIS_API_PASSWORD`\n", + "- `PYSTATIS_ZENSUS_API_USERNAME`\n", + "- `PYSTATIS_ZENSUS_API_PASSWORD`\n", + "- `PYSTATIS_REGIO_API_USERNAME`\n", + "- `PYSTATIS_REGIO_API_PASSWORD`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`dotenv` is uses here to load a local `.env` file that contains the above mentioned environmental variables so we don't have to input them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "888706f5-3a9e-4e0a-9ca6-fa430280bc03", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "import dotenv\n", + "\n", + "import pystatis\n", + "\n", + "print(\"pystatis version: \", pystatis.__version__)\n", + "dotenv.load_dotenv()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee7969b6", + "metadata": {}, + "outputs": [], + "source": [ + "# only execute if you want to delete your config file for test purposes\n", + "# config.delete_config()" + ] + }, + { + "cell_type": "markdown", + "id": "3928f347", + "metadata": {}, + "source": [ + "`init_config` is called when loading pystatis, so a config with empty credentials will be created in your user home directory by default." + ] + }, + { + "cell_type": "markdown", + "id": "18c2633f", + "metadata": {}, + "source": [ + "The only thing you have to do is to set up your user credentials.\n", + "\n", + "You can do so either by:\n", + "1. specifying the 4 environment variables `PYSTATIS_GENESIS_API_USERNAME|PASSWORD`, and `PYSTATIS_ZENSUS_API_USERNAME|PASSWORD`\n", + "2. calling the function `setup_credentials()` which will guide you through the process\n", + "\n", + "Even if you do 1. please call `setup_credentials()` once as it will read out the environment variables and write the credentials to the `config.ini` in your config dir." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c301da5", + "metadata": {}, + "outputs": [], + "source": [ + "pystatis.setup_credentials() # also part of config module" + ] + }, + { + "cell_type": "markdown", + "id": "d7d92f0d", + "metadata": {}, + "source": [ + "Once you have set up your credentials, they are stored in the `config.ini` and in the `config` object of the `config.py` module. You don't have to know this as regular user, this is more internal knowledge." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Warning**: The following code will print out the content of your `config.ini` file **with** the credentials set, so please do not share or push this notebook with outputs enabled." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7ce103d9", + "metadata": {}, + "outputs": [], + "source": [ + "with open(Path.home() / \".pystatis\" / \"config.ini\") as f:\n", + " print(f.read())" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/nb/01_Databases.ipynb b/nb/01_Databases.ipynb new file mode 100644 index 0000000..f7098a6 --- /dev/null +++ b/nb/01_Databases.ipynb @@ -0,0 +1,123 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using different databases\n", + "\n", + "Once you have at least once called `setup_credentials()` you are good to use any method of this package with any of the supported databases.\n", + "\n", + "However, before you can do so, you have to tell `pystatis` which database you want to work with. \n", + "\n", + "This can be done with the `set_db()` function that is used to lock in the database for all subsequent called functions.\n", + "\n", + "If you don't know the names of the supported databases, ask `pystatis.config.get_supported_db()`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pystatis import set_db, logincheck, db\n", + "from pystatis import config" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "config.get_supported_db()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Whenever `pystatis` needs to fetch data from a database, it calls `get_db()` internally to get the database set is currently set as active database. If `set_db()` was not called before, `get_db()` will throw an error and inform you about this." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# expected to fail so you know you have to call set_db() first\n", + "db.get_db()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# expected to fail!\n", + "set_db(\"test\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "set_db(\"genesis\")\n", + "logincheck()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "set_db(\"zensus\")\n", + "logincheck()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "set_db(\"regio\")\n", + "logincheck()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "pystatis", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/nb/helloworld.ipynb b/nb/02_helloworld.ipynb similarity index 67% rename from nb/helloworld.ipynb rename to nb/02_helloworld.ipynb index 4fb6fae..b803753 100644 --- a/nb/helloworld.ipynb +++ b/nb/02_helloworld.ipynb @@ -1,15 +1,30 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Testing your credentials with the helloworld endpoints\n", + "\n", + "Once you have completed the setup as described [here](./00_Setup.ipynb), you can test your credentials with the `logincheck()` function oh the `helloworld` module." + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "import logging\n", - "logging.basicConfig(level=logging.INFO)\n", - "\n", - "from pystatis import logincheck, whoami" + "from pystatis import logincheck, whoami, set_db" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "set_db(\"genesis\")" ] }, { @@ -35,7 +50,7 @@ "metadata": {}, "outputs": [], "source": [ - "# test your login credentials (set via config functions)\n", + "# test your login credentials (set via setup_credentials() from config module)\n", "logincheck()" ] } @@ -56,7 +71,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.11.6" }, "orig_nbformat": 4, "vscode": { diff --git a/nb/cache.ipynb b/nb/cache.ipynb index 474b9c6..bfdb171 100644 --- a/nb/cache.ipynb +++ b/nb/cache.ipynb @@ -10,19 +10,18 @@ "import logging\n", "logging.basicConfig(level=logging.INFO)\n", "\n", - "import pystatis\n", - "from pystatis import Cube, Table, init_config, clear_cache" + "from pystatis import Cube, Table, clear_cache\n", + "from pystatis.db import set_db" ] }, { "cell_type": "code", "execution_count": null, - "id": "6c4b965e", + "id": "5c944d20", "metadata": {}, "outputs": [], "source": [ - "# only run this if you haven't done so earlier\n", - "# init_config()" + "set_db(\"genesis\")" ] }, { @@ -34,7 +33,7 @@ "source": [ "# first, let's download two data sets\n", "# these will be cached under your \n", - "Cube(name=\"22922KJ1141\").get_data()\n", + "# Cube(name=\"22922KJ1141\").get_data() # TODO: currently broken\n", "Table(name=\"21311-0001\").get_data()" ] }, @@ -60,6 +59,14 @@ "# Or without a name to clear the whole cache at once\n", "clear_cache()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7219b44b", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -78,7 +85,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.11.5" }, "vscode": { "interpreter": { diff --git a/nb/cube.ipynb b/nb/cube.ipynb index ef9584e..db56f0a 100644 --- a/nb/cube.ipynb +++ b/nb/cube.ipynb @@ -10,7 +10,18 @@ "import logging\n", "logging.basicConfig(level=logging.INFO)\n", "\n", - "from pystatis import Cube" + "from pystatis import Cube\n", + "from pystatis.db import set_db" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "812c0e39", + "metadata": {}, + "outputs": [], + "source": [ + "set_db(\"genesis\")" ] }, { @@ -30,7 +41,7 @@ "metadata": {}, "outputs": [], "source": [ - "c.get_data()" + "c.get_data() # TODO: currently broken?" ] }, { @@ -105,7 +116,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.11.5" }, "vscode": { "interpreter": { diff --git a/nb/init_config.ipynb b/nb/init_config.ipynb deleted file mode 100644 index 04b50c8..0000000 --- a/nb/init_config.ipynb +++ /dev/null @@ -1,126 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 5, - "id": "0f4d35be-2e17-4e1a-8da4-0a3075f9911f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Collecting python-dotenv\n", - " Downloading python_dotenv-1.0.0-py3-none-any.whl (19 kB)\n", - "Installing collected packages: python-dotenv\n", - "Successfully installed python-dotenv-1.0.0\n" - ] - } - ], - "source": [ - "!pip install python-dotenv" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "888706f5-3a9e-4e0a-9ca6-fa430280bc03", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "pystatis version: 0.1.4\n" - ] - }, - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import os\n", - "\n", - "import dotenv\n", - "import pystatis\n", - "\n", - "print(\"pystatis version: \", pystatis.__version__)\n", - "dotenv.load_dotenv()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "2c6fd817-ca4b-4843-bac9-c6e90e0b1f17", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "\u001b[0;31mSignature:\u001b[0m\n", - "\u001b[0mpystatis\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minit_config\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0musername\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mpassword\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mconfig_dir\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mpathlib\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mPath\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mPosixPath\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'/Users/miay/.pystatis'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDocstring:\u001b[0m\n", - "One-time function to be called for new users to create a new config.ini with default values.\n", - "\n", - "Stores username and password for the GENESIS API, among other settings.\n", - "\n", - "Args:\n", - " username (str): username used to login to GENESIS-Online.\n", - " password (str): password used to login to GENESIS-Online.\n", - " config_dir (Path, optional): Path to the root config directory. Defaults to the user home directory.\n", - "\u001b[0;31mFile:\u001b[0m ~/git/github/CorrelAid/pystatis/src/pystatis/config.py\n", - "\u001b[0;31mType:\u001b[0m function" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "?pystatis.init_config" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "899b9737-a208-4655-b3be-ad7c568cdd2d", - "metadata": {}, - "outputs": [], - "source": [ - "pystatis.init_config(username=os.environ[\"GENESIS_API_USER\"], password=os.environ[\"GENESIS_API_PASSWORD\"])" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.5" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/nb/table.ipynb b/nb/table.ipynb index b8e37cf..aa44914 100644 --- a/nb/table.ipynb +++ b/nb/table.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "165da3a1", "metadata": { "scrolled": true @@ -12,12 +12,23 @@ "import logging\n", "logging.basicConfig(level=logging.INFO)\n", "\n", - "from pystatis import Table" + "from pystatis import Table\n", + "from pystatis import db" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, + "id": "c500ccb8", + "metadata": {}, + "outputs": [], + "source": [ + "db.set_db(\"genesis\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, "id": "5d25c79a", "metadata": {}, "outputs": [], @@ -27,499 +38,52 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "632fc783", "metadata": { "scrolled": false }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "INFO:pystatis.cache:Data was successfully cached under C:\\Users\\micha\\.pystatis\\data\\21311-0001\\5d17194a9264f364cb40\\20220922.txt.\n", - "INFO:pystatis.http_helper:Code 0 : erfolgreich\n" - ] - } - ], + "outputs": [], "source": [ "t.get_data()" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "f5f1aded", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'21311-0001'" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "t.name" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "8fede338", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Statistik_Code;Statistik_Label;Zeit_Code;Zeit_Label;Zeit;1_Merkmal_Code;1_Merkmal_Label;1_Auspraegung_Code;1_Auspraegung_Label;2_Merkmal_Code;2_Merkmal_Label;2_Auspraegung_Code;2_Auspraegung_Label;3_Merkmal_Code;3_Merkmal_Label;3_Auspraegung_Code;3_Auspraegung_Label;BIL002__Studierende__Anzahl\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1998/99;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;907403\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1998/99;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;727254\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1998/99;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;1634657\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1998/99;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;92321\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1998/99;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;73673\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1998/99;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;165994\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1998/99;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;999724\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1998/99;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;800927\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1998/99;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;1800651\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1999/00;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;872178\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1999/00;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;723246\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1999/00;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;1595424\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1999/00;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;95460\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1999/00;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;79605\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1999/00;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;175065\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1999/00;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;967638\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1999/00;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;802851\\n21311;Statistik der Studenten;SEMEST;Semester;WS 1999/00;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;1770489\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2000/01;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;870016\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2000/01;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;741820\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2000/01;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;1611836\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2000/01;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;99906\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2000/01;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;87121\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2000/01;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;187027\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2000/01;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;969922\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2000/01;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;828941\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2000/01;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;1798863\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2001/02;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;887462\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2001/02;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;774628\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2001/02;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;1662090\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2001/02;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;107831\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2001/02;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;98410\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2001/02;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;206241\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2001/02;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;995293\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2001/02;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;873038\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2001/02;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;1868331\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2002/03;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;903218\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2002/03;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;808567\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2002/03;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;1711785\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2002/03;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;117205\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2002/03;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;109821\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2002/03;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;227026\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2002/03;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1020423\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2002/03;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;918388\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2002/03;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;1938811\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2003/04;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;935718\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2003/04;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;837611\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2003/04;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;1773329\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2003/04;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;125826\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2003/04;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;120310\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2003/04;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;246136\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2003/04;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1061544\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2003/04;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;957921\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2003/04;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2019465\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2004/05;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;901979\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2004/05;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;814795\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2004/05;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;1716774\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2004/05;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;124220\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2004/05;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;122114\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2004/05;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;246334\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2004/05;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1026199\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2004/05;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;936909\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2004/05;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;1963108\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2005/06;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;912696\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2005/06;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;824712\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2005/06;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;1737408\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2005/06;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;124447\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2005/06;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;123910\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2005/06;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;248357\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2005/06;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1037143\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2005/06;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;948622\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2005/06;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;1985765\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2006/07;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;909740\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2006/07;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;822934\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2006/07;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;1732674\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2006/07;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;122923\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2006/07;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;123446\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2006/07;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;246369\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2006/07;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1032663\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2006/07;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;946380\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2006/07;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;1979043\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2007/08;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;898061\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2007/08;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;809738\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2007/08;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;1707799\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2007/08;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;116700\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2007/08;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;116906\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2007/08;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;233606\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2007/08;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1014761\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2007/08;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;926644\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2007/08;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;1941405\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2008/09;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;938552\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2008/09;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;847612\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2008/09;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;1786164\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2008/09;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;119254\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2008/09;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;119889\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2008/09;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;239143\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2008/09;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1057806\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2008/09;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;967501\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2008/09;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2025307\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2009/10;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;984097\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2009/10;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;892306\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2009/10;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;1876403\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2009/10;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;122353\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2009/10;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;122422\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2009/10;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;244775\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2009/10;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1106450\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2009/10;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;1014728\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2009/10;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2121178\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2010/11;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;1031086\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2010/11;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;934176\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2010/11;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;1965262\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2010/11;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;126399\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2010/11;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;125633\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2010/11;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;252032\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2010/11;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1157485\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2010/11;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;1059809\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2010/11;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2217294\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2011/12;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;1122200\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2011/12;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;993482\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2011/12;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;2115682\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2011/12;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;133172\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2011/12;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;132120\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2011/12;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;265292\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2011/12;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1255372\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2011/12;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;1125602\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2011/12;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2380974\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2012/13;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;1171894\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2012/13;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;1045314\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2012/13;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;2217208\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2012/13;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;142123\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2012/13;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;140078\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2012/13;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;282201\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2012/13;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1314017\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2012/13;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;1185392\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2012/13;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2499409\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2013/14;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;1218965\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2013/14;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;1096566\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2013/14;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;2315531\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2013/14;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;152675\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2013/14;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;148675\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2013/14;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;301350\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2013/14;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1371640\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2013/14;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;1245241\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2013/14;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2616881\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2014/15;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;1245029\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2014/15;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;1132312\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2014/15;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;2377341\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2014/15;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;163505\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2014/15;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;158064\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2014/15;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;321569\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2014/15;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1408534\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2014/15;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;1290376\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2014/15;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2698910\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2015/16;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;1260203\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2015/16;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;1157291\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2015/16;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;2417494\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2015/16;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;173923\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2015/16;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;166382\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2015/16;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;340305\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2015/16;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1434126\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2015/16;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;1323673\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2015/16;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2757799\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2016/17;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;1269166\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2016/17;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;1178949\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2016/17;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;2448115\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2016/17;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;184459\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2016/17;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;174436\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2016/17;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;358895\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2016/17;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1453625\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2016/17;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;1353385\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2016/17;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2807010\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2017/18;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;1270098\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2017/18;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;1200297\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2017/18;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;2470395\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2017/18;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;194545\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2017/18;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;180038\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2017/18;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;374583\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2017/18;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1464643\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2017/18;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;1380335\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2017/18;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2844978\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2018/19;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;1258281\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2018/19;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;1215276\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2018/19;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;2473557\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2018/19;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;207697\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2018/19;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;186968\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2018/19;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;394665\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2018/19;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1465978\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2018/19;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;1402244\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2018/19;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2868222\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2019/20;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;1246852\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2019/20;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;1232596\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2019/20;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;2479448\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2019/20;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;218015\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2019/20;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;193586\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2019/20;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;411601\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2019/20;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1464867\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2019/20;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;1426182\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2019/20;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2891049\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2020/21;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;1253399\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2020/21;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;1274309\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2020/21;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;2527708\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2020/21;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;222967\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2020/21;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;193470\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2020/21;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;416437\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2020/21;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1476366\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2020/21;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;1467779\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2020/21;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2944145\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2021/22;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESM;männlich;1231256\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2021/22;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;GESW;weiblich;1270095\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2021/22;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATD;Deutsche;GES;Geschlecht;;Insgesamt;2501351\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2021/22;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESM;männlich;235026\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2021/22;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;GESW;weiblich;205538\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2021/22;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;NATA;Ausländer;GES;Geschlecht;;Insgesamt;440564\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2021/22;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESM;männlich;1466282\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2021/22;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;GESW;weiblich;1475633\\n21311;Statistik der Studenten;SEMEST;Semester;WS 2021/22;DINSG;Deutschland insgesamt;DG;Deutschland;NAT;Nationalität;;Insgesamt;GES;Geschlecht;;Insgesamt;2941915\\n'" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "t.raw_data" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "874bbbb9", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Statistik_CodeStatistik_LabelZeit_CodeZeit_LabelZeit1_Merkmal_Code1_Merkmal_Label1_Auspraegung_Code1_Auspraegung_Label2_Merkmal_Code2_Merkmal_Label2_Auspraegung_Code2_Auspraegung_Label3_Merkmal_Code3_Merkmal_Label3_Auspraegung_Code3_Auspraegung_LabelBIL002__Studierende__Anzahl
021311Statistik der StudentenSEMESTSemesterWS 1998/99DINSGDeutschland insgesamtDGDeutschlandNATNationalitätNATDDeutscheGESGeschlechtGESMmännlich907403
121311Statistik der StudentenSEMESTSemesterWS 1998/99DINSGDeutschland insgesamtDGDeutschlandNATNationalitätNATDDeutscheGESGeschlechtGESWweiblich727254
221311Statistik der StudentenSEMESTSemesterWS 1998/99DINSGDeutschland insgesamtDGDeutschlandNATNationalitätNATDDeutscheGESGeschlechtNaNInsgesamt1634657
321311Statistik der StudentenSEMESTSemesterWS 1998/99DINSGDeutschland insgesamtDGDeutschlandNATNationalitätNATAAusländerGESGeschlechtGESMmännlich92321
421311Statistik der StudentenSEMESTSemesterWS 1998/99DINSGDeutschland insgesamtDGDeutschlandNATNationalitätNATAAusländerGESGeschlechtGESWweiblich73673
.........................................................
21121311Statistik der StudentenSEMESTSemesterWS 2021/22DINSGDeutschland insgesamtDGDeutschlandNATNationalitätNATAAusländerGESGeschlechtGESWweiblich205538
21221311Statistik der StudentenSEMESTSemesterWS 2021/22DINSGDeutschland insgesamtDGDeutschlandNATNationalitätNATAAusländerGESGeschlechtNaNInsgesamt440564
21321311Statistik der StudentenSEMESTSemesterWS 2021/22DINSGDeutschland insgesamtDGDeutschlandNATNationalitätNaNInsgesamtGESGeschlechtGESMmännlich1466282
21421311Statistik der StudentenSEMESTSemesterWS 2021/22DINSGDeutschland insgesamtDGDeutschlandNATNationalitätNaNInsgesamtGESGeschlechtGESWweiblich1475633
21521311Statistik der StudentenSEMESTSemesterWS 2021/22DINSGDeutschland insgesamtDGDeutschlandNATNationalitätNaNInsgesamtGESGeschlechtNaNInsgesamt2941915
\n", - "

216 rows × 18 columns

\n", - "
" - ], - "text/plain": [ - " Statistik_Code Statistik_Label Zeit_Code Zeit_Label Zeit \\\n", - "0 21311 Statistik der Studenten SEMEST Semester WS 1998/99 \n", - "1 21311 Statistik der Studenten SEMEST Semester WS 1998/99 \n", - "2 21311 Statistik der Studenten SEMEST Semester WS 1998/99 \n", - "3 21311 Statistik der Studenten SEMEST Semester WS 1998/99 \n", - "4 21311 Statistik der Studenten SEMEST Semester WS 1998/99 \n", - ".. ... ... ... ... ... \n", - "211 21311 Statistik der Studenten SEMEST Semester WS 2021/22 \n", - "212 21311 Statistik der Studenten SEMEST Semester WS 2021/22 \n", - "213 21311 Statistik der Studenten SEMEST Semester WS 2021/22 \n", - "214 21311 Statistik der Studenten SEMEST Semester WS 2021/22 \n", - "215 21311 Statistik der Studenten SEMEST Semester WS 2021/22 \n", - "\n", - " 1_Merkmal_Code 1_Merkmal_Label 1_Auspraegung_Code \\\n", - "0 DINSG Deutschland insgesamt DG \n", - "1 DINSG Deutschland insgesamt DG \n", - "2 DINSG Deutschland insgesamt DG \n", - "3 DINSG Deutschland insgesamt DG \n", - "4 DINSG Deutschland insgesamt DG \n", - ".. ... ... ... \n", - "211 DINSG Deutschland insgesamt DG \n", - "212 DINSG Deutschland insgesamt DG \n", - "213 DINSG Deutschland insgesamt DG \n", - "214 DINSG Deutschland insgesamt DG \n", - "215 DINSG Deutschland insgesamt DG \n", - "\n", - " 1_Auspraegung_Label 2_Merkmal_Code 2_Merkmal_Label 2_Auspraegung_Code \\\n", - "0 Deutschland NAT Nationalität NATD \n", - "1 Deutschland NAT Nationalität NATD \n", - "2 Deutschland NAT Nationalität NATD \n", - "3 Deutschland NAT Nationalität NATA \n", - "4 Deutschland NAT Nationalität NATA \n", - ".. ... ... ... ... \n", - "211 Deutschland NAT Nationalität NATA \n", - "212 Deutschland NAT Nationalität NATA \n", - "213 Deutschland NAT Nationalität NaN \n", - "214 Deutschland NAT Nationalität NaN \n", - "215 Deutschland NAT Nationalität NaN \n", - "\n", - " 2_Auspraegung_Label 3_Merkmal_Code 3_Merkmal_Label 3_Auspraegung_Code \\\n", - "0 Deutsche GES Geschlecht GESM \n", - "1 Deutsche GES Geschlecht GESW \n", - "2 Deutsche GES Geschlecht NaN \n", - "3 Ausländer GES Geschlecht GESM \n", - "4 Ausländer GES Geschlecht GESW \n", - ".. ... ... ... ... \n", - "211 Ausländer GES Geschlecht GESW \n", - "212 Ausländer GES Geschlecht NaN \n", - "213 Insgesamt GES Geschlecht GESM \n", - "214 Insgesamt GES Geschlecht GESW \n", - "215 Insgesamt GES Geschlecht NaN \n", - "\n", - " 3_Auspraegung_Label BIL002__Studierende__Anzahl \n", - "0 männlich 907403 \n", - "1 weiblich 727254 \n", - "2 Insgesamt 1634657 \n", - "3 männlich 92321 \n", - "4 weiblich 73673 \n", - ".. ... ... \n", - "211 weiblich 205538 \n", - "212 Insgesamt 440564 \n", - "213 männlich 1466282 \n", - "214 weiblich 1475633 \n", - "215 Insgesamt 2941915 \n", - "\n", - "[216 rows x 18 columns]" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "t.data" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "81d27994", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'Copyright': '© Statistisches Bundesamt (Destatis), 2022',\n", - " 'Ident': {'Method': 'table', 'Service': 'metadata'},\n", - " 'Object': {'Code': '21311-0001',\n", - " 'Content': 'Studierende: Deutschland, Semester, Nationalität,\\n'\n", - " 'Geschlecht',\n", - " 'Structure': {'Columns': [{'Code': 'NAT',\n", - " 'Content': 'Nationalität',\n", - " 'Selected': None,\n", - " 'Structure': [{'Code': 'GES',\n", - " 'Content': 'Geschlecht',\n", - " 'Selected': None,\n", - " 'Structure': None,\n", - " 'Type': 'Merkmal',\n", - " 'Updated': 'see parent',\n", - " 'Values': None}],\n", - " 'Type': 'Merkmal',\n", - " 'Updated': 'see parent',\n", - " 'Values': None}],\n", - " 'Head': {'Code': '21311',\n", - " 'Content': 'Statistik der Studenten',\n", - " 'Selected': None,\n", - " 'Structure': [{'Code': 'DINSG',\n", - " 'Content': 'Deutschland '\n", - " 'insgesamt',\n", - " 'Selected': '1',\n", - " 'Structure': [{'Code': 'BIL002',\n", - " 'Content': 'Studierende',\n", - " 'Selected': None,\n", - " 'Structure': None,\n", - " 'Type': 'Merkmal',\n", - " 'Updated': 'see '\n", - " 'parent',\n", - " 'Values': None}],\n", - " 'Type': 'Merkmal',\n", - " 'Updated': 'see parent',\n", - " 'Values': '1'}],\n", - " 'Type': 'Statistik',\n", - " 'Updated': 'see parent',\n", - " 'Values': None},\n", - " 'Rows': [{'Code': 'SEMEST',\n", - " 'Content': 'Semester',\n", - " 'Selected': None,\n", - " 'Structure': None,\n", - " 'Type': 'Merkmal',\n", - " 'Updated': 'see parent',\n", - " 'Values': None}],\n", - " 'Subheading': None,\n", - " 'Subtitel': None},\n", - " 'Time': {'From': 'WS 1998/99', 'To': 'WS 2021/22'},\n", - " 'Updated': '13.08.2013 13:41:54h',\n", - " 'Valid': 'false'},\n", - " 'Parameter': {'area': 'Alle',\n", - " 'language': 'de',\n", - " 'name': '21311-0001',\n", - " 'password': '********************',\n", - " 'username': '********************'},\n", - " 'Status': {'Code': 0, 'Content': 'erfolgreich', 'Type': 'Information'}}\n" - ] - } - ], + "outputs": [], "source": [ "from pprint import pprint\n", "\n", @@ -543,7 +107,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.11.5" }, "vscode": { "interpreter": { diff --git a/poetry.lock b/poetry.lock index 6d9a463..b1baa46 100644 --- a/poetry.lock +++ b/poetry.lock @@ -124,20 +124,21 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} [[package]] name = "asttokens" -version = "2.4.0" +version = "2.4.1" description = "Annotate AST trees with source code positions" optional = false python-versions = "*" files = [ - {file = "asttokens-2.4.0-py2.py3-none-any.whl", hash = "sha256:cf8fc9e61a86461aa9fb161a14a0841a03c405fa829ac6b202670b3495d2ce69"}, - {file = "asttokens-2.4.0.tar.gz", hash = "sha256:2e0171b991b2c959acc6c49318049236844a5da1d65ba2672c4880c1c894834e"}, + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, ] [package.dependencies] six = ">=1.12.0" [package.extras] -test = ["astroid", "pytest"] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] [[package]] name = "async-lru" @@ -705,13 +706,13 @@ test = ["pytest (>=6)"] [[package]] name = "executing" -version = "2.0.0" +version = "2.0.1" description = "Get the currently executing AST node of a frame, and other information" optional = false -python-versions = "*" +python-versions = ">=3.5" files = [ - {file = "executing-2.0.0-py2.py3-none-any.whl", hash = "sha256:06df6183df67389625f4e763921c6cf978944721abf3e714000200aab95b0657"}, - {file = "executing-2.0.0.tar.gz", hash = "sha256:0ff053696fdeef426cda5bd18eacd94f82c91f49823a2e9090124212ceea9b08"}, + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, ] [package.extras] @@ -733,19 +734,19 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc [[package]] name = "filelock" -version = "3.12.4" +version = "3.13.0" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, - {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, + {file = "filelock-3.13.0-py3-none-any.whl", hash = "sha256:a552f4fde758f4eab33191e9548f671970f8b06d436d31388c9aa1e5861a710f"}, + {file = "filelock-3.13.0.tar.gz", hash = "sha256:63c6052c82a1a24c873a549fbd39a26982e8f35a3016da231ead11a5be9dad44"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] -typing = ["typing-extensions (>=4.7.1)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] [[package]] name = "flake8" @@ -822,13 +823,13 @@ test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre [[package]] name = "identify" -version = "2.5.30" +version = "2.5.31" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.30-py2.py3-none-any.whl", hash = "sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54"}, - {file = "identify-2.5.30.tar.gz", hash = "sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d"}, + {file = "identify-2.5.31-py2.py3-none-any.whl", hash = "sha256:90199cb9e7bd3c5407a9b7e81b4abec4bb9d249991c79439ec8af740afc6293d"}, + {file = "identify-2.5.31.tar.gz", hash = "sha256:7736b3c7a28233637e3c36550646fc6389bedd74ae84cb788200cc8e2dd60b75"}, ] [package.extras] @@ -845,25 +846,6 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -[[package]] -name = "importlib-metadata" -version = "6.8.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -933,7 +915,6 @@ prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" pygments = ">=2.4.0" stack-data = "*" traitlets = ">=5" -typing-extensions = {version = "*", markers = "python_version < \"3.10\""} [package.extras] all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] @@ -1147,7 +1128,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" python-dateutil = ">=2.8.2" pyzmq = ">=23.0" @@ -1239,18 +1219,17 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} jupyter-server = ">=1.1.2" [[package]] name = "jupyter-server" -version = "2.9.0" +version = "2.9.1" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_server-2.9.0-py3-none-any.whl", hash = "sha256:c02501c5028af95242558c39ac986420be55b92ad69c2f94709a26ee521d87aa"}, - {file = "jupyter_server-2.9.0.tar.gz", hash = "sha256:06a334ffba7797e84c9e0e26c510b78bb8cc9051485ecab7f4e3c0963b8fc714"}, + {file = "jupyter_server-2.9.1-py3-none-any.whl", hash = "sha256:21ad1a3d455d5a79ce4bef5201925cd17510c17898cf9d54e3ccfb6b12734948"}, + {file = "jupyter_server-2.9.1.tar.gz", hash = "sha256:9ba71be4b9c16e479e4c50c929f8ac4b1015baf90237a08681397a98c76c7e5e"}, ] [package.dependencies] @@ -1310,7 +1289,6 @@ files = [ [package.dependencies] async-lru = ">=1.0.0" -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} ipykernel = "*" jinja2 = ">=3.0.3" jupyter-core = "*" @@ -1353,7 +1331,6 @@ files = [ [package.dependencies] babel = ">=2.10" -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} jinja2 = ">=3.0.3" json5 = ">=0.9.0" jsonschema = ">=4.18.0" @@ -1611,7 +1588,6 @@ files = [ beautifulsoup4 = "*" bleach = "!=5.0.0" defusedxml = "*" -importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} jinja2 = ">=3.0" jupyter-core = ">=4.7" jupyterlab-pygments = "*" @@ -1877,42 +1853,42 @@ xml = ["lxml (>=4.8.0)"] [[package]] name = "pandas" -version = "2.1.1" +version = "2.1.2" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" files = [ - {file = "pandas-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58d997dbee0d4b64f3cb881a24f918b5f25dd64ddf31f467bb9b67ae4c63a1e4"}, - {file = "pandas-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02304e11582c5d090e5a52aec726f31fe3f42895d6bfc1f28738f9b64b6f0614"}, - {file = "pandas-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffa8f0966de2c22de408d0e322db2faed6f6e74265aa0856f3824813cf124363"}, - {file = "pandas-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1f84c144dee086fe4f04a472b5cd51e680f061adf75c1ae4fc3a9275560f8f4"}, - {file = "pandas-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ce97667d06d69396d72be074f0556698c7f662029322027c226fd7a26965cb"}, - {file = "pandas-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:4c3f32fd7c4dccd035f71734df39231ac1a6ff95e8bdab8d891167197b7018d2"}, - {file = "pandas-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e2959720b70e106bb1d8b6eadd8ecd7c8e99ccdbe03ee03260877184bb2877d"}, - {file = "pandas-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25e8474a8eb258e391e30c288eecec565bfed3e026f312b0cbd709a63906b6f8"}, - {file = "pandas-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8bd1685556f3374520466998929bade3076aeae77c3e67ada5ed2b90b4de7f0"}, - {file = "pandas-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc3657869c7902810f32bd072f0740487f9e030c1a3ab03e0af093db35a9d14e"}, - {file = "pandas-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:05674536bd477af36aa2effd4ec8f71b92234ce0cc174de34fd21e2ee99adbc2"}, - {file = "pandas-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:b407381258a667df49d58a1b637be33e514b07f9285feb27769cedb3ab3d0b3a"}, - {file = "pandas-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c747793c4e9dcece7bb20156179529898abf505fe32cb40c4052107a3c620b49"}, - {file = "pandas-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3bcad1e6fb34b727b016775bea407311f7721db87e5b409e6542f4546a4951ea"}, - {file = "pandas-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5ec7740f9ccb90aec64edd71434711f58ee0ea7f5ed4ac48be11cfa9abf7317"}, - {file = "pandas-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29deb61de5a8a93bdd033df328441a79fcf8dd3c12d5ed0b41a395eef9cd76f0"}, - {file = "pandas-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f99bebf19b7e03cf80a4e770a3e65eee9dd4e2679039f542d7c1ace7b7b1daa"}, - {file = "pandas-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:84e7e910096416adec68075dc87b986ff202920fb8704e6d9c8c9897fe7332d6"}, - {file = "pandas-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366da7b0e540d1b908886d4feb3d951f2f1e572e655c1160f5fde28ad4abb750"}, - {file = "pandas-2.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e50e72b667415a816ac27dfcfe686dc5a0b02202e06196b943d54c4f9c7693e"}, - {file = "pandas-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc1ab6a25da197f03ebe6d8fa17273126120874386b4ac11c1d687df288542dd"}, - {file = "pandas-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0dbfea0dd3901ad4ce2306575c54348d98499c95be01b8d885a2737fe4d7a98"}, - {file = "pandas-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0489b0e6aa3d907e909aef92975edae89b1ee1654db5eafb9be633b0124abe97"}, - {file = "pandas-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:4cdb0fab0400c2cb46dafcf1a0fe084c8bb2480a1fa8d81e19d15e12e6d4ded2"}, - {file = "pandas-2.1.1.tar.gz", hash = "sha256:fecb198dc389429be557cde50a2d46da8434a17fe37d7d41ff102e3987fd947b"}, + {file = "pandas-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:24057459f19db9ebb02984c6fdd164a970b31a95f38e4a49cf7615b36a1b532c"}, + {file = "pandas-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6cf8fcc8a63d333970b950a7331a30544cf59b1a97baf0a7409e09eafc1ac38"}, + {file = "pandas-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ae6ffbd9d614c20d028c7117ee911fc4e266b4dca2065d5c5909e401f8ff683"}, + {file = "pandas-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff794eeb7883c5aefb1ed572e7ff533ae779f6c6277849eab9e77986e352688"}, + {file = "pandas-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02954e285e8e2f4006b6f22be6f0df1f1c3c97adbb7ed211c6b483426f20d5c8"}, + {file = "pandas-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:5b40c9f494e1f27588c369b9e4a6ca19cd924b3a0e1ef9ef1a8e30a07a438f43"}, + {file = "pandas-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08d287b68fd28906a94564f15118a7ca8c242e50ae7f8bd91130c362b2108a81"}, + {file = "pandas-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bbd98dcdcd32f408947afdb3f7434fade6edd408c3077bbce7bd840d654d92c6"}, + {file = "pandas-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e90c95abb3285d06f6e4feedafc134306a8eced93cb78e08cf50e224d5ce22e2"}, + {file = "pandas-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52867d69a54e71666cd184b04e839cff7dfc8ed0cd6b936995117fdae8790b69"}, + {file = "pandas-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d0382645ede2fde352da2a885aac28ec37d38587864c0689b4b2361d17b1d4c"}, + {file = "pandas-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:65177d1c519b55e5b7f094c660ed357bb7d86e799686bb71653b8a4803d8ff0d"}, + {file = "pandas-2.1.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5aa6b86802e8cf7716bf4b4b5a3c99b12d34e9c6a9d06dad254447a620437931"}, + {file = "pandas-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d594e2ce51b8e0b4074e6644758865dc2bb13fd654450c1eae51201260a539f1"}, + {file = "pandas-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3223f997b6d2ebf9c010260cf3d889848a93f5d22bb4d14cd32638b3d8bba7ad"}, + {file = "pandas-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4944dc004ca6cc701dfa19afb8bdb26ad36b9bed5bcec617d2a11e9cae6902"}, + {file = "pandas-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3f76280ce8ec216dde336e55b2b82e883401cf466da0fe3be317c03fb8ee7c7d"}, + {file = "pandas-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:7ad20d24acf3a0042512b7e8d8fdc2e827126ed519d6bd1ed8e6c14ec8a2c813"}, + {file = "pandas-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:021f09c15e1381e202d95d4a21ece8e7f2bf1388b6d7e9cae09dfe27bd2043d1"}, + {file = "pandas-2.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7f12b2de0060b0b858cfec0016e7d980ae5bae455a1746bfcc70929100ee633"}, + {file = "pandas-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c166b9bb27c1715bed94495d9598a7f02950b4749dba9349c1dd2cbf10729d"}, + {file = "pandas-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25c9976c17311388fcd953cb3d0697999b2205333f4e11e669d90ff8d830d429"}, + {file = "pandas-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:851b5afbb0d62f6129ae891b533aa508cc357d5892c240c91933d945fff15731"}, + {file = "pandas-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:e78507adcc730533619de07bfdd1c62b2918a68cd4419ea386e28abf7f6a1e5c"}, + {file = "pandas-2.1.2.tar.gz", hash = "sha256:52897edc2774d2779fbeb6880d2cfb305daa0b1a29c16b91f531a18918a6e0f3"}, ] [package.dependencies] numpy = [ - {version = ">=1.22.4", markers = "python_version < \"3.11\""}, - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -2224,15 +2200,14 @@ astroid = ">=3.0.1,<=3.1.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, ] isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} tomlkit = ">=0.10.1" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] spelling = ["pyenchant (>=3.2,<4.0)"] @@ -2779,13 +2754,13 @@ files = [ [[package]] name = "ruamel-yaml" -version = "0.18.2" +version = "0.18.3" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false python-versions = ">=3" files = [ - {file = "ruamel.yaml-0.18.2-py3-none-any.whl", hash = "sha256:92076ac8a83dbf44ca661dbed3c935229c8cbc2f10b05959dd3bd5292d8353d3"}, - {file = "ruamel.yaml-0.18.2.tar.gz", hash = "sha256:9bce33f7a814cea4c29a9c62fe872d2363d6220b767891d956eacea8fa5e6fe8"}, + {file = "ruamel.yaml-0.18.3-py3-none-any.whl", hash = "sha256:b5d119e1f9678cf90b58f64bbd2a4e78af76860ae39fab3e73323e622b462df9"}, + {file = "ruamel.yaml-0.18.3.tar.gz", hash = "sha256:36dbbe90390d977f957436570d2bd540bfd600e6ec5a1ea42bcdb9fc7963d802"}, ] [package.dependencies] @@ -3240,22 +3215,7 @@ files = [ {file = "widgetsnbextension-4.0.9.tar.gz", hash = "sha256:3c1f5e46dc1166dfd40a42d685e6a51396fd34ff878742a3e47c6f0cc4a2a385"}, ] -[[package]] -name = "zipp" -version = "3.17.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - [metadata] lock-version = "2.0" -python-versions = "^3.9" -content-hash = "2d7e7a3af1e597dfe6076741513ab7296bf2800a33357752f626f1e4e0b032c8" +python-versions = "^3.10" +content-hash = "d9b55b106b5b3edae9285d4be14a504629d795fc12cb760a2ebee088f6892784" diff --git a/pyproject.toml b/pyproject.toml index 6982380..4f1e32d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,12 +8,10 @@ authors = [ "Frederik Hering ", "Marco Hübner " ] -maintainers = [ - "Michael Aydinbas " -] +maintainers = ["Michael Aydinbas "] description = "Python wrapper for GENESIS web service interface (API) of the Federal Statistical Office." name = "pystatis" -version = "0.1.4" +version = "0.1.5" readme = "README.md" repository = "https://github.com/CorrelAid/pystatis/tree/v0.1.0" classifiers = [ @@ -26,16 +24,15 @@ classifiers = [ "Operating System :: Unix", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Information Analysis", "Topic :: Software Development :: Libraries :: Python Modules" ] [tool.poetry.dependencies] -python = "^3.9" +python = "^3.10" requests = "^2.27.1" pandas = "^2.0" tabulate = "^0.8.10" diff --git a/src/pystatis/__init__.py b/src/pystatis/__init__.py index 99a6479..f76c1c4 100644 --- a/src/pystatis/__init__.py +++ b/src/pystatis/__init__.py @@ -8,23 +8,22 @@ ``` """ from pystatis.cache import clear_cache -from pystatis.config import init_config +from pystatis.config import setup_credentials from pystatis.cube import Cube +from pystatis.db import set_db from pystatis.find import Find from pystatis.helloworld import logincheck, whoami -from pystatis.profile import change_password, remove_result from pystatis.table import Table -__version__ = "0.1.4" +__version__ = "0.1.5" __all__ = [ - "change_password", "clear_cache", "Cube", "Find", - "init_config", "logincheck", - "remove_result", + "set_db", + "setup_credentials", "Table", "whoami", ] diff --git a/src/pystatis/cache.py b/src/pystatis/cache.py index 1c0f0ec..94d52c6 100644 --- a/src/pystatis/cache.py +++ b/src/pystatis/cache.py @@ -6,17 +6,19 @@ import shutil import zipfile from datetime import date +from operator import attrgetter from pathlib import Path from typing import Optional -from pystatis.config import load_config +from pystatis import config logger = logging.getLogger(__name__) + JOB_ID_PATTERN = r"\d+" def cache_data( - cache_dir: Path, + cache_dir: str, name: Optional[str], params: dict, data: str, @@ -28,10 +30,8 @@ def cache_data( This allows to cache different results for different params. Args: - cache_dir (Path): The cash directory as configured in the config. + cache_dir (str): The cash directory as configured in the config. name (str): The unique identifier in GENESIS-Online. - endpoint (str): The endpoint for this data request. - method (str): The method for this data request. params (dict): The dictionary holding the params for this data request. data (str): The actual raw text data as returned by GENESIS-Online. """ @@ -64,17 +64,15 @@ def cache_data( def read_from_cache( - cache_dir: Path, + cache_dir: str, name: Optional[str], params: dict, ) -> str: """Read and return compressed data from cache. Args: - cache_dir (Path): The cash directory as configured in the config. + cache_dir (str): The cash directory as configured in the config. name (str): The unique identifier in GENESIS-Online. - endpoint (str): The endpoint for this data request. - method (str): The method for this data request. params (dict): The dictionary holding the params for this data request. Returns: @@ -85,11 +83,11 @@ def read_from_cache( data_dir = _build_file_path(cache_dir, name, params) - versions = sorted( + latest_version = sorted( data_dir.glob("*"), - key=lambda path: int(path.stem), - ) - file_name = versions[-1].name + key=attrgetter("stem"), + )[-1] + file_name = latest_version.name file_path = data_dir / file_name with zipfile.ZipFile(file_path, "r") as myzip: with myzip.open(file_name.replace(".zip", ".txt")) as file: @@ -98,15 +96,15 @@ def read_from_cache( return data -def _build_file_path(cache_dir: Path, name: str, params: dict) -> Path: +def _build_file_path(cache_dir: str, name: str, params: dict) -> Path: """Builds a unique cache directory name from name and hashed params dictionary. The way this method works is that it creates a path under cache dir that is unique - because the name is a unique EVAS identifier number in Destatis and the hash - is (close enough) unique to a given dictionary with query parameter values. + because the name is a unique EVAS identifier number in Destatis and the hash is + (close enough) unique to a given dictionary with query parameter values. Args: - cache_dir (Path): The root cache directory as configured in the config.ini. + cache_dir (str): The root cache directory as configured in the config.ini. name (str): The unique identifier for an object in Destatis. params (dict): The query parameters for a given call to the Destatis API. @@ -120,7 +118,7 @@ def _build_file_path(cache_dir: Path, name: str, params: dict) -> Path: del params_["job"] params_hash = hashlib.blake2s(digest_size=10, usedforsecurity=False) params_hash.update(json.dumps(params_).encode("UTF-8")) - data_dir = cache_dir / name / params_hash.hexdigest() + data_dir = Path(cache_dir) / name / params_hash.hexdigest() return data_dir @@ -141,17 +139,15 @@ def normalize_name(name: str) -> str: def hit_in_cash( - cache_dir: Path, + cache_dir: str, name: Optional[str], params: dict, ) -> bool: """Check if data is already cached. Args: - cache_dir (Path): The cash directory as configured in the config. + cache_dir (str): The cash directory as configured in the config. name (str): The unique identifier in GENESIS-Online. - endpoint (str): The endpoint for this data request. - method (str): The method for this data request. params (dict): The dictionary holding the params for this data request. Returns: @@ -170,12 +166,13 @@ def clear_cache(name: Optional[str] = None) -> None: Args: name (str, optional): Unique name to be deleted from cached data. """ - config = load_config() - cache_dir = Path(config["DATA"]["cache_dir"]) + cache_dir = Path(config.get_cache_dir()) # remove specified file (directory) from the data cache # or clear complete cache (remove childs, preserve base) - file_paths = [cache_dir / name] if name is not None else cache_dir.iterdir() + file_paths = ( + [cache_dir / name] if name is not None else list(cache_dir.iterdir()) + ) for file_path in file_paths: # delete if file or symlink, otherwise remove complete tree diff --git a/src/pystatis/config.py b/src/pystatis/config.py index 415d120..536dae6 100644 --- a/src/pystatis/config.py +++ b/src/pystatis/config.py @@ -1,155 +1,200 @@ -"""Module for handling settings.ini and config.ini files. - -This package stores core information in the settings.ini, which is stored under the user home directory. -The parent directory for the settings.ini is called after the package name. -The settings.ini gets automatically created by importing this package, if it does not exist already. -The config.ini is stored in a directory that is configured in the settings.ini via `config_dir`. -The config.ini holds all revelant information about the usage of GENESIS API like credentials. -If there is no config.ini in the given config_dir, a default config will be created with empty credentials. +"""Module for handling `config.ini` files. + +This package stores core information in the `config.ini`, + which is stored under the user home directory. +The user can change the default config directory + by setting the environment variable `PYSTATIS_CONFIG_DIR` + or pass a custom directory to the `init_config` function. +The current config directory is always stored in the `settings.ini` file, + which is located under the default config directory, + which the user can not change. +If the user does not specify a custom config directory, + the default config directory is used, + which is `~/.pystatis` on Linux and `%USERHOME%/.pystatis` on Windows. +The `config.ini` holds all relevant information + about all supported databases like user credentials. +When the package is loaded for the first time, + a default config will be created with empty credentials. + Subsequent calls to other `pystatis` functions will throw an error + until the user has filled in the credentials. """ import logging +import os from configparser import ConfigParser from pathlib import Path PKG_NAME = __name__.split(".", maxsplit=1)[0] +DEFAULT_CONFIG_DIR = str(Path().home() / f".{PKG_NAME}") +SUPPORTED_DB = ["genesis", "zensus", "regio"] logger = logging.getLogger(__name__) +config = ConfigParser(interpolation=None) -DEFAULT_CONFIG_DIR = Path().home() / f".{PKG_NAME}" -DEFAULT_SETTINGS_FILE = DEFAULT_CONFIG_DIR / "settings.ini" +def init_config() -> None: + """Create a new config .ini file in the given directory. -def create_settings() -> None: - """Create a settings.ini file within the default config folder in the user home directory.""" - if not DEFAULT_SETTINGS_FILE.exists(): - default_settings = ConfigParser() - default_settings["SETTINGS"] = {"config_dir": str(DEFAULT_CONFIG_DIR)} + One-time function to be called for new users to create a new `config.ini` with default values (empty credentials). - _write_config(default_settings, DEFAULT_SETTINGS_FILE) - logger.info( - "Settings file was created. Path: %s.", DEFAULT_SETTINGS_FILE - ) - - -def load_settings() -> ConfigParser: - """Load the config from settings.ini. - - Returns: - ConfigParser: Sections and key-value pairs from settings.ini. + Args: + config_dir (str, optional): Path to the root config directory. Defaults to the user home directory. """ - settings_file = DEFAULT_SETTINGS_FILE + if not config_exists(): + create_default_config() + write_config() + else: + loaded_config = load_config() + for section in loaded_config.sections(): + config.add_section(section) + for option in loaded_config.options(section): + config.set(section, option, loaded_config.get(section, option)) - settings = ConfigParser() - settings.read(settings_file) - return settings +def config_exists() -> bool: + """Check if the config file exists.""" + config_file = _build_config_file_path() + return config_file.exists() -def get_config_path_from_settings() -> Path: - """Return full local path to config.ini. +def setup_credentials() -> None: + """Setup credentials for all supported databases.""" + for db in get_supported_db(): + config.set(db, "username", _get_user_input(db, "username")) + config.set(db, "password", _get_user_input(db, "password")) - Returns: - Path: Path to config_dir / config.ini. - """ - settings = load_settings() - return Path(settings.get("SETTINGS", "config_dir")) / "config.ini" + write_config() + logger.info( + "Config was updated with latest credentials. Path: %s.", + _build_config_file_path(), + ) -def init_config( - username: str, password: str, config_dir: Path = DEFAULT_CONFIG_DIR -) -> None: - """One-time function to be called for new users to create a new config.ini with default values. - Stores username and password for the GENESIS API, among other settings. +def _build_config_file_path() -> Path: + """Build the path to the config file.""" + return Path(DEFAULT_CONFIG_DIR) / "config.ini" - Args: - username (str): username used to login to GENESIS-Online. - password (str): password used to login to GENESIS-Online. - config_dir (Path, optional): Path to the root config directory. Defaults to the user home directory. - """ - default_settings = load_settings() - default_settings["SETTINGS"]["config_dir"] = str(config_dir) - _write_config(default_settings, DEFAULT_SETTINGS_FILE) - logger.info( - "Settings file updated: config_dir set to %s. Path: %s.", - config_dir, - DEFAULT_SETTINGS_FILE, - ) +def _get_user_input(db: str, field: str) -> str: + """Get user input for the given database and field.""" + env_var = os.environ.get(f"PYSTATIS_{db.upper()}_API_{field.upper()}") - config_file = get_config_path_from_settings() - config = _create_default_config() - config["GENESIS API"]["username"] = username - config["GENESIS API"]["password"] = password - _write_config(config, config_file) + if env_var is not None: + return env_var - cache_dir = Path(config["DATA"]["cache_dir"]) - if not cache_dir.exists(): - cache_dir.mkdir() + user_input = input(f"Please enter your {field} for the database {db}:") + print("You entered: " + user_input) - logger.info("New config was created. Path: %s.", config_file) + return user_input -def load_config() -> ConfigParser: - """Load the config from config.ini. +def load_config(config_file: Path | None = None) -> ConfigParser: + """Load a config from a file.""" + if config_file is None: + config_file = _build_config_file_path() - Returns: - ConfigParser: Sections and key-value pairs from config.ini. - """ - config_file = get_config_path_from_settings() - config = _load_config(config_file) + loaded_config = ConfigParser(interpolation=None) + successful_reads = loaded_config.read(config_file) + + if not successful_reads: + logger.critical( + "Error while loading the config file. Could not find %s. " + "Please make sure to run init_config() first. ", + config_file, + ) - if config.has_section("GENESIS API"): - if not config.get("GENESIS API", "username") or not config.get( - "GENESIS API", "password" - ): - logger.critical( - "Username and/or password are missing! " - "Please make sure to fill in your username and password for GENESIS API. " - "Path: %s.", - config_file, - ) + return loaded_config - return config +def write_config() -> None: + """Write a config to a file.""" + config_file = _build_config_file_path() -def _write_config(config: ConfigParser, config_file: Path) -> None: if not config_file.parent.exists(): config_file.parent.mkdir(parents=True) + cache_dir = Path(get_cache_dir()) + if not cache_dir.exists(): + cache_dir.mkdir() + with open(config_file, "w", encoding="utf-8") as fp: config.write(fp) -def _load_config(config_file: Path) -> ConfigParser: - config = ConfigParser() - successful_reads = config.read(config_file) +def create_default_config() -> None: + """Create a default config parser with empty credentials.""" + config.add_section("settings") + config.set("settings", "active_db", "") + config.set("settings", "supported_db", ",".join(SUPPORTED_DB)) + + config.add_section("genesis") + config.set( + "genesis", + "base_url", + "https://www-genesis.destatis.de/genesisWS/rest/2020/", + ) + config.set("genesis", "username", "") + config.set("genesis", "password", "") + config.set( + "genesis", + "doku", + "https://www-genesis.destatis.de/genesis/misc/GENESIS-Webservices_Einfuehrung.pdf", + ) + + config.add_section("zensus") + config.set( + "zensus", + "base_url", + "https://ergebnisse2011.zensus2022.de/api/rest/2020/", + ) + config.set("zensus", "username", "") + config.set("zensus", "password", "") + config.set( + "zensus", + "doku", + "https://ergebnisse2011.zensus2022.de/datenbank/misc/ZENSUS-Webservices_Einfuehrung.pdf", + ) + + config.add_section("regio") + config.set( + "regio", + "base_url", + "https://www.regionalstatistik.de/genesisws/rest/2020/", + ) + config.set("regio", "username", "") + config.set("regio", "password", "") + config.set( + "regio", + "doku", + "https://www.regionalstatistik.de/genesis/misc/GENESIS-Webservices_Einfuehrung.pdf", + ) + + config.add_section("data") + cache_dir = Path(DEFAULT_CONFIG_DIR) / "data" + config.set("data", "cache_dir", str(cache_dir)) + + +def get_supported_db() -> list[str]: + """Get a list of supported database names.""" + return SUPPORTED_DB - if not successful_reads: - logger.critical( - "Error while loading the config file. Could not find %s. " - "Please make sure to run init_config() first. ", - config_file, - ) - return config +def get_cache_dir() -> str: + """Get the cache directory.""" + return config.get("data", "cache_dir") -def _create_default_config() -> ConfigParser: - config = ConfigParser() - settings = load_settings() - config["GENESIS API"] = { - "base_url": "https://www-genesis.destatis.de/genesisWS/rest/2020/", - "username": "", - "password": "", - "doku": "https://www-genesis.destatis.de/genesis/misc/GENESIS-Webservices_Einfuehrung.pdf", - } +def delete_config() -> None: + """Delete the config file.""" + if config_exists(): + config_file = _build_config_file_path() + config_file.unlink() - config["DATA"] = { - "cache_dir": str(Path(settings["SETTINGS"]["config_dir"]) / "data") - } + for section in config.sections(): + config.remove_section(section) + init_config() - return config + logger.info("Config was deleted. Path: %s.", config_file) -create_settings() +init_config() diff --git a/src/pystatis/db.py b/src/pystatis/db.py new file mode 100644 index 0000000..e39dda2 --- /dev/null +++ b/src/pystatis/db.py @@ -0,0 +1,61 @@ +"""Module provides functions to set the active database and get active database properties.""" +import logging + +from pystatis import config +from pystatis.exception import PystatisConfigError + +logger = logging.getLogger(__name__) + + +def set_db(name: str) -> None: + """Set the active database. + + Args: + name (str): Name of the database. Must be one of the supported databases. + See `pystatis.config.get_supported_db()`. + """ + if name.lower() not in config.get_supported_db(): + raise ValueError( + f"Database {name} not supported! Please choose one of {', '.join(config.get_supported_db())}" + ) + config.config.set("settings", "active_db", name.lower()) + + if not get_db_user() or not get_db_pw(): + logger.critical( + "No credentials for %s found. Please run `setup_credentials()`.", + name, + ) + + +def get_db() -> str: + """Get the active database.""" + active_db = config.config.get("settings", "active_db") + + if not active_db: + raise PystatisConfigError( + "No active database set! Please run `set_db()`." + ) + + return active_db + + +def get_db_host() -> str: + return config.config[get_db()]["base_url"] + + +def get_db_user() -> str: + return config.config[get_db()]["username"] + + +def get_db_pw() -> str: + return config.config[get_db()]["password"] + + +def set_db_pw(new_pw: str) -> None: + config.config.set(get_db(), "password", new_pw) + config.write_config() + + +def get_db_settings() -> tuple[str, str, str]: + """Get the active database settings (host, user, password).""" + return get_db_host(), get_db_user(), get_db_pw() diff --git a/src/pystatis/custom_exceptions.py b/src/pystatis/exception.py similarity index 63% rename from src/pystatis/custom_exceptions.py rename to src/pystatis/exception.py index 2b09525..a30bd23 100644 --- a/src/pystatis/custom_exceptions.py +++ b/src/pystatis/exception.py @@ -5,3 +5,9 @@ class DestatisStatusError(ValueError): """Raised when Destatis status code indicates an error ("Fehler")""" pass + + +class PystatisConfigError(Exception): + """Raised when pystatis configuration is invalid.""" + + pass diff --git a/src/pystatis/helloworld.py b/src/pystatis/helloworld.py index 865c853..89f85a3 100644 --- a/src/pystatis/helloworld.py +++ b/src/pystatis/helloworld.py @@ -2,7 +2,7 @@ import requests -from pystatis.config import load_config +from pystatis import db from pystatis.http_helper import _check_invalid_status_code @@ -14,8 +14,7 @@ def whoami() -> str: Returns: str: text test response from Destatis """ - config = load_config() - url = f"{config['GENESIS API']['base_url']}" + "helloworld/whoami" + url = f"{db.get_db_host()}" + "helloworld/whoami" response = requests.get(url, timeout=(1, 15)) @@ -32,12 +31,12 @@ def logincheck() -> str: Returns: str: text logincheck response from Destatis """ - config = load_config() - url = f"{config['GENESIS API']['base_url']}" + "helloworld/logincheck" + db_host, db_user, db_pw = db.get_db_settings() + url = f"{db_host}helloworld/logincheck" params = { - "username": config["GENESIS API"]["username"], - "password": config["GENESIS API"]["password"], + "username": db_user, + "password": db_pw, } response = requests.get(url, params=params, timeout=(1, 15)) diff --git a/src/pystatis/http_helper.py b/src/pystatis/http_helper.py index 689eea2..edd0680 100644 --- a/src/pystatis/http_helper.py +++ b/src/pystatis/http_helper.py @@ -3,19 +3,18 @@ import logging import re import time -from pathlib import Path from typing import Union import requests +from pystatis import config, db from pystatis.cache import ( cache_data, hit_in_cash, normalize_name, read_from_cache, ) -from pystatis.config import load_config -from pystatis.custom_exceptions import DestatisStatusError +from pystatis.exception import DestatisStatusError logger = logging.getLogger(__name__) @@ -39,8 +38,7 @@ def load_data( Returns: Union[str, dict]: The data as raw text or JSON dict. """ - config = load_config() - cache_dir = Path(config["DATA"]["cache_dir"]) + cache_dir = config.get_cache_dir() name = params.get("name") if name is not None: @@ -94,15 +92,15 @@ def get_data_from_endpoint( Returns: requests.Response: the response object holding the response from calling the Destatis endpoint. """ - config = load_config() - url = f"{config['GENESIS API']['base_url']}{endpoint}/{method}" + db_host, db_user, db_pw = db.get_db_settings() + url = f"{db_host}{endpoint}/{method}" # params is used to calculate hash for caching so don't alter params dict here! params_ = params.copy() params_.update( { - "username": config["GENESIS API"]["username"], - "password": config["GENESIS API"]["password"], + "username": db_user, + "password": db_pw, } ) diff --git a/src/pystatis/profile.py b/src/pystatis/profile.py index 94bba5c..f845f28 100644 --- a/src/pystatis/profile.py +++ b/src/pystatis/profile.py @@ -3,11 +3,7 @@ import logging from typing import cast -from pystatis.config import ( - _write_config, - get_config_path_from_settings, - load_config, -) +from pystatis import db from pystatis.http_helper import load_data logger = logging.getLogger(__name__) @@ -28,23 +24,12 @@ def change_password(new_password: str) -> str: "repeat": new_password, } - # load config.ini beforehand, to ensure passwords are changed at the same time - config = load_config() - try: - config["GENESIS API"]["password"] - except KeyError as e: - raise KeyError( - "Password not found in config! Please make sure \ - init_config() was run properly & your user data is set correctly!", - ) from e - # change remote password response_text = load_data( endpoint="profile", method="password", params=params ) # change local password - config["GENESIS API"]["password"] = new_password - _write_config(config, get_config_path_from_settings()) + db.set_db_pw(new_password) logger.info("Password changed successfully!") diff --git a/tests/test_cache.py b/tests/test_cache.py index 25196bf..5c6df3f 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -1,8 +1,10 @@ -import re +import shutil +from configparser import RawConfigParser from pathlib import Path import pytest +from pystatis import config from pystatis.cache import ( _build_file_path, cache_data, @@ -11,34 +13,31 @@ normalize_name, read_from_cache, ) -from pystatis.config import ( - DEFAULT_SETTINGS_FILE, - _write_config, - init_config, - load_config, - load_settings, -) @pytest.fixture() -def cache_dir(tmp_path_factory): - # remove white-space and non-latin characters (issue fo some user names) - temp_dir = str(tmp_path_factory.mktemp(".pystatis")) - temp_dir = re.sub(r"[^\x00-\x7f]", r"", temp_dir.replace(" ", "")) - - init_config("myuser", "mypw", temp_dir) - - config = load_config() - cache_dir = Path(config["DATA"]["cache_dir"]) +def config_() -> RawConfigParser: + old_config = config.load_config() + config.delete_config() + yield config.config + config.config = old_config + config.write_config() - return cache_dir - -@pytest.fixture(autouse=True) -def restore_settings(): - old_settings = load_settings() - yield - _write_config(old_settings, DEFAULT_SETTINGS_FILE) +@pytest.fixture() +def cache_dir(config_) -> str: + old_cache_dir = config.get_cache_dir() + config_.set( + "data", + "cache_dir", + str(Path(config.DEFAULT_CONFIG_DIR) / "test-cache-dir"), + ) + cache_dir = config.get_cache_dir() + yield cache_dir + config_.set("data", "cache_dir", old_cache_dir) + if Path(cache_dir).exists(): + # delete cache dir + shutil.rmtree(cache_dir) @pytest.fixture(scope="module") @@ -55,7 +54,7 @@ def test_build_file_path(cache_dir, params): def test_cache_data(cache_dir, params): - assert len(list((cache_dir / "data").glob("*"))) == 0 + assert len(list(Path(cache_dir).glob("*"))) == 0 test_data = "test" cache_data(cache_dir, "test-cache-data", params, test_data) diff --git a/tests/test_config.py b/tests/test_config.py index 45ffc87..0fdaf1f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,128 +1,93 @@ -import logging -import re +import copy +import os from configparser import ConfigParser from pathlib import Path import pytest -import pystatis.config -from pystatis.config import ( - DEFAULT_SETTINGS_FILE, - _write_config, - create_settings, - get_config_path_from_settings, - init_config, - load_config, - load_settings, -) +from pystatis import config @pytest.fixture() -def config_dir(tmp_path_factory): - # remove white-space and non-latin characters (issue fo some user names) - temp_dir = str(tmp_path_factory.mktemp(".pystatis")) - temp_dir = re.sub(r"[^\x00-\x7f]", r"", temp_dir.replace(" ", "")) - - return Path(temp_dir) - - -@pytest.fixture(autouse=True) -def restore_settings(): - old_settings = load_settings() - yield - _write_config(old_settings, DEFAULT_SETTINGS_FILE) - - -def test_create_settings_is_run_on_import(): - assert DEFAULT_SETTINGS_FILE.exists() and DEFAULT_SETTINGS_FILE.is_file() - - -def test_create_settings(config_dir, mocker): - mocker.patch.object(pystatis.config, "DEFAULT_CONFIG_DIR", config_dir) - mocker.patch.object( - pystatis.config, "DEFAULT_SETTINGS_FILE", config_dir / "settings.ini" +def config_() -> ConfigParser: + old_config = config.load_config() + config.delete_config() + yield config.config + config.config = old_config + config.write_config() + + +def test_config_path(): + assert ( + config._build_config_file_path() + == Path(config.DEFAULT_CONFIG_DIR) / "config.ini" + ) + assert config.get_cache_dir() == str( + Path(config.DEFAULT_CONFIG_DIR) / "data" ) - create_settings() - - assert (config_dir / "settings.ini").is_file() - - -def test_load_settings(): - settings = load_settings() - - assert isinstance(settings, ConfigParser) - assert settings.has_option("SETTINGS", "config_dir") - - -def test_get_config_path_from_settings(): - config_path = get_config_path_from_settings() - - assert isinstance(config_path, Path) - - -def test_init_config_with_config_dir(config_dir, caplog): - caplog.clear() - caplog.set_level(logging.INFO) - - init_config("myuser", "mypw", config_dir) - - assert len(caplog.records) == 2 - assert caplog.records[0].levelname == "INFO" - assert caplog.records[1].levelname == "INFO" - assert "Settings file updated" in caplog.text - assert "New config was created" in caplog.text - assert (config_dir / "data").exists() - - config = load_config() - assert isinstance(config, ConfigParser) - assert len(config.sections()) > 0 - assert config["DATA"]["cache_dir"] == str(config_dir / "data") - assert len(list((config_dir / "data").glob("*"))) == 0 - config_file = get_config_path_from_settings() +def test_init_config_is_run_on_import(config_): + assert isinstance(config_, ConfigParser) + assert config._build_config_file_path().exists() + assert Path(config.get_cache_dir()).exists() - assert config_file.exists() and config_file.is_file() +def test_load_config(config_): + assert config_.has_section("settings") + assert config_.has_option("settings", "active_db") + assert config_.has_option("settings", "supported_db") + assert config_.has_section("data") + assert config_.has_option("data", "cache_dir") -def test_load_config(config_dir): - init_config("myuser", "mypw123!", config_dir) - config: ConfigParser = load_config() + for section in config.get_supported_db(): + assert config_.has_section(section) - for section in ["GENESIS API", "DATA"]: - assert config.has_section(section) + assert config_.options(section) == [ + "base_url", + "username", + "password", + "doku", + ] - assert config.options("GENESIS API") == [ - "base_url", - "username", - "password", - "doku", - ] - assert config.options("DATA") == ["cache_dir"] + assert config_.get(section, "username") == "" + assert config_.get(section, "password") == "" - assert config["GENESIS API"]["username"] == "myuser" - assert config["GENESIS API"]["password"] == "mypw123!" +def test_missing_file(config_, caplog): + (Path(config.DEFAULT_CONFIG_DIR) / "config.ini").unlink() -def test_missing_username(config_dir, caplog): - init_config("", "", config_dir) + assert not config.config_exists() caplog.clear() - _ = load_config() + config_ = config.load_config() + assert not config_.sections() - assert caplog.records[0].levelname == "CRITICAL" - assert "Username and/or password are missing!" in caplog.text + for record in caplog.records: + assert record.levelname == "CRITICAL" -def test_missing_file(config_dir, caplog): - init_config("", "", config_dir) - (config_dir / "config.ini").unlink() +def test_setup_credentials(config_): + for db in config.get_supported_db(): + for field in ["username", "password"]: + if field == "username": + os.environ[ + f"PYSTATIS_{db.upper()}_API_{field.upper()}" + ] = "test" + else: + os.environ[ + f"PYSTATIS_{db.upper()}_API_{field.upper()}" + ] = "test123!" - caplog.clear() + config.setup_credentials() - config = load_config() - assert not config.sections() + for db in config.get_supported_db(): + assert config_[db]["username"] == "test" + assert config_[db]["password"] == "test123!" - for record in caplog.records: - assert record.levelname == "CRITICAL" + +def test_supported_db(): + db = config.get_supported_db() + assert isinstance(db, list) + assert isinstance(db[0], str) diff --git a/tests/test_db.py b/tests/test_db.py new file mode 100644 index 0000000..aaaac08 --- /dev/null +++ b/tests/test_db.py @@ -0,0 +1,55 @@ +import logging +from configparser import RawConfigParser + +import pytest + +from pystatis import config, db +from pystatis.exception import PystatisConfigError + + +@pytest.fixture() +def config_() -> RawConfigParser: + old_config = config.load_config() + config.delete_config() + yield config.config + config.config = old_config + config.write_config() + + +@pytest.mark.parametrize("name", ["genesis", "zensus", "regio"]) +def test_set_db(config_, name: str): + db.set_db(name) + + assert db.get_db() == name + + +def test_set_db_with_invalid_name(): + with pytest.raises(ValueError): + db.set_db("invalid_db_name") + + +def test_set_db_without_credentials(config_, caplog): + caplog.clear() + caplog.set_level(logging.CRITICAL) + + db.set_db("genesis") + + assert len(caplog.records) == 1 + assert caplog.records[0].levelname == "CRITICAL" + assert ( + caplog.records[0].message + == "No credentials for genesis found. Please run `setup_credentials()`." + ) + + +def test_get_db_without_set_db(config_): + with pytest.raises(PystatisConfigError): + db.get_db() + + +def test_get_db_settings(config_): + db.set_db("genesis") + settings = db.get_db_settings() + assert isinstance(settings, tuple) + assert len(settings) == 3 + assert all(isinstance(setting, str) for setting in settings) diff --git a/tests/test_helloworld.py b/tests/test_helloworld.py index 0106394..721a794 100644 --- a/tests/test_helloworld.py +++ b/tests/test_helloworld.py @@ -3,21 +3,11 @@ def test_whoami(mocker): - mocker.patch( - "pystatis.helloworld.load_config", - return_value={ - "GENESIS API": { - "base_url": "mocked_url", - "username": "JaneDoe", - "password": "password", - } - }, - ) - mocker.patch( "pystatis.helloworld.requests.get", return_value=_generic_request_status(), ) + mocker.patch("pystatis.db.get_db_host", return_value="genesis") response = whoami() @@ -25,20 +15,13 @@ def test_whoami(mocker): def test_logincheck(mocker): - mocker.patch( - "pystatis.helloworld.load_config", - return_value={ - "GENESIS API": { - "base_url": "mocked_url", - "username": "JaneDoe", - "password": "password", - } - }, - ) mocker.patch( "pystatis.helloworld.requests.get", return_value=_generic_request_status(), ) + mocker.patch( + "pystatis.db.get_db_settings", return_value=("host", "user", "pw") + ) response = logincheck() diff --git a/tests/test_http_helper.py b/tests/test_http_helper.py index 1c8823f..cfffc08 100644 --- a/tests/test_http_helper.py +++ b/tests/test_http_helper.py @@ -4,7 +4,7 @@ import pytest import requests -from pystatis.custom_exceptions import DestatisStatusError +from pystatis.exception import DestatisStatusError from pystatis.http_helper import ( _check_invalid_destatis_status_code, _check_invalid_status_code, @@ -63,14 +63,7 @@ def test_get_response_from_endpoint(mocker): "pystatis.http_helper.requests", return_value=_generic_request_status() ) mocker.patch( - "pystatis.http_helper.load_config", - return_value={ - "GENESIS API": { - "base_url": "mocked_url", - "username": "JaneDoe", - "password": "password", - } - }, + "pystatis.db.get_db_settings", return_value=("host", "user", "pw") ) get_data_from_endpoint(endpoint="endpoint", method="method", params={}) diff --git a/tests/test_profile.py b/tests/test_profile.py index a3f6571..eed3940 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -1,66 +1,35 @@ import re -from configparser import ConfigParser -from pathlib import Path +from configparser import RawConfigParser import pytest +from pystatis import config, db from pystatis.profile import change_password, remove_result from tests.test_http_helper import _generic_request_status @pytest.fixture() -def cache_dir(tmp_path_factory): - # remove white-space and non-latin characters (issue fo some user names) - temp_dir = str(tmp_path_factory.mktemp(".pystatis")) - temp_dir = re.sub(r"[^\x00-\x7f]", r"", temp_dir.replace(" ", "")) +def config_() -> RawConfigParser: + old_config = config.load_config() + config.delete_config() + yield config.config + config.config = old_config + config.write_config() - return Path(temp_dir) - -def test_change_password(mocker, cache_dir): +def test_change_password(mocker, config_): # mock configparser to be able to test writing of new password - config = ConfigParser() - config["GENESIS API"] = { - "base_url": "mocked_url", - "username": "JaneDoe", - "password": "password", - } - mocker.patch("pystatis.profile.load_config", return_value=config) + assert config_.get("genesis", "password") == "" + mocker.patch( "pystatis.profile.load_data", - return_value=str(_generic_request_status().text), - ) - mocker.patch( - "pystatis.profile.get_config_path_from_settings", - return_value=cache_dir / "config.ini", + return_value=_generic_request_status(), ) + db.set_db("genesis") response = change_password("new_password") - assert response == str(_generic_request_status().text) - - -def test_change_password_keyerror(mocker, cache_dir): - # define empty config (no password) - mocker.patch( - "pystatis.profile.load_config", return_value={"GENESIS API": {}} - ) - mocker.patch( - "pystatis.profile.load_data", - return_value=str(_generic_request_status().text), - ) - mocker.patch( - "pystatis.profile.get_config_path_from_settings", - return_value=cache_dir / "config.ini", - ) - - with pytest.raises(KeyError) as e: - change_password("new_password") - assert ( - "Password not found in config! Please make sure \ - init_config() was run properly & your user data is set correctly!" - in str(e.value) - ) + assert config_.get("genesis", "password") == "new_password" def test_remove_result(mocker): diff --git a/tests/test_version.py b/tests/test_version.py index 1735424..2bb5737 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,5 +1,12 @@ +import subprocess + from pystatis import __version__ def test_version(): - assert __version__ == "0.1.4" + assert ( + __version__ + == subprocess.check_output(["poetry", "version"], text=True) + .strip() + .split()[-1] + )