diff --git a/.gitignore b/.gitignore index b1f138d..e2422b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,15 @@ -.idea +__pycache__ +*/output/ +*/ignored/ +*.egg-info/ +.idea/ +build/ dev/ +dist/ env/ -__pycache__ -*.pyc *~$* +*.pyc *.DS_Store -dist/ -build/ -MANIFEST -tags *.swp -/*.html -*.egg-info -output/ +tags +MANIFEST diff --git a/CHANGES.txt b/CHANGES.txt index 6464d54..9391fea 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,7 @@ +v0.5.0, 24 January 2019 + * XlsForm now has a warnings attribute + * Updated requirements: requirements-unlock.txt list base, non-transitive depdendencies, while requirements.txt lists the results of pip freeze. + v0.4.0, 12 December 2018 * Added direct python API for borrow, e.g. from pmix.borrow import borrow. diff --git a/makefile b/makefile index a9fba79..42fc2b3 100644 --- a/makefile +++ b/makefile @@ -75,6 +75,9 @@ clean: rm -rf ./*.egg-info build: clean python3 setup.py sdist bdist_wheel +install: + pip install -r requirements-unlocked.txt --no-cache-dir; \ + pip freeze > requirements.txt pypi: build twine upload --repository-url https://upload.pypi.org/legacy/ dist/*; pypi_test: build diff --git a/pmix/__init__.py b/pmix/__init__.py index 35c3c92..24de8ce 100644 --- a/pmix/__init__.py +++ b/pmix/__init__.py @@ -1 +1,2 @@ """A mixed bag of utilities for PMA2020.""" + diff --git a/pmix/__version__.py b/pmix/__version__.py index 058fbca..4c97a37 100644 --- a/pmix/__version__.py +++ b/pmix/__version__.py @@ -1,3 +1,3 @@ """Semantic versioning for the package pmix.""" -VERSION = (0, 4, 0) +VERSION = (0, 5, 0) __version__ = '.'.join(map(str, VERSION)) diff --git a/pmix/cell.py b/pmix/cell.py index 14f7c67..25ce941 100644 --- a/pmix/cell.py +++ b/pmix/cell.py @@ -44,6 +44,10 @@ class Cell: def __init__(self, value=None): """Initialize cell to have value as Python object. + Attributes: + value: A python object that is stored in the cell. Should be + castable as str. + Args: value: The value of the cell. Defaults to None for a blank cell. """ diff --git a/pmix/workbook.py b/pmix/workbook.py index d63fd75..c5edc42 100644 --- a/pmix/workbook.py +++ b/pmix/workbook.py @@ -1,5 +1,4 @@ """Module defines a Workbook class to represent Excel files.""" - import itertools import copy import os.path diff --git a/pmix/xlsform.py b/pmix/xlsform.py index c24eb4f..0de0a91 100644 --- a/pmix/xlsform.py +++ b/pmix/xlsform.py @@ -1,4 +1,6 @@ """Module defining Xlsform class to work with ODK XLSForms.""" +from copy import deepcopy +from xlsxwriter.utility import xl_col_to_name from typing import List, Optional from pmix.xlstab import Xlstab @@ -26,6 +28,8 @@ def __init__(self, path: str, stripstr: bool = True): self.data = [Xlstab.from_worksheet(ws) for ws in self] self.settings = {} self.init_settings() + self.warnings = {} + self.init_warnings() def init_settings(self): """Get settings from Xlsform. @@ -86,6 +90,44 @@ def form_language(self) -> Optional[str]: pass return language + def init_warnings(self): + """Validate data and return warnings. + + Side effects: + self.warnings (dict): sets warnings + + Examples: + self.warnings = + '#VALUE!': { + 'survey': [X10, C56, E122] + }, + '#REF!': { + 'survey': [T5, C53] + }, + } + """ + warnings_schema = { # taken from xlrd.error_text_from_code + '#NULL!': {}, # Intersection of two cell ranges is empty + '#DIV/0': {}, # Division by zero + '#VALUE!': {}, # Wrong type of operand + '#REF!': {}, # Illegal or deleted cell reference + '#NAME?': {}, # Wrong function or range name + '#NUM!': {}, # Value range overflow + '#N/A': {}, # Argument or function not available + } + warnings = deepcopy(warnings_schema) + error_codes = [k for k, v in warnings.items()] + for ws in self.data: + for i, row in enumerate(ws.data): + for j, cell in enumerate(row): + if cell.value in error_codes: + if ws.name not in warnings[cell.value]: + warnings[cell.value][ws.name] = [] + label = xl_col_to_name(j) + warnings[cell.value][ws.name].append(label + str(i)) + warnings = {k: v for k, v in warnings.items() if v != {}} + self.warnings = warnings + def add_language(self, language: str): """Add appropriate language columns to an Xlsform. diff --git a/requirements-dev.txt b/requirements-unlocked.txt similarity index 100% rename from requirements-dev.txt rename to requirements-unlocked.txt diff --git a/setup.py b/setup.py index 4ac906b..14a7900 100644 --- a/setup.py +++ b/setup.py @@ -65,12 +65,15 @@ def status(s): print('\033[1m{0}\033[0m'.format(s)) def initialize_options(self): + """Initialize options""" pass def finalize_options(self): + """Finalize options""" pass def run(self): + """Run""" try: self.status('Removing previous builds…') rmtree(os.path.join(here, 'dist')) @@ -78,8 +81,8 @@ def run(self): pass self.status('Building Source and Wheel (universal) distribution…') - os.system( - '{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) + os.system('{0} setup.py sdist bdist_wheel --universal' + .format(sys.executable)) self.status('Uploading the package to PyPI via Twine…') os.system('twine upload dist/*')