diff --git a/LICENSE b/LICENSE index 112e17a..f1029de 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 lwanger +Copyright (c) 2017 len wanger Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LICENSE.txt b/LICENSE.txt index d636c8e..dc6d593 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,5 +1,5 @@ The MIT License (MIT) -Copyright © 2017 Len Wanger and Impossible Objects +Copyright © 2017 Len Wanger Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal diff --git a/MANIFEST.in b/MANIFEST.in index 2c6efd2..fedebf9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,5 @@ include README.rst +include LICENSE.txt +include LICENSE +recursive-include docs *.rst conf.py Makefile +recursive-include cooked_input/examples *.py diff --git a/README.rst b/README.rst index 0c36d66..9c36979 100644 --- a/README.rst +++ b/README.rst @@ -8,11 +8,6 @@ cooked input. github archive: https://github.com/lwanger/cooked_input - for the latest documentation, see: https://readthedocs.org/projects/cooked-input/ ----- - -This is the readme for the project. - see TODO.md for list of TODO items diff --git a/TODO.md b/TODO.md index dc26e92..0b98784 100644 --- a/TODO.md +++ b/TODO.md @@ -4,6 +4,7 @@ **TODO:** * general: + * Ship example and test dirs. * Need to work on error messages. Not handled well now. * Add tables (build-a-burger) to tutorial * Review examples, tutorial interface. Clean up and make: easier, cleaner, more consistent. @@ -47,4 +48,8 @@ * add dollar convertor that has minimum of 0.00 and strips off $ sign and commas. Returns float * validators: - * add: date range, date day of week \ No newline at end of file + * add: date range, date day of week + * allow forcing to validate all validators instead of stopping on first failure + * return list of all validation failures + * provide list of hints for what is required + * password validator should create hint of what's required for password \ No newline at end of file diff --git a/config.py b/config.py deleted file mode 100644 index 83b9957..0000000 --- a/config.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -config.py - -Configuration file for flask app - -author: Len Wanger -Copyright Impossible Objects, 2017 - -TODO: -""" - - -# Statement for enabling the development environment -DEBUG = True - -# Define the application directory -import os -BASE_DIR = os.path.abspath(os.path.dirname(__file__)) - -# Define the database - we are working with -SQLALCHEMY_DATABASE_URI = 'sqlite:///' - -SQLALCHEMY_BINDS = { - 'users': 'sqlite:///' + os.path.join(BASE_DIR, 'users.db'), - 'builds': 'sqlite:///' + os.path.join(BASE_DIR, 'builds.db'), - 'part_nums': 'sqlite:///' + os.path.join(BASE_DIR, 'part_nums.db'), - 'inks': 'sqlite:///' + os.path.join(BASE_DIR, 'ink.db') -} - -DATABASE_CONNECT_OPTIONS = {} - -# Application threads. A common general assumption is -# using 2 per available processor cores - to handle -# incoming requests using one and performing background -# operations using the other. -THREADS_PER_PAGE = 2 - -# This needs to be turned back on if models_committed or before_models_committed is used, otherwise keep it False. -SQLALCHEMY_TRACK_MODIFICATIONS = False - -# Enable protection agains *Cross-site Request Forgery (CSRF)* -CSRF_ENABLED = True - -# Use a secure, unique and absolutely secret key for -# signing the data. -# CSRF_SESSION_KEY = "secret" -CSRF_SESSION_KEY = "Impossible Silence" - -# Secret key for signing cookies -# SECRET_KEY = "secret" -SECRET_KEY = "Loose Lips Sync Shyps!" diff --git a/cooked_input/__init__.py b/cooked_input/__init__.py index 30ce4f5..7aa2572 100644 --- a/cooked_input/__init__.py +++ b/cooked_input/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -__version__ = '0.1.4' +__version__ = '0.1.6' from .get_input import get_input, get_table_input, process_value, make_pretty_table from .get_input import TABLE_ID, TABLE_VALUE, TABLE_ID_OR_VALUE @@ -9,7 +9,7 @@ from .convertors import ListConvertor, DateConvertor, YesNoConvertor, TableConvertor from .validators import Validator, ExactLengthValidator, InLengthValidator, ExactValueValidator, InRangeValidator from .validators import NotInValidator, InChoicesValidator, RegexValidator, PasswordValidator, ListValidator -from .validators import validate, in_all, in_any, not_in +from .validators import in_all, in_any, not_in, validate from .cleaners import Cleaner, UpperCleaner, LowerCleaner, CapitalizeCleaner from .cleaners import StripCleaner, ReplaceCleaner diff --git a/cooked_input/cleaners.py b/cooked_input/cleaners.py index a4bb59a..89128fa 100644 --- a/cooked_input/cleaners.py +++ b/cooked_input/cleaners.py @@ -1,13 +1,10 @@ """ -get_input module to get values from the command line. - -This file contains cleaner classes for io_get_input +This file contains cleaner classes for cooked_input Author: Len Wanger Copyright: Len Wanger, 2017 """ -import sys from string import capwords @@ -73,9 +70,9 @@ def __init__(self, all_words=False, **kwargs): def __call__(self, value): if self.all_words: - result = value.capitalize() - else: result = capwords(value) + else: + result = value.capitalize() return result diff --git a/cooked_input/convertors.py b/cooked_input/convertors.py index 8f9bf77..f1bffd3 100644 --- a/cooked_input/convertors.py +++ b/cooked_input/convertors.py @@ -1,17 +1,10 @@ """ -get_input module to get values from the command line. - -convertor classes - -This file contains convertors classes for io_get_input - +This file contains convertors classes for cooked_input Author: Len Wanger Copyright: Len Wanger, 2017 """ -import sys -import re import dateparser import csv from io import StringIO diff --git a/cooked_input/examples/get_database.py b/cooked_input/examples/get_database.py index 0782412..f24a7d3 100644 --- a/cooked_input/examples/get_database.py +++ b/cooked_input/examples/get_database.py @@ -1,3 +1,9 @@ +""" +cooked input example showing how to use with entries from database tables. + +Len Wanger, 2017 +""" + import sqlite3 from collections import Counter import cooked_input diff --git a/cooked_input/examples/get_ints.py b/cooked_input/examples/get_ints.py index 1f8d7d8..33ed745 100644 --- a/cooked_input/examples/get_ints.py +++ b/cooked_input/examples/get_ints.py @@ -1,5 +1,7 @@ """ -Test to test getting an integer values +cooked input examples of getting an integer values + +Len Wanger, 2017 """ from cooked_input import get_input diff --git a/cooked_input/examples/get_lists.py b/cooked_input/examples/get_lists.py index 173f5eb..fe170a3 100644 --- a/cooked_input/examples/get_lists.py +++ b/cooked_input/examples/get_lists.py @@ -1,6 +1,7 @@ """ -Test to test getting lists +cooked input examples of getting an list values +Len Wanger, 2017 """ from cooked_input import get_input @@ -20,10 +21,10 @@ upper_cleaner = UpperCleaner() # get any string - print(get_input(prompt='Enter a list', convertor=ListConvertor())) - print(get_input(prompt='Enter a list (blank OK)', convertor=ListConvertor(), blank_ok=True)) - print(get_input(prompt='Enter a list (blank not OK)', convertor=ListConvertor(), blank_ok=False)) - print(get_input(prompt='Enter a list (stripped)', convertor=ListConvertor(), cleaners=strip_cleaner)) + print(get_input(prompt='Enter a list (separated by commas)', convertor=ListConvertor())) + print(get_input(prompt='Enter a list (separated by commas, blank OK)', convertor=ListConvertor(), blank_ok=True)) + print(get_input(prompt='Enter a list (separated by commas, blank not OK)', convertor=ListConvertor(), blank_ok=False)) + print(get_input(prompt='Enter a list (separated by commas, stripped)', convertor=ListConvertor(), cleaners=strip_cleaner)) # specified delimiter print(get_input(prompt='Enter a list (separated by commas)', convertor=ListConvertor(delimiter=','))) diff --git a/cooked_input/examples/get_menu.py b/cooked_input/examples/get_menu.py index 347e0d5..7dd52e4 100644 --- a/cooked_input/examples/get_menu.py +++ b/cooked_input/examples/get_menu.py @@ -1,3 +1,9 @@ +""" +cooked input examples of getting entry from menus + +Len Wanger, 2017 +""" + from cooked_input import get_table_input from cooked_input.convertors import IntConvertor diff --git a/cooked_input/examples/get_passwords.py b/cooked_input/examples/get_passwords.py index 966f064..2f3323b 100644 --- a/cooked_input/examples/get_passwords.py +++ b/cooked_input/examples/get_passwords.py @@ -1,3 +1,8 @@ +""" +cooked input examples of getting password values + +Len Wanger, 2017 +""" from cooked_input import get_input from cooked_input.validators import PasswordValidator diff --git a/cooked_input/examples/get_strs.py b/cooked_input/examples/get_strs.py index 71957b9..48f8592 100644 --- a/cooked_input/examples/get_strs.py +++ b/cooked_input/examples/get_strs.py @@ -1,8 +1,7 @@ """ -Test to test getting a string values +cooked input examples of getting an string values - -- show cleaners: strip, upper, lower +Len Wanger, 2017 """ from cooked_input import get_input @@ -25,43 +24,38 @@ not_in_choices_validator = NotInValidator(validators=[bad_flavor_validator]) strip_cleaner = StripCleaner() + rstrip_cleaner = StripCleaner(lstrip=False, rstrip=True) lower_cleaner = LowerCleaner() upper_cleaner = UpperCleaner() capitalize_cleaner = CapitalizeCleaner(all_words=False) capitalize_all_cleaner = CapitalizeCleaner(all_words=True) + strip_and_lower_cleaners = [strip_cleaner, lower_cleaner] + # get any string print(get_input(prompt='Enter any string')) print(get_input(prompt='Enter any string', blank_ok=True)) print(get_input(prompt='Enter any string (will be stripped of leading and trailing spaces and converted to lower)', - cleaners=[strip_cleaner, lower_cleaner])) + cleaners=strip_and_lower_cleaners)) print(get_input(prompt='Enter any string (will be stripped of trailing spaces and converted to upper)', - cleaners=[strip_cleaner, upper_cleaner])) + cleaners=[rstrip_cleaner, upper_cleaner])) - print(get_input(prompt='Enter your name (first word will be capitalized)', - cleaners=capitalize_cleaner)) - print(get_input(prompt='Enter your name (all words will be capitalized)', - cleaners=capitalize_cleaner)) + # capitalization cleaning (CapitalizeCleaner) + print(get_input(prompt='Enter your name (first word will be capitalized)', cleaners=capitalize_cleaner)) + print(get_input(prompt='Enter your name (all words will be capitalized)', cleaners=capitalize_all_cleaner)) - prompt_str = "What is your favorite flavor jelly bean (don't say licorice!)?" + # picking from choices (InchoicesValidator) + prompt_str = "What is your favorite flavor jelly bean (pick any flavor, don't say licorice!)?" print(get_input(prompt=prompt_str, validators=not_in_choices_validator, default='cherry')) - prompt_str = "Which of these is your favorite flavor jelly bean (%s, but not licorice!)?" % ', '.join(good_flavors) - cleaners = [strip_cleaner, lower_cleaner] + prompt_str = "Which of these is your favorite flavor jelly bean (choose from: %s, but not licorice!)?" % ', '.join(good_flavors) validators = [good_flavor_validator, not_in_choices_validator] - print(get_input(prompt=prompt_str, cleaners=cleaners, validators=validators, default='cherry')) + print(get_input(prompt=prompt_str, cleaners=strip_and_lower_cleaners, validators=validators, default='cherry')) # get different length strings print(get_input(prompt='Enter a three letter string', validators=[length_3_validator])) print(get_input(prompt='Enter a string at least 5 letters long', validators=[length_5_plus_validator])) print(get_input(prompt='Enter a 2 to 4 letter string', validators=[length_2_to_4_validator])) - # Use InChoicesValidator - prompt_str = 'What is your favorite color (%s)' % ', '.join(colors) - print(get_input(prompt=prompt_str, default='green')) - - print(get_input(prompt=prompt_str, cleaners=cleaners, validators=validators, default='cherry')) - + # Use YesNoConvertor print(get_input(prompt="Yes or no?", cleaners=strip_cleaner, convertor=YesNoConvertor(), default='Y')) - - diff --git a/cooked_input/examples/get_table_input.py b/cooked_input/examples/get_table_input.py index 54bd51e..21f0d3c 100644 --- a/cooked_input/examples/get_table_input.py +++ b/cooked_input/examples/get_table_input.py @@ -1,3 +1,8 @@ +""" +cooked input examples of getting inputs from tables + +Len Wanger, 2017 +""" import cooked_input from cooked_input import get_table_input diff --git a/cooked_input/examples/get_user_info.py b/cooked_input/examples/get_user_info.py index 89af5b5..54b95b6 100644 --- a/cooked_input/examples/get_user_info.py +++ b/cooked_input/examples/get_user_info.py @@ -1,8 +1,7 @@ """ -No error msgs for password +cooked input examples of getting inputs from tables -Have Password validator create hints for proper input. Expand that idea? -Have error messages for all fields +Len Wanger, 2017 """ @@ -99,6 +98,9 @@ def __call__(self, value): role_validtor = ListValidator(elem_validators=InChoicesValidator(roles_list)) role_prompt = 'Roles ({}, separated by commas)'.format(sorted(roles_list)) + # TODO -- remove me!!! + roles = get_input(prompt=role_prompt, cleaners=default_cleaners, convertor=ListConvertor(), validators=role_validtor, blank_ok=False) + # Simulate logging the user in: try: user_name = get_input(prompt='Username', cleaners=default_cleaners, validators=CheckUserValidator(), retries=3) diff --git a/cooked_input/examples/simple_input.py b/cooked_input/examples/simple_input.py index f42a7a0..78eb770 100644 --- a/cooked_input/examples/simple_input.py +++ b/cooked_input/examples/simple_input.py @@ -5,6 +5,8 @@ simple_guess: the naive version. Not robust. robust_guess: trying to beef up the naive version, lots of code for a simple input of an integer! cooked_input_guess: implemented with cooked_input + +Len Wanger, 2017 """ import sys diff --git a/cooked_input/examples/validate_tk.py b/cooked_input/examples/validate_tk.py index a03ae85..cabeb53 100644 --- a/cooked_input/examples/validate_tk.py +++ b/cooked_input/examples/validate_tk.py @@ -14,9 +14,9 @@ def on_button(): value = entry1.get() - processed_value = process_value(value, cleaners=StripCleaner(), convertor=IntConvertor(), validators=InRangeValidator(min_val=1, max_val=10)) + valid, processed_value = process_value(value, cleaners=StripCleaner(), convertor=IntConvertor(), validators=InRangeValidator(min_val=1, max_val=10)) - if processed_value: + if valid: messagebox.showinfo("Integer is...", "Integer is good: {}".format(processed_value)) top.quit() else: diff --git a/cooked_input/get_input.py b/cooked_input/get_input.py index 9c5590a..7bddbe0 100644 --- a/cooked_input/get_input.py +++ b/cooked_input/get_input.py @@ -2,9 +2,7 @@ """ get_input module to get values from the command line. -document usage. -point to examples -talk about convertors, cleaners, and validators +see: https://github.com/lwanger/cooked_input for more information. Author: Len Wanger Copyright: Len Wanger, 2017 @@ -16,11 +14,8 @@ import getpass import prettytable -# relative import needed to run, but sphinx need non-relative. -from .validators import InChoicesValidator, in_all +from .validators import InChoicesValidator, in_all, validate from .convertors import TableConvertor -# from validators import InChoicesValidator -# from convertors import TableConvertor TABLE_ID = 0 @@ -304,15 +299,4 @@ def get_table_input(table=None, cleaners=None, convertor=None, validators=None, if return_value == TABLE_VALUE: return t[TABLE_VALUE] else: - return t[TABLE_ID] - - -def validate(value, validators=None): - """ - Run validators on a value. - - :param value: the value to validate. - :param validators: list of validators to run on the value. - :return: True if the input passed validation, else False - """ - return compose(value, validators) + return t[TABLE_ID] \ No newline at end of file diff --git a/cooked_input/test/get_int.py b/cooked_input/test/get_int.py index 5d8ced5..51218ac 100644 --- a/cooked_input/test/get_int.py +++ b/cooked_input/test/get_int.py @@ -12,6 +12,7 @@ class redirect_stdin(): + # context manager for redirecting stdin. Usable with "with" keyword def __init__(self, f): self.f = f @@ -21,6 +22,7 @@ def __enter__(self): def __exit__(self, *args): sys.stdin = sys.__stdin__ + test_get_int_str=""" 10 5 diff --git a/cooked_input/test/validate.py b/cooked_input/test/validate.py new file mode 100644 index 0000000..11691bf --- /dev/null +++ b/cooked_input/test/validate.py @@ -0,0 +1,14 @@ + +""" +pytest tests for cooked_input: test the validate method + +Len Wanger, 2017 +""" + +from cooked_input import validate, InRangeValidator, NotInValidator + +validators = [InRangeValidator(min_val=1, max_val=10), NotInValidator(5)] + +for v in [(-1, False), (1, True), (5, False), (6, True), (11, False)]: + result = validate(v[0], validators) + assert(result==v[1]) diff --git a/cooked_input/validators.py b/cooked_input/validators.py index 2b765a2..160e00f 100644 --- a/cooked_input/validators.py +++ b/cooked_input/validators.py @@ -1,7 +1,5 @@ """ -get_input module to get values from the command line. - -This file contains validator classes for io_get_input +This file contains validator classes for cooked_input For more validators: look at using validus: https://shopnilsazal.github.io/validus/readme.html @@ -10,23 +8,9 @@ Copyright: Len Wanger, 2017 """ -import sys import string import re - - -def compose(value, funcs): - # compose functions and return the result: compose(value, [f1,f2,f3]) = f3(f2(f1(value))) - result = None - if callable(funcs): - result = funcs(value) - else: - for func in funcs: - if not result: - result = func(value) - else: - result = func(result) - return result +import collections def in_any(value, validators): @@ -38,17 +22,17 @@ def in_any(value, validators): :return: True if any of the validators pass, False if they all fail. """ - if isinstance(validators, collections.Iterable): - result = any(validator(value) for validator in validators) - elif validators is None: + if validators is None: result = True - else: + elif isinstance(validators, collections.Iterable): # list of validators (or other iterable) + result = any(validator(value) for validator in validators) + elif callable(validators): # single validator function result = validators(value) + else: # single value + result = value == validators return result -import collections - def in_all(value, validators): """ @@ -59,12 +43,14 @@ def in_all(value, validators): :return: True if all of the validators pass, False if they all fail. """ - if isinstance(validators, collections.Iterable): - result = all(validator(value) for validator in validators) - elif validators is None: + if validators is None: result = True - else: + elif isinstance(validators, collections.Iterable): + result = all(validator(value) for validator in validators) + elif callable(validators): result = validators(value) + else: + result = value == validators return result @@ -77,8 +63,7 @@ def not_in(value, validators): :param validators: an iterable (list or tuple) containing the validators to use. :return: True if none of the validators pass, False if they any of them pass. """ - result = in_any(value, validators) - return not result + return not in_any(value, validators) def validate(value, validators=None): @@ -89,7 +74,15 @@ def validate(value, validators=None): :param validators: list of validators to run on the value. :return: True if the input passed validation, else False """ - return compose(value, validators) + if callable(validators): + result = validators(value) + else: + for v in validators: + result = v(value) + if not result: + break + + return result #### @@ -364,7 +357,7 @@ def __call__(self, value): if self._elem_validators: for item in value: - result = compose(item, self._elem_validators) + result = validate(item, self._elem_validators) if not result: return False diff --git a/setup.py b/setup.py index 4ea8589..5ce22f7 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,6 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - #version='0.1.0', version=cooked_input.__version__, description='Toolkit for getting input from the keyboard.', @@ -88,7 +87,8 @@ # your project is installed. For an analysis of "install_requires" vs pip's # requirements files see: # https://packaging.python.org/en/latest/requirements.html - install_requires=['dateparser', 'prettytable'], + # install_requires=['dateparser', 'prettytable', 'validus'], + install_requires=['setuptools-git'], # List additional groups of dependencies here (e.g. development # dependencies). You can install these using the following syntax, @@ -105,6 +105,8 @@ package_data={ # 'sample': ['package_data.dat'], }, + + include_package_data=True, # Although 'package_data' is the preferred approach, in some case you may # need to place data files outside of your packages. See: