Source code for nutstools.main

# -*- coding: utf-8 -*-
"""
A tool to convert postal codes to NUTS-values based on the NUTS files distributed by Eurostat.

Usage:
------

**Examples**:

Convert a postal code to NUTS:

.. code-block:: text

    > postalcode2nuts.exe --postal_code 2612AB

Output of this command is:

.. code-block:: text

    2612AB    NL333

Convert the file *postal_codes_NL.txt* with postal codes to NUTS:

.. code-block:: text

    >> postalcode2nuts.exe --input_file_name postal_codes_NL.txt --output_file_name -

gives  the following output:

.. code-block:: text

    8277AM    NL211
    2871KA    NL33B
    9408BJ    NL131
    3076KA    NL33C
    3068LM    NL33C
    7543GV    NL213

In case the *--output_file_name* argument is not given, the codes are written to file with the same name
as the input file with a suffix nuts3 (default), where 3 stands for the level of the NUTS output.
The level can be altered using the *--level*  option.

**Help**:

.. code-block:: text

    PS>postalcode2nuts.exe --help

    usage: postalcode2nuts [-h] [--version] [-p POSTALCODE] [-i INPUT_FILE_NAME]
                           [--nuts_file_name NUTS_INPUT_FILE_NAME] [-o OUTPUT_FILE_NAME] [-v] [-vv] [-l LEVEL]
                           [--year {2021}]
                           [--country {PT,DK,SE,PL,TR,MK,NO,SI,LV,ES,CH,NL,SK,CZ,LI,EL,HR,IS,LT,UK,IT,FI,HU,CY,EE,RS,IE,
                           RO,LU,BE,DE,FR,AT,BG}]
                           [--update_settings] [--force_download] [--directory DIRECTORY]

    Converts a postal code to its NUTS code

    options:
      -h, --help            show this help message and exit
      --version             show program's version number and exit
      -p POSTALCODE, --postal_code POSTALCODE
                            Postcode
      -i INPUT_FILE_NAME, --input_file_name INPUT_FILE_NAME
                            Input file with Postal codes
      --nuts_file_name NUTS_INPUT_FILE_NAME
                            Overrule input filename with the NUTS translation data
      -o OUTPUT_FILE_NAME, --output_file_name OUTPUT_FILE_NAME
                            Output file with Postal codes and NUTS
      -v, --verbose         set loglevel to INFO
      -vv, --debug          set loglevel to DEBUG
      -l LEVEL, --level LEVEL
                            The level at we want to get the NUTS-code
      --year {2021}         The year of the NUTS files
      --country {PT,DK,SE,PL,TR,MK,NO,SI,LV,ES,CH,NL,SK,CZ,LI,EL,HR,IS,LT,UK,IT,FI,HU,CY,EE,RS,IE,RO,LU,BE,DE,FR,AT,BG}
                            The country code for the NUTS file
      --update_settings     Update the settings file with the new values
      --force_download      Forces to download the datafile again, even if it already exists
      --directory DIRECTORY
                        The location of the the NUTS files. If not given, the default directory will be picked
      --config_show         Show the location of the configuration files and exit
"""

import argparse
import logging
import sys
from pathlib import Path

import pandas as pd

from nutstools import __version__
from nutstools import postalnuts
from nutstools.nutsdata import COUNTRY_CODES, DEFAULT_YEAR, NUTS_YEARS, DEFAULT_COUNTRY

__author__ = "EVLT"
__copyright__ = "EVLT"
__license__ = "MIT"

_logger = logging.getLogger(__name__)


[docs] def check_if_valid_nuts_level(value): """check if the argument is a valid nuts level. Must be between 0 and 3""" try: value = int(value) except ValueError: raise argparse.ArgumentTypeError( f"Nuts level should be an integer between the range 0 - 3. Now given level {value}" ) try: assert 0 <= value <= 3 except AssertionError: raise argparse.ArgumentTypeError( f"Nuts level should be in the range 0 - 3. Now given level {value}" ) return value
[docs] def parse_args(args): """Parse command line parameters Args: args (List[str]): command line parameters as list of strings (for example ``["--help"]``). Returns: :obj:`argparse.Namespace`: command line parameters namespace """ parser = argparse.ArgumentParser( description="Converts a postal code to its NUTS code" ) parser.add_argument( "--version", action="version", version="NutsTools {ver}".format(ver=__version__), ) parser.add_argument( "-p", "--postal_code", help="Postcode", type=str, metavar="POSTALCODE", action="append", ) parser.add_argument( "-i", "--input_file_name", help="Input file with Postal codes", type=str, metavar="INPUT_FILE_NAME", ) parser.add_argument( "--nuts_file_name", help="Overrule input filename with the NUTS translation data", type=str, metavar="NUTS_INPUT_FILE_NAME", ) parser.add_argument( "-o", "--output_file_name", help="Output file with Postal codes and NUTS", type=str, metavar="OUTPUT_FILE_NAME", ) parser.add_argument( "-v", "--verbose", dest="loglevel", help="set loglevel to INFO", action="store_const", const=logging.INFO, ) parser.add_argument( "-vv", "--debug", dest="loglevel", help="set loglevel to DEBUG", action="store_const", const=logging.DEBUG, ) parser.add_argument( "-l", "--level", dest="level", type=check_if_valid_nuts_level, help="The level at we want to get the NUTS-code", default=3, ) parser.add_argument( "--year", help="The year of the NUTS files", choices=NUTS_YEARS, ) parser.add_argument( "--country", help="The country code for the NUTS file ", choices=COUNTRY_CODES, ) parser.add_argument( "--update_settings", help="Update the settings file with the new values", action="store_true", ) parser.add_argument( "--force_download", help="Forces to download the datafile again, even if it already exists", action="store_true", ) parser.add_argument( "--directory", help="The location of the the NUTS files. If not given, the default directory will be picked ", ) parser.add_argument( "--config_show", help="Show the location of the configuration files and exit", action="store_true", ) return parser.parse_args(args)
[docs] def setup_logging(loglevel): """Setup basic logging Args: loglevel (int): minimum loglevel for emitting messages """ logformat = "[%(asctime)s] %(levelname)s:%(name)s:%(message)s" logging.basicConfig( level=loglevel, stream=sys.stdout, format=logformat, datefmt="%Y-%m-%d %H:%M:%S" )
[docs] def main(args): """Wrapper allowing :func:`postal_code2nuts` to be called with string arguments in a CLI fashion Instead of returning the value from :func:`postal_code2nuts`, it prints the result to the ``stdout`` in a nicely formatted message. Args: args (List[str]): command line parameters as list of strings (for example ``["--verbose", "42"]``). """ args = parse_args(args) setup_logging(args.loglevel) if not args.config_show: # in case config show is not requested: check if postal_code or input_file_name is given if args.postal_code is None and args.input_file_name is None: raise argparse.ArgumentError( argument=args.postal_code, message="Either --postal_code or --input_file_name option must be given", ) elif args.postal_code is not None and args.input_file_name is not None: # also check if not both argumetns are given raise argparse.ArgumentError( argument=args.postal_code, message="Only one of the options --postal_code or --input_file_name option can " "be given", ) nuts_dl = postalnuts.NutsData( year=args.year, country=args.country, nuts_file_name=args.nuts_file_name, nuts_code_directory=args.directory, update_settings=args.update_settings, force_download=args.force_download, ) if args.config_show: print(f"Settings file : {nuts_dl.settings_file_name}") print(f"Nuts code file: {nuts_dl.nuts_codes_file}") print(f"URL of source : {nuts_dl.url}") print(f"Country to show: {nuts_dl.country}") print(f"Year of data: {nuts_dl.year}") sys.exit(0) if args.input_file_name is not None: input_file_name = Path(args.input_file_name) postal_codes = pd.read_csv(input_file_name) output_file_name = "_".join( [input_file_name.with_suffix("").as_posix(), f"nuts{args.level}.csv"] ) else: postal_codes = pd.DataFrame(data=args.postal_code, columns=["CODES"]) output_file_name = None if args.output_file_name is not None: if args.output_file_name == "-": output_file_name = None else: output_file_name = Path(args.output_file_name) first_column_name = postal_codes.columns[0] postal_codes = ( postal_codes[first_column_name] .str.replace("'", "") .replace(r"\s", "", regex=True) ) nuts = postalnuts.NutsPostalCode(file_name=nuts_dl.nuts_codes_file) nuts_codes = nuts.postal2nuts(postal_codes=postal_codes, level=args.level) if output_file_name is not None: _logger.info(f"Writing nuts codes to {output_file_name}") nuts_codes.to_csv(output_file_name) else: print(nuts_codes.to_string(header=False)) _logger.info("Script ends here")
[docs] def run(): """Calls :func:`main` passing the CLI arguments extracted from :obj:`sys.argv` This function can be used as entry point to create console scripts with setuptools. """ main(sys.argv[1:])
if __name__ == "__main__": # ^ This is a guard statement that will prevent the following code from # being executed in the case someone imports this file instead of # executing it as a script. # https://docs.python.org/3/library/__main__.html # After installing your project with pip, users can also run your Python # modules as scripts via the ``-m`` flag, as defined in PEP 338:: # # python -m nutstools.skeleton 42 # run()