diff --git a/.coveragerc b/.coveragerc index 0b21d16a7..0f8316766 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,5 @@ [run] omit = - taxcalc/_version.py taxcalc/calcfunctions.py taxcalc/*.json taxcalc/cli/* diff --git a/Makefile b/Makefile index b05fdb261..a5382ae3f 100644 --- a/Makefile +++ b/Makefile @@ -75,7 +75,7 @@ TESTS_JSON_FILES := $(shell ls -l ./taxcalc/tests/*json | awk '{print $$9}') PYLINT_FILES := $(shell grep -rl --include="*py" disable=locally-disabled .) PYLINT_OPTIONS = --disable=locally-disabled --score=no --jobs=4 RECIPE_FILES := $(shell ls -l ./docs/cookbook/recipe*py | awk '{print $$9}') -RECIPE_OPTIONS = --disable=C0103,C0111,W0401,W0614 --score=no --jobs=4 +RECIPE_OPTIONS = --disable=C0103,C0111,W0401,W0614,E0401 --score=no --jobs=4 .PHONY=cstest cstest: diff --git a/README.md b/README.md index 2cbf2489f..4102c2ff0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Python 3.6.5](https://img.shields.io/badge/python-3.6.5-blue.svg)](https://www.python.org/downloads/release/python-365/) +[![Python 3.6](https://img.shields.io/badge/python-3.6-blue.svg)](https://www.python.org/downloads/release/python-360/) [![Build Status](https://travis-ci.org/open-source-economics/Tax-Calculator.svg?branch=master)](https://travis-ci.org/open-source-economics/Tax-Calculator) [![Codecov](https://codecov.io/gh/open-source-economics/Tax-Calculator/branch/master/graph/badge.svg)](https://codecov.io/gh/open-source-economics/Tax-Calculator) diff --git a/conda.recipe/install_local_package.sh b/conda.recipe/install_local_package.sh index a65186bb6..76a086a7d 100755 --- a/conda.recipe/install_local_package.sh +++ b/conda.recipe/install_local_package.sh @@ -16,7 +16,7 @@ echo "BUILD-PREP..." # check version of conda package conda list conda | awk '$1=="conda"{v=$2;gsub(/\./,"",v);nv=v+0;if(nv<444)rc=1}END{exit(rc)}' -if [ $? -eq 1 ]; then +if [[ $? -eq 1 ]]; then echo "==> Installing conda 4.4.4+" conda install conda>=4.4.4 --yes 2>&1 > /dev/null echo "==> Continuing to build new taxcalc package" @@ -24,7 +24,7 @@ fi # install conda-build package if not present conda list build | awk '$1~/conda-build/{rc=1}END{exit(rc)}' -if [ $? -eq 0 ]; then +if [[ $? -eq 0 ]]; then echo "==> Installing conda-build package" conda install conda-build --yes 2>&1 > /dev/null echo "==> Continuing to build new taxcalc package" diff --git a/conda.recipe/remove_local_package.sh b/conda.recipe/remove_local_package.sh index 7f63d09f0..d6f03c360 100755 --- a/conda.recipe/remove_local_package.sh +++ b/conda.recipe/remove_local_package.sh @@ -5,8 +5,8 @@ # removing a local conda package is analogous to a "make clean" operation # uninstall any existing taxcalc conda package -conda list taxcalc | awk '$1~/taxcalc/{rc=1}END{exit(rc)}' -if [ $? -eq 1 ]; then +conda list taxcalc | awk '$1=="taxcalc"{rc=1}END{exit(rc)}' +if [[ $? -eq 1 ]]; then conda uninstall taxcalc --yes 2>&1 > /dev/null fi diff --git a/docs/cookbook.html b/docs/cookbook.html index 3920828fb..22704f6fd 100644 --- a/docs/cookbook.html +++ b/docs/cookbook.html @@ -94,12 +94,12 @@

Preliminaries: Recipe Techniques

and give it a new file name before you start to modify the recipe.

The Calculator object is the central object in Tax-Calculator and -it is created by passing four secondary objects (a Policy object, a -Records object, a Behavior object, and a Consumption object) to the +it is created by passing three secondary objects (a Policy object, a +Records object, and a Consumption object) to the Calculator class constructor. When modifying a recipe, following a few rules will minimize the chance of running into problems.

-

Fully specify Consumption, Behavior, Records, and Policy objects +

Fully specify Policy, Records, and Consumption objects before passing them to the Calculator class constructor.

After initializing a Calculator object, manipulate it using only @@ -236,16 +236,17 @@

Advanced Recipe: (2) Estimating Behavioral Response

This is an advanced recipe that should be followed only after mastering the basic recipe. This recipe -shows how to analyze the behavioral responses to a tax reform.

+shows how to analyze the behavioral responses to a tax reform using +the Behavioral-Responses behresp package. Before following +this recipe, be sure to install the behresp package on your +compter by executing this at the command prompt: +conda install -c OSPC behresp

Ingredients

Policy reform in the ingredients/reformA.json file.

-

Economic assumptions in -the ingredients/assumpA.json file.

-

Instructions

Step-by-step diff --git a/docs/cookbook/ingredients/assumpA.json b/docs/cookbook/ingredients/assumpA.json deleted file mode 100644 index d2d384f8b..000000000 --- a/docs/cookbook/ingredients/assumpA.json +++ /dev/null @@ -1,16 +0,0 @@ -// This JSON economic assumption file specifies a non-zero substitution -// elasticity for taxable income, which causes a response in taxable income -// to a reform-induced change in marginal tax rates -{ - "consumption": { - }, - "behavior": { - "_BE_sub": {"2013": [0.25]} - }, - "growdiff_baseline": { - }, - "growdiff_response": { - }, - "growmodel": { - } -} diff --git a/docs/cookbook/recipe02.py b/docs/cookbook/recipe02.py index 64b8e4f9c..09f0aa681 100644 --- a/docs/cookbook/recipe02.py +++ b/docs/cookbook/recipe02.py @@ -1,9 +1,10 @@ from taxcalc import * +import behresp # use publicly-available CPS input file recs = Records.cps_constructor() -# specify Calculator object representing current-law policy +# specify baseline Calculator object representing current-law policy pol = Policy() calc1 = Calculator(policy=pol, records=recs) @@ -14,37 +15,34 @@ calc1.calc_all() itax_rev1 = calc1.weighted_total('iitax') -# read JSON reform file and use (the default) static analysis assumptions +# read JSON reform file reform_filename = './ingredients/reformA.json' params = Calculator.read_json_param_objects(reform=reform_filename, assump=None) # specify Calculator object for static analysis of reform policy pol.implement_reform(params['policy']) -calc2 = Calculator(policy=pol, records=recs) +calc2sa = Calculator(policy=pol, records=recs) # calculate reform income tax liabilities for cyr under static assumptions -calc2.advance_to_year(cyr) -calc2.calc_all() -itax_rev2 = calc2.weighted_total('iitax') +calc2sa.advance_to_year(cyr) +calc2sa.calc_all() +itax_rev2sa = calc2sa.weighted_total('iitax') -# read JSON reform file and (dynamic) behavioral-analysis assumptions -assump_filename = './ingredients/assumpA.json' -params = Calculator.read_json_param_objects(reform=reform_filename, - assump=assump_filename) -behv = Behavior() -behv.update_behavior(params['behavior']) +# specify behavioral-response assumptions +behresp_json = '{"BE_sub": {"2018": 0.25}}' +behresp_dict = Calculator.read_json_assumptions(behresp_json) -# specify Calculator object for behavioral-response analysis of reform policy -calc3 = Calculator(policy=pol, records=recs, behavior=behv) +# specify Calculator object for analysis of reform with behavioral response +calc2br = Calculator(policy=pol, records=recs) +calc2br.advance_to_year(cyr) +_, df2br = behresp.response(calc1, calc2br, behresp_dict) -# calculate reform income tax liabilities for cyr under dynamic assumptions -calc3.advance_to_year(cyr) -calc3br = Behavior.response(calc1, calc3) -itax_rev3 = calc3br.weighted_total('iitax') +# calculate reform income tax liabilities for cyr with behavioral response +itax_rev2br = (df2br['iitax'] * df2br['s006']).sum() -# print total revenue estimates for cyr +# print total income tax revenue estimates for cyr # (estimates in billons of dollars rounded to nearest hundredth of a billion) print('{}_CURRENT_LAW_P__itax_rev($B)= {:.2f}'.format(cyr, itax_rev1 * 1e-9)) -print('{}_REFORM_STATIC__itax_rev($B)= {:.2f}'.format(cyr, itax_rev2 * 1e-9)) -print('{}_REFORM_DYNAMIC_itax_rev($B)= {:.2f}'.format(cyr, itax_rev3 * 1e-9)) +print('{}_REFORM_STATIC__itax_rev($B)= {:.2f}'.format(cyr, itax_rev2sa * 1e-9)) +print('{}_REFORM_DYNAMIC_itax_rev($B)= {:.2f}'.format(cyr, itax_rev2br * 1e-9)) diff --git a/docs/cookbook/test_recipes.py b/docs/cookbook/test_recipes.py index be35e2c9b..8463ed3ee 100644 --- a/docs/cookbook/test_recipes.py +++ b/docs/cookbook/test_recipes.py @@ -20,7 +20,7 @@ RECIPES = glob.glob('./recipe[0-9][0-9].py') # execute each recipe in RECIPES list and compare output with expected output -for recipe in RECIPES: +for recipe in sorted(RECIPES): out_filename = recipe.replace('.py', '.out') if os.path.isfile(out_filename): os.remove(out_filename) diff --git a/docs/index_results.sh b/docs/index_results.sh index 3a87cadc8..057fa589b 100755 --- a/docs/index_results.sh +++ b/docs/index_results.sh @@ -10,7 +10,7 @@ echo "STARTING : `date`" # check existence of taxcalc package conda list taxcalc | awk '$1~/taxcalc/{rc=1}END{exit(rc)}' -if [ $? -eq 0 ]; then +if [[ $? -eq 0 ]]; then echo "ERROR: taxcalc package does not exist" exit 1 fi diff --git a/docs/recipe00.graph.html b/docs/recipe00.graph.html index 4d745def8..6ed9a64a9 100644 --- a/docs/recipe00.graph.html +++ b/docs/recipe00.graph.html @@ -1,25 +1,48 @@ + + + - - - recipe00.graph + + + + + recipe00.graph + + + + + - - - - - + + + + + + + + + + + + + -

-
-
+ + + +
+ + - - + + + \ No newline at end of file diff --git a/docs/recipe02.py.html b/docs/recipe02.py.html index ad949ab5e..f02b125ae 100644 --- a/docs/recipe02.py.html +++ b/docs/recipe02.py.html @@ -1,10 +1,11 @@ recipe02.py
 from taxcalc import *
+import behresp
 
 # use publicly-available CPS input file
 recs = Records.cps_constructor()
 
-# specify Calculator object representing current-law policy
+# specify baseline Calculator object representing current-law policy
 pol = Policy()
 calc1 = Calculator(policy=pol, records=recs)
 
@@ -15,38 +16,35 @@
 calc1.calc_all()
 itax_rev1 = calc1.weighted_total('iitax')
 
-# read JSON reform file and use (the default) static analysis assumptions
+# read JSON reform file
 reform_filename = './ingredients/reformA.json'
 params = Calculator.read_json_param_objects(reform=reform_filename,
                                             assump=None)
 
 # specify Calculator object for static analysis of reform policy
 pol.implement_reform(params['policy'])
-calc2 = Calculator(policy=pol, records=recs)
+calc2sa = Calculator(policy=pol, records=recs)
 
 # calculate reform income tax liabilities for cyr under static assumptions
-calc2.advance_to_year(cyr)
-calc2.calc_all()
-itax_rev2 = calc2.weighted_total('iitax')
+calc2sa.advance_to_year(cyr)
+calc2sa.calc_all()
+itax_rev2sa = calc2sa.weighted_total('iitax')
 
-# read JSON reform file and (dynamic) behavioral-analysis assumptions
-assump_filename = './ingredients/assumpA.json'
-params = Calculator.read_json_param_objects(reform=reform_filename,
-                                            assump=assump_filename)
-behv = Behavior()
-behv.update_behavior(params['behavior'])
+# specify behavioral-response assumptions
+behresp_json = '{"BE_sub": {"2018": 0.25}}'
+behresp_dict = Calculator.read_json_assumptions(behresp_json)
 
-# specify Calculator object for behavioral-response analysis of reform policy
-calc3 = Calculator(policy=pol, records=recs, behavior=behv)
+# specify Calculator object for analysis of reform with behavioral response
+calc2br = Calculator(policy=pol, records=recs)
+calc2br.advance_to_year(cyr)
+_, df2br = behresp.response(calc1, calc2br, behresp_dict)
 
-# calculate reform income tax liabilities for cyr under dynamic assumptions
-calc3.advance_to_year(cyr)
-calc3br = Behavior.response(calc1, calc3)
-itax_rev3 = calc3br.weighted_total('iitax')
+# calculate reform income tax liabilities for cyr with behavioral response
+itax_rev2br = (df2br['iitax'] * df2br['s006']).sum()
 
-# print total revenue estimates for cyr
+# print total income tax revenue estimates for cyr
 # (estimates in billons of dollars rounded to nearest hundredth of a billion)
 print('{}_CURRENT_LAW_P__itax_rev($B)= {:.2f}'.format(cyr, itax_rev1 * 1e-9))
-print('{}_REFORM_STATIC__itax_rev($B)= {:.2f}'.format(cyr, itax_rev2 * 1e-9))
-print('{}_REFORM_DYNAMIC_itax_rev($B)= {:.2f}'.format(cyr, itax_rev3 * 1e-9))
+print('{}_REFORM_STATIC__itax_rev($B)= {:.2f}'.format(cyr, itax_rev2sa * 1e-9))
+print('{}_REFORM_DYNAMIC_itax_rev($B)= {:.2f}'.format(cyr, itax_rev2br * 1e-9))
 
diff --git a/read-the-docs/source/public_api.rst b/read-the-docs/source/public_api.rst index 972690213..e6a70a90b 100644 --- a/read-the-docs/source/public_api.rst +++ b/read-the-docs/source/public_api.rst @@ -70,16 +70,10 @@ taxcalc.Growfactors .. autoclass:: taxcalc.Growfactors :members: -taxcalc.macro_elasticity ------------------------- - -.. automodule:: taxcalc.macro_elasticity - :members: - -taxcalc.ParametersBase ----------------------- +taxcalc.Parameters +------------------ -.. autoclass:: taxcalc.ParametersBase +.. autoclass:: taxcalc.Parameters :members: :exclude-members: _update, _indexing_rates_for_update diff --git a/setup.py b/setup.py index c3c034d66..9486b4a07 100755 --- a/setup.py +++ b/setup.py @@ -1,10 +1,3 @@ -import versioneer -versioneer.VCS = 'git' -versioneer.versionfile_source = 'taxcalc/_version.py' -versioneer.versionfile_build = 'taxcalc/_version.py' -versioneer.tag_prefix = '' # tags are like 1.2.0 -versioneer.parentdir_prefix = 'taxcalc-' # dirname like 'taxcalc-1.2.0' - try: from setuptools import setup except ImportError: @@ -13,8 +6,7 @@ with open('README.md') as f: longdesc = f.read() -version = versioneer.get_version() -cmdclass = versioneer.get_cmdclass() +version = '0.0.0' config = { 'description': 'Tax Calculator', @@ -23,7 +15,6 @@ 'description': 'taxcalc', 'long_description': longdesc, 'version': version, - 'cmdclass': cmdclass, 'license': 'CC0 1.0 Universal public domain dedication', 'packages': ['taxcalc', 'taxcalc.tbi', 'taxcalc.cli'], 'include_package_data': True, diff --git a/taxcalc/__init__.py b/taxcalc/__init__.py index 08eb96131..b4eb9a267 100755 --- a/taxcalc/__init__.py +++ b/taxcalc/__init__.py @@ -17,6 +17,4 @@ from taxcalc.tbi import * from taxcalc.cli import * -from taxcalc._version import get_versions -__version__ = get_versions()['version'] -del get_versions +__version__ = '0.0.0' diff --git a/taxcalc/_version.py b/taxcalc/_version.py deleted file mode 100644 index b614d185a..000000000 --- a/taxcalc/_version.py +++ /dev/null @@ -1,192 +0,0 @@ - -# This file helps to compute a version number in source trees obtained from -# git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (built by setup.py sdist) and build -# directories (produced by setup.py build) will contain a much shorter file -# that just contains the computed version number. - -# This file is released into the public domain. Generated by -# versioneer-0.12 (https://github.com/warner/python-versioneer) - -import os -import sys -import re -import subprocess -import errno - -# these strings will be replaced by git during git-archive -git_refnames = "$Format:%d$" -git_full = "$Format:%H$" - -# these strings are filled in when 'setup.py versioneer' creates _version.py -tag_prefix = "" -parentdir_prefix = "taxcalc-" -versionfile_source = "taxcalc/_version.py" - - -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): - assert isinstance(commands, list) - p = None - for c in commands: - try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) - break - except EnvironmentError: - e = sys.exc_info()[1] - if e.errno == errno.ENOENT: - continue - if verbose: - print("unable to run %s" % args[0]) - print(e) - return None - else: - if verbose: - print("unable to find command, tried %s" % (commands,)) - return None - stdout = p.communicate()[0].strip() - if sys.version >= '3': - stdout = stdout.decode() - if p.returncode != 0: - if verbose: - print("unable to run %s (error)" % args[0]) - return None - return stdout - - -def versions_from_parentdir(parentdir_prefix, root, verbose=False): - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%s'," - "but '%s' doesn't start with prefix '%s'" - % (root, dirname, parentdir_prefix)) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} - - -def git_get_keywords(versionfile_abs): - # the code embedded in _version.py can just fetch the value of these - # keywords. When used from setup.py, we don't want to import _version.py, - # so we do it with a regexp instead. This function is not used from - # _version.py. - keywords = {} - try: - f = open(versionfile_abs, "r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - f.close() - except EnvironmentError: - pass - return keywords - - -def git_versions_from_keywords(keywords, tag_prefix, verbose=False): - if not keywords: - return {} # keyword-finding function failed to find keywords - refnames = keywords["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("keywords are unexpanded, not using") - return {} # unexpanded, so not in an unpacked git-archive tarball - refs = set([r.strip() for r in refnames.strip("()").split(",")]) - # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of - # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) - if not tags: - # Either we're using git < 1.8.3, or there really are no tags. We use - # a heuristic: assume all version tags have a digit. The old git %d - # expansion behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us distinguish - # between branches and tags. By ignoring refnames without digits, we - # filter out many common branch names like "release" and - # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) - if verbose: - print("discarding '%s', no digits" % ",".join(refs - tags)) - if verbose: - print("likely tags: %s" % ",".join(sorted(tags))) - for ref in sorted(tags): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - if verbose: - print("picking %s" % r) - return {"version": r, - "full": keywords["full"].strip()} - # no suitable tags, so we use the full revision id - if verbose: - print("no suitable tags, using full revision id") - return {"version": keywords["full"].strip(), - "full": keywords["full"].strip()} - - -def git_versions_from_vcs(tag_prefix, root, verbose=False): - # this runs 'git' from the root of the source tree. This only gets called - # if the git-archive 'subst' keywords were *not* expanded, and - # _version.py hasn't already been rewritten with a short version string, - # meaning we're inside a checked out source tree. - - if not os.path.exists(os.path.join(root, ".git")): - if verbose: - print("no .git in %s" % root) - return {} - - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - stdout = run_command(GITS, ["describe", "--tags", "--dirty", "--always"], - cwd=root) - if stdout is None: - return {} - if not stdout.startswith(tag_prefix): - if verbose: - print("tag '%s' doesn't start with prefix '%s'" % - (stdout, tag_prefix)) - return {} - tag = stdout[len(tag_prefix):] - stdout = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) - if stdout is None: - return {} - full = stdout.strip() - if tag.endswith("-dirty"): - full += "-dirty" - return {"version": tag, "full": full} - - -def get_versions(default={"version": "unknown", "full": ""}, verbose=False): - # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have - # __file__, we can work backwards from there to the root. Some - # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which - # case we can only use expanded keywords. - - keywords = {"refnames": git_refnames, "full": git_full} - ver = git_versions_from_keywords(keywords, tag_prefix, verbose) - if ver: - return ver - - try: - root = os.path.abspath(__file__) - # versionfile_source is the relative path from the top of the source - # tree (where the .git directory might live) to this file. Invert - # this to find the root from __file__. - for i in range(len(versionfile_source.split(os.sep))): - root = os.path.dirname(root) - except NameError: - return default - - return (git_versions_from_vcs(tag_prefix, root, verbose) or - versions_from_parentdir(parentdir_prefix, root, verbose) or - default) diff --git a/taxcalc/behavior.py b/taxcalc/behavior.py index 5b65f2757..72e77b06c 100644 --- a/taxcalc/behavior.py +++ b/taxcalc/behavior.py @@ -18,6 +18,10 @@ class Behavior(Parameters): Constructor for elasticity-based behavioral-response class. + WARNING: the Behavior class is deprecated and will be removed soon. + FUTURE: use the Behavioral-Responses behresp package OR + use the Tax-Calculator quantity_response function. + Parameters ---------- start_year: integer @@ -45,6 +49,10 @@ class instance: Behavior def __init__(self, start_year=JSON_START_YEAR, num_years=DEFAULT_NUM_YEARS): + print(('WARNING: the Behavior class is deprecated ' + 'and will be removed soon.')) + print('FUTURE: use the Behavioral-Responses behresp package OR') + print(' use the Tax-Calculator quantity_response function.') super(Behavior, self).__init__() self._vals = self._params_dict_from_json_file() if start_year < Policy.JSON_START_YEAR: diff --git a/taxcalc/calculator.py b/taxcalc/calculator.py index 32ade2b82..9ed58da43 100644 --- a/taxcalc/calculator.py +++ b/taxcalc/calculator.py @@ -114,7 +114,7 @@ def __init__(self, policy=None, records=None, verbose=True, if self.__policy.current_year < self.__records.data_year: self.__policy.set_year(self.__records.data_year) if consumption is None: - self.__consumption = Consumption(start_year=policy.start_year) + self.__consumption = Consumption() elif isinstance(consumption, Consumption): self.__consumption = copy.deepcopy(consumption) else: diff --git a/taxcalc/cli/tc.py b/taxcalc/cli/tc.py index ff89c7f99..054f503ec 100644 --- a/taxcalc/cli/tc.py +++ b/taxcalc/cli/tc.py @@ -133,10 +133,7 @@ def cli_tc_main(): args = parser.parse_args() # show Tax-Calculator version and quit if --version option specified if args.version: - version = tc.__version__ - if version == 'unknown': - version = 'locally.generated.package' - sys.stdout.write('Tax-Calculator {}\n'.format(version)) + sys.stdout.write('Tax-Calculator {}\n'.format(tc.__version__)) return 0 # write test input and expected output files if --test option specified if args.test: diff --git a/taxcalc/consumption.py b/taxcalc/consumption.py index e0a5ddbc9..8d9bae768 100644 --- a/taxcalc/consumption.py +++ b/taxcalc/consumption.py @@ -19,18 +19,7 @@ class Consumption(Parameters): Parameters ---------- - start_year: integer - first calendar year for consumption parameters. - - num_years: integer - number of calendar years for which to specify parameter - values beginning with start_year. - - Raises - ------ - ValueError: - if start_year is less than Policy.JSON_START_YEAR. - if num_years is less than one. + none Returns ------- @@ -41,15 +30,11 @@ class instance: Consumption DEFAULTS_FILENAME = 'consumption.json' DEFAULT_NUM_YEARS = Policy.DEFAULT_NUM_YEARS - def __init__(self, - start_year=JSON_START_YEAR, - num_years=DEFAULT_NUM_YEARS): + def __init__(self): super(Consumption, self).__init__() self._vals = self._params_dict_from_json_file() - if start_year < Policy.JSON_START_YEAR: - raise ValueError('start_year < Policy.JSON_START_YEAR') - if num_years < 1: - raise ValueError('num_years < 1') + start_year = Consumption.JSON_START_YEAR + num_years = Consumption.DEFAULT_NUM_YEARS self.initialize(start_year, num_years) self.parameter_errors = '' diff --git a/taxcalc/growdiff.py b/taxcalc/growdiff.py index 2f69d6436..673917529 100644 --- a/taxcalc/growdiff.py +++ b/taxcalc/growdiff.py @@ -18,18 +18,7 @@ class GrowDiff(Parameters): Parameters ---------- - start_year: integer - first calendar year for growth difference parameters. - - num_years: integer - number of calendar years for which to specify parameter - values beginning with start_year. - - Raises - ------ - ValueError: - if start_year is less than 2013 - if num_years is less than one. + none Returns ------- @@ -40,15 +29,11 @@ class instance: GrowDiff DEFAULTS_FILENAME = 'growdiff.json' DEFAULT_NUM_YEARS = 15 # must be same as Policy.DEFAULT_NUM_YEARS - def __init__(self, - start_year=JSON_START_YEAR, - num_years=DEFAULT_NUM_YEARS): + def __init__(self): super(GrowDiff, self).__init__() self._vals = self._params_dict_from_json_file() - if start_year < GrowDiff.JSON_START_YEAR: - raise ValueError('start_year < GrowDiff.JSON_START_YEAR') - if num_years < 1: - raise ValueError('num_years < 1') + start_year = GrowDiff.JSON_START_YEAR + num_years = GrowDiff.DEFAULT_NUM_YEARS self.initialize(start_year, num_years) self.parameter_errors = '' diff --git a/taxcalc/parameters.py b/taxcalc/parameters.py index 44ae53cee..d1188b29f 100644 --- a/taxcalc/parameters.py +++ b/taxcalc/parameters.py @@ -22,7 +22,7 @@ class Parameters(): DEFAULTS_FILENAME = None @classmethod - def default_data(cls, metadata=False, start_year=None): + def default_data(cls, metadata=False): """ Return parameter data read from the subclass's json file. @@ -30,22 +30,12 @@ def default_data(cls, metadata=False, start_year=None): ---------- metadata: boolean - start_year: int or None - Returns ------- params: dictionary of data """ - # extract different data from DEFAULT_FILENAME depending on start_year - if start_year is None: - params = cls._params_dict_from_json_file() - else: - nyrs = start_year - cls.JSON_START_YEAR + 1 - ppo = cls(num_years=nyrs) - ppo.set_year(start_year) - params = getattr(ppo, '_vals') - params = Parameters._revised_default_data(params, start_year, - nyrs, ppo) + # extract data from DEFAULT_FILENAME + params = cls._params_dict_from_json_file() # return different data from params dict depending on metadata value if metadata: return params @@ -316,54 +306,6 @@ def _validate_assump_parameter_values(self, parameters_set): ) del parameters - @staticmethod - def _revised_default_data(params, start_year, nyrs, ppo): - """ - Return revised default parameter data. - - Parameters - ---------- - params: dictionary of NAME:DATA pairs for each parameter - as defined in calling default_data staticmethod. - - start_year: int - as defined in calling default_data staticmethod. - - nyrs: int - as defined in calling default_data staticmethod. - - ppo: Policy object - as defined in calling default_data staticmethod. - - Returns - ------- - params: dictionary of revised parameter data - - Notes - ----- - This staticmethod is called from default_data staticmethod in - order to reduce the complexity of the default_data staticmethod. - """ - start_year_str = '{}'.format(start_year) - for name, data in params.items(): - data['start_year'] = start_year - values = data['value'] - num_values = len(values) - if num_values <= nyrs: - # val should be the single start_year value - rawval = getattr(ppo, name[1:]) - if isinstance(rawval, np.ndarray): - val = rawval.tolist() - else: - val = rawval - data['value'] = [val] - data['row_label'] = [start_year_str] - else: # if num_values > nyrs - # val should extend beyond the start_year value - data['value'] = data['value'][(nyrs - 1):] - data['row_label'] = data['row_label'][(nyrs - 1):] - return params - @classmethod def _params_dict_from_json_file(cls): """ diff --git a/taxcalc/policy.py b/taxcalc/policy.py index b77123d60..c57d69a8c 100644 --- a/taxcalc/policy.py +++ b/taxcalc/policy.py @@ -23,19 +23,10 @@ class Policy(Parameters): gfactors: GrowFactors class instance containing price inflation rates and wage growth rates - start_year: integer - first calendar year for historical policy parameters. - - num_years: integer - number of calendar years for which to specify policy parameter - values beginning with start_year. - Raises ------ ValueError: - if gfactors is not a GrowFactors class instance. - if start_year is less than JSON_START_YEAR. - if num_years is less than one. + if gfactors is not a GrowFactors class instance or None. Returns ------- @@ -50,10 +41,7 @@ class instance: Policy # should increase LAST_BUDGET_YEAR by one every calendar year DEFAULT_NUM_YEARS = LAST_BUDGET_YEAR - JSON_START_YEAR + 1 - def __init__(self, - gfactors=None, - start_year=JSON_START_YEAR, - num_years=DEFAULT_NUM_YEARS): + def __init__(self, gfactors=None): super(Policy, self).__init__() if gfactors is None: @@ -63,21 +51,15 @@ def __init__(self, else: raise ValueError('gfactors is not None or a GrowFactors instance') - # read default parameters + # read default parameters and initialize self._vals = self._params_dict_from_json_file() - - if start_year < Policy.JSON_START_YEAR: - raise ValueError('start_year cannot be less than JSON_START_YEAR') - if num_years < 1: - raise ValueError('num_years cannot be less than one') - - syr = start_year - lyr = start_year + num_years - 1 + syr = Policy.JSON_START_YEAR + lyr = Policy.LAST_BUDGET_YEAR + nyrs = Policy.DEFAULT_NUM_YEARS self._inflation_rates = self._gfactors.price_inflation_rates(syr, lyr) - self._apply_clp_cpi_offset(self._vals['_cpi_offset'], num_years) + self._apply_clp_cpi_offset(self._vals['_cpi_offset'], nyrs) self._wage_growth_rates = self._gfactors.wage_growth_rates(syr, lyr) - - self.initialize(start_year, num_years) + self.initialize(syr, nyrs) self.parameter_warnings = '' self.parameter_errors = '' diff --git a/taxcalc/records_variables.json b/taxcalc/records_variables.json index 982e96949..ee01f44f1 100644 --- a/taxcalc/records_variables.json +++ b/taxcalc/records_variables.json @@ -479,12 +479,6 @@ "form": {"2013-2016": "sample construction info"}, "availability": "taxdata_cps" }, - "filer": { - "type": "int", - "desc": "1 if unit files an income tax return; 0 if not (not used in tax-calculation logic); in the puf.csv file a value of 1 indicates record is from IRS/SOI PUF and 0 indicates record is from CPS", - "form": {"2013-2016": "sample construction info"}, - "availability": "taxdata_puf, taxdata_cps" - }, "fips": { "type": "int", "desc": "FIPS state code (not used in tax-calculation logic)", diff --git a/taxcalc/tests/test_calculator.py b/taxcalc/tests/test_calculator.py index c18557ac6..cfac69045 100644 --- a/taxcalc/tests/test_calculator.py +++ b/taxcalc/tests/test_calculator.py @@ -41,32 +41,19 @@ def fixture_rawinputfile(): pass # sometimes we can't remove a generated temporary file -@pytest.fixture(scope='module', name='policyfile') -def fixture_policyfile(): - txt = """{"_almdep": {"value": [7150, 7250, 7400]}, - "_almsep": {"value": [40400, 41050]}, - "_rt5": {"value": [0.33 ]}, - "_rt7": {"value": [0.396]}}""" - f = tempfile.NamedTemporaryFile(mode="a", delete=False) - f.write(txt + "\n") - f.close() - # Must close and then yield for Windows platform - yield f - os.remove(f.name) - - def test_make_calculator(cps_subsample): - syr = 2014 - pol = Policy(start_year=syr, num_years=9) - assert pol.current_year == syr + start_year = Policy.JSON_START_YEAR + sim_year = 2018 + pol = Policy() + assert pol.current_year == start_year rec = Records.cps_constructor(data=cps_subsample) consump = Consumption() - consump.update_consumption({syr: {'_MPC_e20400': [0.05]}}) - assert consump.current_year == Consumption.JSON_START_YEAR + consump.update_consumption({sim_year: {'_MPC_e20400': [0.05]}}) + assert consump.current_year == start_year calc = Calculator(policy=pol, records=rec, consumption=consump, behavior=Behavior()) - assert calc.current_year == syr - assert calc.records_current_year() == syr + assert calc.current_year == Records.CPSCSV_YEAR + assert calc.records_current_year() == Records.CPSCSV_YEAR # test incorrect Calculator instantiation: with pytest.raises(ValueError): Calculator(policy=None, records=rec) @@ -224,8 +211,7 @@ def test_calculator_mtr_when_PT_rates_differ(): def test_make_calculator_increment_years_first(cps_subsample): # create Policy object with policy reform - syr = 2013 - pol = Policy(start_year=syr) + pol = Policy() reform = {2015: {}, 2016: {}} std5 = 2000 reform[2015]['_STD_Aged'] = [[std5, std5, std5, std5, std5]] @@ -238,6 +224,7 @@ def test_make_calculator_increment_years_first(cps_subsample): calc = Calculator(policy=pol, records=rec) # compare expected policy parameter values with those embedded in calc irates = pol.inflation_rates() + syr = Policy.JSON_START_YEAR irate2015 = irates[2015 - syr] irate2016 = irates[2016 - syr] std6 = std5 * (1.0 + irate2015) diff --git a/taxcalc/tests/test_consumption.py b/taxcalc/tests/test_consumption.py index a9c74af38..5f7b7c082 100644 --- a/taxcalc/tests/test_consumption.py +++ b/taxcalc/tests/test_consumption.py @@ -7,11 +7,9 @@ from taxcalc import Policy, Records, Calculator, Consumption -def test_incorrect_Consumption_instantiation(): - with pytest.raises(ValueError): - consump = Consumption(start_year=2000) - with pytest.raises(ValueError): - consump = Consumption(num_years=0) +def test_year_consistency(): + assert Consumption.JSON_START_YEAR == Policy.JSON_START_YEAR + assert Consumption.DEFAULT_NUM_YEARS == Policy.DEFAULT_NUM_YEARS def test_validity_of_consumption_vars_set(): @@ -22,7 +20,7 @@ def test_validity_of_consumption_vars_set(): def test_update_consumption(): - consump = Consumption(start_year=2013) + consump = Consumption() consump.update_consumption({}) consump.update_consumption({2014: {'_MPC_e20400': [0.05], '_BEN_mcare_value': [0.75]}, diff --git a/taxcalc/tests/test_cpscsv.py b/taxcalc/tests/test_cpscsv.py index 6903790bd..2371b47ed 100644 --- a/taxcalc/tests/test_cpscsv.py +++ b/taxcalc/tests/test_cpscsv.py @@ -107,7 +107,7 @@ def test_cps_availability(tests_path, cps_path): if 'taxdata_cps' in vdict.get('availability', ''): recvars.add(vname) # check that cpsvars and recvars sets are the same - assert (cpsvars - recvars) == set() + assert (cpsvars - recvars) == set(['filer']) assert (recvars - cpsvars) == set() diff --git a/taxcalc/tests/test_growdiff.py b/taxcalc/tests/test_growdiff.py index b6739e687..a00350295 100644 --- a/taxcalc/tests/test_growdiff.py +++ b/taxcalc/tests/test_growdiff.py @@ -3,7 +3,7 @@ import os import json -from numpy.testing import assert_allclose +import numpy as np import pytest from taxcalc import GrowDiff, GrowFactors, Policy @@ -13,26 +13,19 @@ def test_year_consistency(): assert GrowDiff.DEFAULT_NUM_YEARS == Policy.DEFAULT_NUM_YEARS -def test_incorrect_growdiff_ctor(): - with pytest.raises(ValueError): - gdiff = GrowDiff(start_year=2000) - with pytest.raises(ValueError): - gdiff = GrowDiff(num_years=0) - - def test_update_and_apply_growdiff(): - syr = 2013 - nyrs = 5 - lyr = syr + nyrs - 1 - gdiff = GrowDiff(start_year=syr, num_years=nyrs) + gdiff = GrowDiff() # update GrowDiff instance diffs = {2014: {'_AWAGE': [0.01]}, 2016: {'_AWAGE': [0.02]}} gdiff.update_growdiff(diffs) - expected_wage_diffs = [0.00, 0.01, 0.01, 0.02, 0.02] - assert_allclose(gdiff._AWAGE, expected_wage_diffs, atol=0.0, rtol=0.0) + expected_wage_diffs = [0.00, 0.01, 0.01, 0.02, 0.02] + [0.02]*10 + assert np.allclose(gdiff._AWAGE, expected_wage_diffs, atol=0.0, rtol=0.0) # apply growdiff to GrowFactors instance gf = GrowFactors() + syr = GrowDiff.JSON_START_YEAR + nyrs = GrowDiff.DEFAULT_NUM_YEARS + lyr = syr + nyrs - 1 pir_pre = gf.price_inflation_rates(syr, lyr) wgr_pre = gf.wage_growth_rates(syr, lyr) gfactors = GrowFactors() @@ -41,8 +34,8 @@ def test_update_and_apply_growdiff(): wgr_pst = gfactors.wage_growth_rates(syr, lyr) expected_wgr_pst = [wgr_pre[i] + expected_wage_diffs[i] for i in range(0, nyrs)] - assert_allclose(pir_pre, pir_pst, atol=0.0, rtol=0.0) - assert_allclose(wgr_pst, expected_wgr_pst, atol=1.0e-9, rtol=0.0) + assert np.allclose(pir_pre, pir_pst, atol=0.0, rtol=0.0) + assert np.allclose(wgr_pst, expected_wgr_pst, atol=1.0e-9, rtol=0.0) def test_incorrect_update_growdiff(): @@ -61,11 +54,12 @@ def test_incorrect_update_growdiff(): def test_has_any_response(): - syr = 2014 - gdiff = GrowDiff(start_year=syr) + start_year = GrowDiff.JSON_START_YEAR + gdiff = GrowDiff() + assert gdiff.current_year == start_year assert gdiff.has_any_response() is False gdiff.update_growdiff({2020: {'_AWAGE': [0.01]}}) - assert gdiff.current_year == syr + assert gdiff.current_year == start_year assert gdiff.has_any_response() is True diff --git a/taxcalc/tests/test_policy.py b/taxcalc/tests/test_policy.py index c986b8bab..f2cbe6486 100644 --- a/taxcalc/tests/test_policy.py +++ b/taxcalc/tests/test_policy.py @@ -6,7 +6,6 @@ import json import tempfile import numpy as np -from numpy.testing import assert_allclose import pytest from taxcalc import Policy, Calculator @@ -14,10 +13,6 @@ def test_incorrect_Policy_instantiation(): with pytest.raises(ValueError): Policy(gfactors=list()) - with pytest.raises(ValueError): - Policy(start_year=2000) - with pytest.raises(ValueError): - Policy(num_years=0) def test_correct_Policy_instantiation(): @@ -53,8 +48,7 @@ def test_policy_json_content(): def test_constant_inflation_rate_with_reform(): - syr = 2013 - pol = Policy(start_year=syr) + pol = Policy() # implement reform in year before final year fyr = Policy.LAST_BUDGET_YEAR ryr = fyr - 1 @@ -65,6 +59,7 @@ def test_constant_inflation_rate_with_reform(): pol.implement_reform(reform) # extract price inflation rates pirates = pol.inflation_rates() + syr = Policy.JSON_START_YEAR irate_b = pirates[ryr - 2 - syr] irate_a = pirates[ryr - syr] # check implied inflation rate just before reform @@ -76,8 +71,8 @@ def test_constant_inflation_rate_with_reform(): def test_variable_inflation_rate_with_reform(): - syr = 2013 - pol = Policy(start_year=syr) + pol = Policy() + syr = Policy.JSON_START_YEAR assert pol._II_em[2013 - syr] == 3900 # implement reform in 2020 which is two years before the last year, 2022 reform = { @@ -108,9 +103,9 @@ def test_multi_year_reform(): Test multi-year reform involving 1D and 2D parameters. """ # specify dimensions of policy Policy object - syr = 2013 + syr = Policy.JSON_START_YEAR nyrs = Policy.DEFAULT_NUM_YEARS - pol = Policy(start_year=syr) + pol = Policy() iratelist = pol.inflation_rates() ifactor = {} for i in range(0, nyrs): @@ -120,48 +115,48 @@ def test_multi_year_reform(): for i in range(0, nyrs): wfactor[syr + i] = 1.0 + wratelist[i] # confirm that parameters have current-law values - assert_allclose(getattr(pol, '_EITC_c'), - Policy._expand_array( - np.array([[487, 3250, 5372, 6044], - [496, 3305, 5460, 6143], - [503, 3359, 5548, 6242], - [506, 3373, 5572, 6269], - [510, 3400, 5616, 6318]], - dtype=np.float64), - False, False, - inflate=True, - inflation_rates=iratelist, - num_years=nyrs), - atol=0.01, rtol=0.0) - assert_allclose(getattr(pol, '_STD_Dep'), - Policy._expand_array( - np.array([1000, 1000, 1050, 1050, 1050], - dtype=np.float64), - False, False, - inflate=True, - inflation_rates=iratelist, - num_years=nyrs), - atol=0.01, rtol=0.0) - assert_allclose(getattr(pol, '_CTC_c'), - Policy._expand_array( - np.array([1000] * 5 + [1400] * 4 + - [1500] * 3 + [1600] + [1000], - dtype=np.float64), - False, False, - inflate=False, - inflation_rates=iratelist, - num_years=nyrs), - atol=0.01, rtol=0.0) + assert np.allclose(getattr(pol, '_EITC_c'), + Policy._expand_array( + np.array([[487, 3250, 5372, 6044], + [496, 3305, 5460, 6143], + [503, 3359, 5548, 6242], + [506, 3373, 5572, 6269], + [510, 3400, 5616, 6318]], + dtype=np.float64), + False, False, + inflate=True, + inflation_rates=iratelist, + num_years=nyrs), + atol=0.01, rtol=0.0) + assert np.allclose(getattr(pol, '_STD_Dep'), + Policy._expand_array( + np.array([1000, 1000, 1050, 1050, 1050], + dtype=np.float64), + False, False, + inflate=True, + inflation_rates=iratelist, + num_years=nyrs), + atol=0.01, rtol=0.0) + assert np.allclose(getattr(pol, '_CTC_c'), + Policy._expand_array( + np.array([1000] * 5 + [1400] * 4 + + [1500] * 3 + [1600] + [1000], + dtype=np.float64), + False, False, + inflate=False, + inflation_rates=iratelist, + num_years=nyrs), + atol=0.01, rtol=0.0) # this parameter uses a different indexing rate - assert_allclose(getattr(pol, '_SS_Earnings_c'), - Policy._expand_array( - np.array([113700, 117000, 118500, 118500, 127200], - dtype=np.float64), - False, False, - inflate=True, - inflation_rates=wratelist, - num_years=nyrs), - atol=0.01, rtol=0.0) + assert np.allclose(getattr(pol, '_SS_Earnings_c'), + Policy._expand_array( + np.array([113700, 117000, 118500, 118500, 127200], + dtype=np.float64), + False, False, + inflate=True, + inflation_rates=wratelist, + num_years=nyrs), + atol=0.01, rtol=0.0) # specify multi-year reform using a dictionary of year_provisions dicts reform = { 2015: { @@ -232,26 +227,26 @@ def check_eitc_c(ppo, reform, ifactor): alen = len(arr[0]) for i in range(0, ppo.num_years): actual[ppo.start_year + i] = arr[i] - assert_allclose(actual[2013], [487, 3250, 5372, 6044], - atol=0.01, rtol=0.0) - assert_allclose(actual[2014], [496, 3305, 5460, 6143], - atol=0.01, rtol=0.0) - assert_allclose(actual[2015], [503, 3359, 5548, 6242], - atol=0.01, rtol=0.0) + assert np.allclose(actual[2013], [487, 3250, 5372, 6044], + atol=0.01, rtol=0.0) + assert np.allclose(actual[2014], [496, 3305, 5460, 6143], + atol=0.01, rtol=0.0) + assert np.allclose(actual[2015], [503, 3359, 5548, 6242], + atol=0.01, rtol=0.0) e2016 = reform[2016]['_EITC_c'][0] - assert_allclose(actual[2016], e2016, atol=0.01, rtol=0.0) + assert np.allclose(actual[2016], e2016, atol=0.01, rtol=0.0) e2017 = [ifactor[2016] * actual[2016][j] for j in range(0, alen)] - assert_allclose(actual[2017], e2017, atol=0.01, rtol=0.0) + assert np.allclose(actual[2017], e2017, atol=0.01, rtol=0.0) e2018 = [ifactor[2017] * actual[2017][j] for j in range(0, alen)] assert np.allclose(actual[2018], e2018, atol=0.01, rtol=0.0) e2019 = reform[2019]['_EITC_c'][0] - assert_allclose(actual[2019], e2019, atol=0.01, rtol=0.0) + assert np.allclose(actual[2019], e2019, atol=0.01, rtol=0.0) e2020 = [ifactor[2019] * actual[2019][j] for j in range(0, alen)] - assert_allclose(actual[2020], e2020, atol=0.01, rtol=0.0) + assert np.allclose(actual[2020], e2020, atol=0.01, rtol=0.0) e2021 = [ifactor[2020] * actual[2020][j] for j in range(0, alen)] - assert_allclose(actual[2021], e2021, atol=0.01, rtol=0.0) + assert np.allclose(actual[2021], e2021, atol=0.01, rtol=0.0) e2022 = [ifactor[2021] * actual[2021][j] for j in range(0, alen)] - assert_allclose(actual[2022], e2022, atol=0.01, rtol=0.0) + assert np.allclose(actual[2022], e2022, atol=0.01, rtol=0.0) def check_ii_em(ppo, reform, ifactor): @@ -335,40 +330,40 @@ def test_create_parameters_from_file(monkeypatch, defaultpolicyfile): monkeypatch.setattr(Policy, 'DEFAULTS_FILENAME', defaultpolicyfile.name) ppo = Policy() inf_rates = ppo.inflation_rates() - assert_allclose(ppo._almdep, - Policy._expand_array( - np.array([7150, 7250, 7400], - dtype=np.float64), - False, False, - inflate=True, - inflation_rates=inf_rates, - num_years=ppo.num_years), - atol=0.01, rtol=0.0) - assert_allclose(ppo._almsep, - Policy._expand_array( - np.array([40400, 41050], - dtype=np.float64), - False, False, - inflate=True, - inflation_rates=inf_rates, - num_years=ppo.num_years), - atol=0.01, rtol=0.0) - assert_allclose(ppo._rt5, - Policy._expand_array( - np.array([0.33]), - False, False, - inflate=False, - inflation_rates=inf_rates, - num_years=ppo.num_years), - atol=0.01, rtol=0.0) - assert_allclose(ppo._rt7, - Policy._expand_array( - np.array([0.396]), - False, False, - inflate=False, - inflation_rates=inf_rates, - num_years=ppo.num_years), - atol=0.01, rtol=0.0) + assert np.allclose(ppo._almdep, + Policy._expand_array( + np.array([7150, 7250, 7400], + dtype=np.float64), + False, False, + inflate=True, + inflation_rates=inf_rates, + num_years=ppo.num_years), + atol=0.01, rtol=0.0) + assert np.allclose(ppo._almsep, + Policy._expand_array( + np.array([40400, 41050], + dtype=np.float64), + False, False, + inflate=True, + inflation_rates=inf_rates, + num_years=ppo.num_years), + atol=0.01, rtol=0.0) + assert np.allclose(ppo._rt5, + Policy._expand_array( + np.array([0.33]), + False, False, + inflate=False, + inflation_rates=inf_rates, + num_years=ppo.num_years), + atol=0.01, rtol=0.0) + assert np.allclose(ppo._rt7, + Policy._expand_array( + np.array([0.396]), + False, False, + inflate=False, + inflation_rates=inf_rates, + num_years=ppo.num_years), + atol=0.01, rtol=0.0) def test_parameters_get_default(): @@ -384,23 +379,23 @@ def test_implement_reform_Policy_raises_on_no_year(): def test_Policy_reform_in_start_year(): - ppo = Policy(start_year=2013) + ppo = Policy() reform = {2013: {'_STD': [[16000, 13000, 13000, 16000, 16000]]}} ppo.implement_reform(reform) - assert_allclose(ppo.STD, - np.array([16000, 13000, 13000, 16000, 16000]), - atol=0.01, rtol=0.0) + assert np.allclose(ppo.STD, + np.array([16000, 13000, 13000, 16000, 16000]), + atol=0.01, rtol=0.0) -def test_implement_reform_Policy_raises_on_future_year(): - ppo = Policy(start_year=2013) +def test_implement_reform_Policy_raises_on_early_year(): + ppo = Policy() reform = {2010: {'_STD_Aged': [[1400, 1100, 1100, 1400, 1400]]}} with pytest.raises(ValueError): ppo.implement_reform(reform) def test_Policy_reform_with_default_cpi_flags(): - ppo = Policy(start_year=2013) + ppo = Policy() reform = {2015: {'_II_em': [4300]}} ppo.implement_reform(reform) # '_II_em' has a default cpi_flag of True, so @@ -410,43 +405,47 @@ def test_Policy_reform_with_default_cpi_flags(): def test_Policy_reform_after_start_year(): - ppo = Policy(start_year=2013) + ppo = Policy() reform = {2015: {'_STD_Aged': [[1400, 1100, 1100, 1400, 1400]]}} ppo.implement_reform(reform) ppo.set_year(2015) - assert_allclose(ppo.STD_Aged, - np.array([1400, 1100, 1100, 1400, 1400]), - atol=0.01, rtol=0.0) + assert np.allclose(ppo.STD_Aged, + np.array([1400, 1100, 1100, 1400, 1400]), + atol=0.01, rtol=0.0) def test_Policy_reform_makes_no_changes_before_year(): - ppo = Policy(start_year=2013) + ppo = Policy() reform = {2015: {'_II_em': [4400], '_II_em_cpi': True}} ppo.implement_reform(reform) ppo.set_year(2015) - assert_allclose(ppo._II_em[:3], np.array([3900, 3950, 4400]), - atol=0.01, rtol=0.0) + assert np.allclose(ppo._II_em[:3], np.array([3900, 3950, 4400]), + atol=0.01, rtol=0.0) assert ppo.II_em == 4400 -def test_parameters_get_default_start_year(): - paramdata = Policy.default_data(metadata=True, start_year=2015) - # 1D data, has 2015 values +def test_parameters_get_default_data(): + paramdata = Policy.default_data(metadata=True) + # 1D data, has 2013+ values meta_II_em = paramdata['_II_em'] - assert meta_II_em['start_year'] == 2015 - assert meta_II_em['row_label'] == [str(cyr) for cyr in range(2015, 2027)] - assert meta_II_em['value'] == [4000, 4050, 4050] + [0] * 8 + [4883] - # 2D data, has 2015 values + assert meta_II_em['start_year'] == 2013 + assert meta_II_em['row_label'] == [str(cyr) for cyr in range(2013, 2027)] + expval = [3900, 3950, 4000, 4050, 4050] + [0] * 8 + [4883] + assert meta_II_em['value'] == expval + # 2D data, has 2013+ values meta_std_aged = paramdata['_STD_Aged'] - assert meta_std_aged['start_year'] == 2015 - assert meta_std_aged['row_label'] == ['2015', '2016', '2017'] - assert meta_std_aged['value'] == [[1550, 1250, 1250, 1550, 1550], + assert meta_std_aged['start_year'] == 2013 + explabels = ['2013', '2014', '2015', '2016', '2017'] + assert meta_std_aged['row_label'] == explabels + assert meta_std_aged['value'] == [[1500, 1200, 1200, 1500, 1500], + [1550, 1200, 1200, 1550, 1550], + [1550, 1250, 1250, 1550, 1550], [1550, 1250, 1250, 1550, 1550], [1550, 1250, 1250, 1550, 1550]] - # 1D data, doesn't have 2015 values, is not CPI inflated + # 1D data, has only 2013 values because is not CPI inflated meta_kt_c_age = paramdata['_AMT_KT_c_Age'] - assert meta_kt_c_age['start_year'] == 2015 - assert meta_kt_c_age['row_label'] == ['2015'] + assert meta_kt_c_age['start_year'] == 2013 + assert meta_kt_c_age['row_label'] == ['2013'] assert meta_kt_c_age['value'] == [24] @@ -553,11 +552,12 @@ def test_pop_the_cap_reform(): Test eliminating the maximum taxable earnings (MTE) used in the calculation of the OASDI payroll tax. """ - # clarify start year and create Policy parameters object - syr = 2013 - ppo = Policy(start_year=syr) + # create Policy parameters object + ppo = Policy() + assert ppo.current_year == Policy.JSON_START_YEAR # confirm that MTE has current-law values in 2015 and 2016 mte = ppo._SS_Earnings_c + syr = Policy.JSON_START_YEAR assert mte[2015 - syr] == 118500 assert mte[2016 - syr] == 118500 # specify a "pop the cap" reform that eliminates MTE cap in 2016 @@ -580,9 +580,9 @@ def test_order_of_cpi_and_level_reforms(): {2015: {'_SS_Earnings_c_cpi': False, '_SS_Earnings_c': [500000]}}] # specify two Policy objects - syr = 2013 - ppo = [Policy(start_year=syr), Policy(start_year=syr)] + ppo = [Policy(), Policy()] # apply reforms to corresponding Policy object & check post-reform values + syr = Policy.JSON_START_YEAR for ref in range(len(reform)): # confirm pre-reform MTE values in 2014-17 mte = ppo[ref]._SS_Earnings_c diff --git a/taxcalc/tests/test_pufcsv.py b/taxcalc/tests/test_pufcsv.py index 40fc064a7..7110ad451 100644 --- a/taxcalc/tests/test_pufcsv.py +++ b/taxcalc/tests/test_pufcsv.py @@ -331,7 +331,7 @@ def test_puf_availability(tests_path, puf_path): if 'taxdata_puf' in vdict.get('availability', ''): recvars.add(vname) # check that pufvars and recvars sets are the same - assert (pufvars - recvars) == set() + assert (pufvars - recvars) == set(['filer']) assert (recvars - pufvars) == set() diff --git a/taxcalc/validation/taxsim/test.sh b/taxcalc/validation/taxsim/test.sh index 255b6cd16..097a042bf 100755 --- a/taxcalc/validation/taxsim/test.sh +++ b/taxcalc/validation/taxsim/test.sh @@ -50,7 +50,7 @@ unzip -oq output-taxsim.zip $LYY.in.out-taxsim$SUFFIX tclsh taxdiffs.tcl $OVAR4 $LYY.in.out-simtax$SUFFIX \ $LYY.in.out-taxsim$SUFFIX > $LYY$SUFFIX.taxdiffs RC=$? -if [ $RC -ne 0 ]; then +if [[ $RC -ne 0 ]]; then exit $RC fi # Check for difference between actual .taxdiffs and expected .taxdiffs files diff --git a/versioneer.py b/versioneer.py deleted file mode 100644 index 179e34c9e..000000000 --- a/versioneer.py +++ /dev/null @@ -1,941 +0,0 @@ - -# Version: 0.12 - -""" -The Versioneer -============== - -* like a rocketeer, but for versions! -* https://github.com/warner/python-versioneer -* Brian Warner -* License: Public Domain -* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, and pypy - -[![Build Status] -(https://travis-ci.org/warner/python-versioneer.png?branch=master)] -(https://travis-ci.org/warner/python-versioneer) - -This is a tool for managing a recorded version number in distutils-based -python projects. The goal is to remove the tedious and error-prone "update -the embedded version string" step from your release process. Making a new -release should be as easy as recording a new tag in your version-control -system, and maybe making new tarballs. - - -## Quick Install - -* `pip install versioneer` to somewhere to your $PATH -* run `versioneer-installer` in your source tree: this installs `versioneer.py` -* follow the instructions below (also in the `versioneer.py` docstring) - -## Version Identifiers - -Source trees come from a variety of places: - -* a version-control system checkout (mostly used by developers) -* a nightly tarball, produced by build automation -* a snapshot tarball, produced by a web-based VCS browser, like github's - "tarball from tag" feature -* a release tarball, produced by "setup.py sdist", distributed through PyPI - -Within each source tree, the version identifier (either a string or a number, -this tool is format-agnostic) can come from a variety of places: - -* ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows - about recent "tags" and an absolute revision-id -* the name of the directory into which the tarball was unpacked -* an expanded VCS keyword ($Id$, etc) -* a `_version.py` created by some earlier build step - -For released software, the version identifier is closely related to a VCS -tag. Some projects use tag names that include more than just the version -string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool -needs to strip the tag prefix to extract the version identifier. For -unreleased software (between tags), the version identifier should provide -enough information to help developers recreate the same tree, while also -giving them an idea of roughly how old the tree is (after version 1.2, before -version 1.3). Many VCS systems can report a description that captures this, -for example 'git describe --tags --dirty --always' reports things like -"0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the -0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has -uncommitted changes. - -The version identifier is used for multiple purposes: - -* to allow the module to self-identify its version: `myproject.__version__` -* to choose a name and prefix for a 'setup.py sdist' tarball - -## Theory of Operation - -Versioneer works by adding a special `_version.py` file into your source -tree, where your `__init__.py` can import it. This `_version.py` knows how to -dynamically ask the VCS tool for version information at import time. However, -when you use "setup.py build" or "setup.py sdist", `_version.py` in the new -copy is replaced by a small static file that contains just the generated -version data. - -`_version.py` also contains `$Revision$` markers, and the installation -process marks `_version.py` to have this marker rewritten with a tag name -during the "git archive" command. As a result, generated tarballs will -contain enough information to get the proper version. - - -## Installation - -First, decide on values for the following configuration variables: - -* `VCS`: the version control system you use. Currently accepts "git". - -* `versionfile_source`: - - A project-relative pathname into which the generated version strings should - be written. This is usually a `_version.py` next to your project's main - `__init__.py` file, so it can be imported at runtime. If your project uses - `src/myproject/__init__.py`, this should be `src/myproject/_version.py`. - This file should be checked in to your VCS as usual: the copy created below - by `setup.py versioneer` will include code that parses expanded VCS - keywords in generated tarballs. The 'build' and 'sdist' commands will - replace it with a copy that has just the calculated version string. - - This must be set even if your project does not have any modules (and will - therefore never import `_version.py`), since "setup.py sdist" -based trees - still need somewhere to record the pre-calculated version strings. Anywhere - in the source tree should do. If there is a `__init__.py` next to your - `_version.py`, the `setup.py versioneer` command (described below) will - append some `__version__`-setting assignments, if they aren't already - present. - -* `versionfile_build`: - - Like `versionfile_source`, but relative to the build directory instead of - the source directory. These will differ when your setup.py uses - 'package_dir='. If you have `package_dir={'myproject': 'src/myproject'}`, - then you will probably have `versionfile_build='myproject/_version.py'` and - `versionfile_source='src/myproject/_version.py'`. - - If this is set to None, then `setup.py build` will not attempt to rewrite - any `_version.py` in the built tree. If your project does not have any - libraries (e.g. if it only builds a script), then you should use - `versionfile_build = None` and override `distutils.command.build_scripts` - to explicitly insert a copy of `versioneer.get_version()` into your - generated script. - -* `tag_prefix`: - - a string, like 'PROJECTNAME-', which appears at the start of all VCS tags. - If your tags look like 'myproject-1.2.0', then you should use - tag_prefix='myproject-'. If you use unprefixed tags like '1.2.0', this - should be an empty string. - -* `parentdir_prefix`: - - a string, frequently the same as tag_prefix, which appears at the start of - all unpacked tarball filenames. If your tarball unpacks into - 'myproject-1.2.0', this should be 'myproject-'. - -This tool provides one script, named `versioneer-installer`. That script does -one thing: write a copy of `versioneer.py` into the current directory. - -To versioneer-enable your project: - -* 1: Run `versioneer-installer` to copy `versioneer.py` into the top of your - source tree. - -* 2: add the following lines to the top of your `setup.py`, with the - configuration values you decided earlier: - - import versioneer - versioneer.VCS = 'git' - versioneer.versionfile_source = 'src/myproject/_version.py' - versioneer.versionfile_build = 'myproject/_version.py' - versioneer.tag_prefix = '' # tags are like 1.2.0 - versioneer.parentdir_prefix = ( - 'myproject-' # dirname like 'myproject-1.2.0') - -* 3: add the following arguments to the setup() call in your setup.py: - - version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass(), - -* 4: now run `setup.py versioneer`, which will create `_version.py`, and will - modify your `__init__.py` (if one exists next to `_version.py`) to define - `__version__` (by calling a function from `_version.py`). It will also - modify your `MANIFEST.in` to include both `versioneer.py` and the generated - `_version.py` in sdist tarballs. - -* 5: commit these changes to your VCS. To make sure you won't forget, - `setup.py versioneer` will mark everything it touched for addition. - -## Post-Installation Usage - -Once established, all uses of your tree from a VCS checkout should get the -current version string. All generated tarballs should include an embedded -version string (so users who unpack them will not need a VCS tool installed). - -If you distribute your project through PyPI, then the release process should -boil down to two steps: - -* 1: git tag 1.0 -* 2: python setup.py register sdist upload - -If you distribute it through github (i.e. users use github to generate -tarballs with `git archive`), the process is: - -* 1: git tag 1.0 -* 2: git push; git push --tags - -Currently, all version strings must be based upon a tag. Versioneer will -report "unknown" until your tree has at least one tag in its history. This -restriction will be fixed eventually (see issue #12). - -## Version-String Flavors - -Code which uses Versioneer can learn about its version string at runtime by -importing `_version` from your main `__init__.py` file and running the -`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can -import the top-level `versioneer.py` and run `get_versions()`. - -Both functions return a dictionary with different keys for different flavors -of the version string: - -* `['version']`: condensed tag+distance+shortid+dirty identifier. For git, - this uses the output of `git describe --tags --dirty --always` but strips - the tag_prefix. For example "0.11-2-g1076c97-dirty" indicates that the tree - is like the "1076c97" commit but has uncommitted changes ("-dirty"), and - that this commit is two revisions ("-2-") beyond the "0.11" tag. For - released software (exactly equal to a known tag), the identifier will only - contain the stripped tag, e.g. "0.11". - -* `['full']`: detailed revision identifier. For Git, this is the full SHA1 - commit id, followed by "-dirty" if the tree contains uncommitted changes, - e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac-dirty". - -Some variants are more useful than others. Including `full` in a bug report -should allow developers to reconstruct the exact code being tested (or -indicate the presence of local changes that should be shared with the -developers). `version` is suitable for display in an "about" box or a CLI -`--version` output: it can be easily compared against release notes and lists -of bugs fixed in various releases. - -In the future, this will also include a -[PEP-0440](http://legacy.python.org/dev/peps/pep-0440/) -compatible flavor -(e.g. `1.2.post0.dev123`). This loses a lot of information (and has no room -for a hash-based revision id), but is safe to use in a `setup.py` -"`version=`" argument. It also enables tools like *pip* to compare version -strings and evaluate compatibility constraint declarations. - -The `setup.py versioneer` command adds the following text to your -`__init__.py` to place a basic version in `YOURPROJECT.__version__`: - - from ._version import get_versions - __version__ = get_versions()['version'] - del get_versions - -## Updating Versioneer - -To upgrade your project to a new release of Versioneer, do the following: - -* install the new Versioneer (`pip install -U versioneer` or equivalent) -* re-run `versioneer-installer` in your source tree to replace your copy of - `versioneer.py` -* edit `setup.py`, if necessary, to include any new configuration settings - indicated by the release notes -* re-run `setup.py versioneer` to replace `SRC/_version.py` -* commit any changed files - -### Upgrading from 0.10 to 0.11 - -You must add a `versioneer.VCS = "git"` to your `setup.py` before re-running -`setup.py versioneer`. This will enable the use of additional version-control -systems (SVN, etc) in the future. - -### Upgrading from 0.11 to 0.12 - -Nothing special. - -## Future Directions - -This tool is designed to make it easily extended to other version-control -systems: all VCS-specific components are in separate directories like -src/git/ . The top-level `versioneer.py` script is assembled from these -components by running make-versioneer.py . In the future, make-versioneer.py -will take a VCS name as an argument, and will construct a version of -`versioneer.py` that is specific to the given VCS. It might also take the -configuration arguments that are currently provided manually during -installation by editing setup.py . Alternatively, it might go the other -direction and include code from all supported VCS systems, reducing the -number of intermediate scripts. - - -## License - -To make Versioneer easier to embed, all its code is hereby released into the -public domain. The `_version.py` that it creates is also in the public -domain. - -""" - -import os -import sys -import re -import subprocess -import errno -from distutils.core import Command -from distutils.command.sdist import sdist as _sdist -from distutils.command.build import build as _build - -# these configuration settings will be overridden by setup.py after it -# imports us -versionfile_source = None -versionfile_build = None -tag_prefix = None -parentdir_prefix = None -VCS = None - -# these dictionaries contain VCS-specific tools -LONG_VERSION_PY = {} - - -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): - assert isinstance(commands, list) - p = None - for c in commands: - try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) - break - except EnvironmentError: - e = sys.exc_info()[1] - if e.errno == errno.ENOENT: - continue - if verbose: - print("unable to run %s" % args[0]) - print(e) - return None - else: - if verbose: - print("unable to find command, tried %s" % (commands,)) - return None - stdout = p.communicate()[0].strip() - if sys.version >= '3': - stdout = stdout.decode() - if p.returncode != 0: - if verbose: - print("unable to run %s (error)" % args[0]) - return None - return stdout - -LONG_VERSION_PY['git'] = ''' -# This file helps to compute a version number in source trees obtained from -# git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (built by setup.py sdist) and build -# directories (produced by setup.py build) will contain a much shorter file -# that just contains the computed version number. - -# This file is released into the public domain. Generated by -# versioneer-0.12 (https://github.com/warner/python-versioneer) - -# these strings will be replaced by git during git-archive -git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" -git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" - -# these strings are filled in when 'setup.py versioneer' creates _version.py -tag_prefix = "%(TAG_PREFIX)s" -parentdir_prefix = "%(PARENTDIR_PREFIX)s" -versionfile_source = "%(VERSIONFILE_SOURCE)s" - -import os, sys, re, subprocess, errno - -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): - assert isinstance(commands, list) - p = None - for c in commands: - try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) - break - except EnvironmentError: - e = sys.exc_info()[1] - if e.errno == errno.ENOENT: - continue - if verbose: - print("unable to run %%s" %% args[0]) - print(e) - return None - else: - if verbose: - print("unable to find command, tried %%s" %% (commands,)) - return None - stdout = p.communicate()[0].strip() - if sys.version >= '3': - stdout = stdout.decode() - if p.returncode != 0: - if verbose: - print("unable to run %%s (error)" %% args[0]) - return None - return stdout - - -def versions_from_parentdir(parentdir_prefix, root, verbose=False): - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%%s', - but '%%s' doesn't start with prefix '%%s'" %% - (root, dirname, parentdir_prefix)) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} - -def git_get_keywords(versionfile_abs): - # the code embedded in _version.py can just fetch the value of these - # keywords. When used from setup.py, we don't want to import _version.py, - # so we do it with a regexp instead. This function is not used from - # _version.py. - keywords = {} - try: - f = open(versionfile_abs,"r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - f.close() - except EnvironmentError: - pass - return keywords - -def git_versions_from_keywords(keywords, tag_prefix, verbose=False): - if not keywords: - return {} # keyword-finding function failed to find keywords - refnames = keywords["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("keywords are unexpanded, not using") - return {} # unexpanded, so not in an unpacked git-archive tarball - refs = set([r.strip() for r in refnames.strip("()").split(",")]) - # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of - # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) - if not tags: - # Either we're using git < 1.8.3, or there really are no tags. We use - # a heuristic: assume all version tags have a digit. The old git %%d - # expansion behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us distinguish - # between branches and tags. By ignoring refnames without digits, we - # filter out many common branch names like "release" and - # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) - if verbose: - print("discarding '%%s', no digits" %% ",".join(refs-tags)) - if verbose: - print("likely tags: %%s" %% ",".join(sorted(tags))) - for ref in sorted(tags): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - if verbose: - print("picking %%s" %% r) - return { "version": r, - "full": keywords["full"].strip() } - # no suitable tags, so we use the full revision id - if verbose: - print("no suitable tags, using full revision id") - return { "version": keywords["full"].strip(), - "full": keywords["full"].strip() } - - -def git_versions_from_vcs(tag_prefix, root, verbose=False): - # this runs 'git' from the root of the source tree. This only gets called - # if the git-archive 'subst' keywords were *not* expanded, and - # _version.py hasn't already been rewritten with a short version string, - # meaning we're inside a checked out source tree. - - if not os.path.exists(os.path.join(root, ".git")): - if verbose: - print("no .git in %%s" %% root) - return {} - - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - stdout = run_command(GITS, ["describe", "--tags", "--dirty", "--always"], - cwd=root) - if stdout is None: - return {} - if not stdout.startswith(tag_prefix): - if verbose: - print("tag '%%s' doesn't start with prefix - '%%s'" %% (stdout, tag_prefix)) - return {} - tag = stdout[len(tag_prefix):] - stdout = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) - if stdout is None: - return {} - full = stdout.strip() - if tag.endswith("-dirty"): - full += "-dirty" - return {"version": tag, "full": full} - - -def get_versions(default={"version": "unknown", "full": ""}, verbose=False): - # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have - # __file__, we can work backwards from there to the root. Some - # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which - # case we can only use expanded keywords. - - keywords = { "refnames": git_refnames, "full": git_full } - ver = git_versions_from_keywords(keywords, tag_prefix, verbose) - if ver: - return ver - - try: - root = os.path.abspath(__file__) - # versionfile_source is the relative path from the top of the source - # tree (where the .git directory might live) to this file. Invert - # this to find the root from __file__. - for i in range(len(versionfile_source.split(os.sep))): - root = os.path.dirname(root) - except NameError: - return default - - return (git_versions_from_vcs(tag_prefix, root, verbose) - or versions_from_parentdir(parentdir_prefix, root, verbose) - or default) -''' - - -def git_get_keywords(versionfile_abs): - # the code embedded in _version.py can just fetch the value of these - # keywords. When used from setup.py, we don't want to import _version.py, - # so we do it with a regexp instead. This function is not used from - # _version.py. - keywords = {} - try: - f = open(versionfile_abs, "r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - f.close() - except EnvironmentError: - pass - return keywords - - -def git_versions_from_keywords(keywords, tag_prefix, verbose=False): - if not keywords: - return {} # keyword-finding function failed to find keywords - refnames = keywords["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("keywords are unexpanded, not using") - return {} # unexpanded, so not in an unpacked git-archive tarball - refs = set([r.strip() for r in refnames.strip("()").split(",")]) - # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of - # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) - if not tags: - # Either we're using git < 1.8.3, or there really are no tags. We use - # a heuristic: assume all version tags have a digit. The old git %d - # expansion behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us distinguish - # between branches and tags. By ignoring refnames without digits, we - # filter out many common branch names like "release" and - # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) - if verbose: - print("discarding '%s', no digits" % ",".join(refs - tags)) - if verbose: - print("likely tags: %s" % ",".join(sorted(tags))) - for ref in sorted(tags): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - if verbose: - print("picking %s" % r) - return {"version": r, - "full": keywords["full"].strip()} - # no suitable tags, so we use the full revision id - if verbose: - print("no suitable tags, using full revision id") - return {"version": keywords["full"].strip(), - "full": keywords["full"].strip()} - - -def git_versions_from_vcs(tag_prefix, root, verbose=False): - # this runs 'git' from the root of the source tree. This only gets called - # if the git-archive 'subst' keywords were *not* expanded, and - # _version.py hasn't already been rewritten with a short version string, - # meaning we're inside a checked out source tree. - - if not os.path.exists(os.path.join(root, ".git")): - if verbose: - print("no .git in %s" % root) - return {} - - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - stdout = run_command(GITS, ["describe", "--tags", "--dirty", "--always"], - cwd=root) - if stdout is None: - return {} - if not stdout.startswith(tag_prefix): - if verbose: - print("tag '%s' doesn't start with prefix '%s'" - % (stdout, tag_prefix)) - return {} - tag = stdout[len(tag_prefix):] - stdout = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) - if stdout is None: - return {} - full = stdout.strip() - if tag.endswith("-dirty"): - full += "-dirty" - return {"version": tag, "full": full} - - -def do_vcs_install(manifest_in, versionfile_source, ipy): - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - files = [manifest_in, versionfile_source] - if ipy: - files.append(ipy) - try: - me = __file__ - if me.endswith(".pyc") or me.endswith(".pyo"): - me = os.path.splitext(me)[0] + ".py" - versioneer_file = os.path.relpath(me) - except NameError: - versioneer_file = "versioneer.py" - files.append(versioneer_file) - present = False - try: - f = open(".gitattributes", "r") - for line in f.readlines(): - if line.strip().startswith(versionfile_source): - if "export-subst" in line.strip().split()[1:]: - present = True - f.close() - except EnvironmentError: - pass - if not present: - f = open(".gitattributes", "a+") - f.write("%s export-subst\n" % versionfile_source) - f.close() - files.append(".gitattributes") - run_command(GITS, ["add", "--"] + files) - - -def versions_from_parentdir(parentdir_prefix, root, verbose=False): - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%s'," - "but '%s' doesn't start with prefix '%s'" % - (root, dirname, parentdir_prefix)) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} - -SHORT_VERSION_PY = """ -# This file was generated by 'versioneer.py' (0.12) from -# revision-control system data, or from the parent directory name of an -# unpacked source archive. Distribution tarballs contain a pre-generated copy -# of this file. - -version_version = '%(version)s' -version_full = '%(full)s' -def get_versions(default={}, verbose=False): - return {'version': version_version, 'full': version_full} - -""" - -DEFAULT = {"version": "unknown", "full": "unknown"} - - -def versions_from_file(filename): - versions = {} - try: - with open(filename) as f: - for line in f.readlines(): - mo = re.match("version_version = '([^']+)'", line) - if mo: - versions["version"] = mo.group(1) - mo = re.match("version_full = '([^']+)'", line) - if mo: - versions["full"] = mo.group(1) - except EnvironmentError: - return {} - - return versions - - -def write_to_version_file(filename, versions): - with open(filename, "w") as f: - f.write(SHORT_VERSION_PY % versions) - - print("set %s to '%s'" % (filename, versions["version"])) - - -def get_root(): - try: - return os.path.dirname(os.path.abspath(__file__)) - except NameError: - return os.path.dirname(os.path.abspath(sys.argv[0])) - - -def vcs_function(vcs, suffix): - return getattr(sys.modules[__name__], '%s_%s' % (vcs, suffix), None) - - -def get_versions(default=DEFAULT, verbose=False): - # returns dict with two keys: 'version' and 'full' - assert (versionfile_source is not None, - "please set versioneer.versionfile_source") - assert tag_prefix is not None, "please set versioneer.tag_prefix" - assert (parentdir_prefix is not None, - "please set versioneer.parentdir_prefix") - assert VCS is not None, "please set versioneer.VCS" - - # I am in versioneer.py, which must live at the top of the source tree, - # which we use to compute the root directory. py2exe/bbfreeze/non-CPython - # don't have __file__, in which case we fall back to sys.argv[0] (which - # ought to be the setup.py script). We prefer __file__ since that's more - # robust in cases where setup.py was invoked in some weird way (e.g. pip) - root = get_root() - versionfile_abs = os.path.join(root, versionfile_source) - - # extract version from first of _version.py, VCS command (e.g. 'git - # describe'), parentdir. This is meant to work for developers using a - # source checkout, for users of a tarball created by 'setup.py sdist', - # and for users of a tarball/zipball created by 'git archive' or github's - # download-from-tag feature or the equivalent in other VCSes. - - get_keywords_f = vcs_function(VCS, "get_keywords") - versions_from_keywords_f = vcs_function(VCS, "versions_from_keywords") - if get_keywords_f and versions_from_keywords_f: - vcs_keywords = get_keywords_f(versionfile_abs) - ver = versions_from_keywords_f(vcs_keywords, tag_prefix) - if ver: - if verbose: - print("got version from expanded keyword %s" % ver) - return ver - - ver = versions_from_file(versionfile_abs) - if ver: - if verbose: - print("got version from file %s %s" - % (versionfile_abs, ver)) - return ver - - versions_from_vcs_f = vcs_function(VCS, "versions_from_vcs") - if versions_from_vcs_f: - ver = versions_from_vcs_f(tag_prefix, root, verbose) - if ver: - if verbose: - print("got version from VCS %s" % ver) - return ver - - ver = versions_from_parentdir(parentdir_prefix, root, verbose) - if ver: - if verbose: - print("got version from parentdir %s" % ver) - return ver - - if verbose: - print("got version from default %s" % default) - return default - - -def get_version(verbose=False): - return get_versions(verbose=verbose)["version"] - - -class cmd_version(Command): - description = "report generated version string" - user_options = [] - boolean_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - ver = get_version(verbose=True) - print("Version is currently: %s" % ver) - - -class cmd_build(_build): - def run(self): - versions = get_versions(verbose=True) - _build.run(self) - # now locate _version.py in the new build/ directory and replace it - # with an updated value - if versionfile_build: - target_versionfile = os.path.join(self.build_lib, - versionfile_build) - print("UPDATING %s" % target_versionfile) - os.unlink(target_versionfile) - with open(target_versionfile, "w") as f: - f.write(SHORT_VERSION_PY % versions) - -if 'cx_Freeze' in sys.modules: # cx_freeze enabled? - from cx_Freeze.dist import build_exe as _build_exe - - class cmd_build_exe(_build_exe): - def run(self): - versions = get_versions(verbose=True) - target_versionfile = versionfile_source - print("UPDATING %s" % target_versionfile) - os.unlink(target_versionfile) - with open(target_versionfile, "w") as f: - f.write(SHORT_VERSION_PY % versions) - - _build_exe.run(self) - os.unlink(target_versionfile) - with open(versionfile_source, "w") as f: - assert VCS is not None, "please set versioneer.VCS" - LONG = LONG_VERSION_PY[VCS] - f.write(LONG % {"DOLLAR": "$", - "TAG_PREFIX": tag_prefix, - "PARENTDIR_PREFIX": parentdir_prefix, - "VERSIONFILE_SOURCE": versionfile_source, - }) - - -class cmd_sdist(_sdist): - def run(self): - versions = get_versions(verbose=True) - self._versioneer_generated_versions = versions - # unless we update this, the command will keep using the old version - self.distribution.metadata.version = versions["version"] - return _sdist.run(self) - - def make_release_tree(self, base_dir, files): - _sdist.make_release_tree(self, base_dir, files) - # now locate _version.py in the new base_dir directory (remembering - # that it may be a hardlink) and replace it with an updated value - target_versionfile = os.path.join(base_dir, versionfile_source) - print("UPDATING %s" % target_versionfile) - os.unlink(target_versionfile) - with open(target_versionfile, "w") as f: - f.write(SHORT_VERSION_PY % self._versioneer_generated_versions) - -INIT_PY_SNIPPET = """ -from ._version import get_versions -__version__ = get_versions()['version'] -del get_versions -""" - - -class cmd_update_files(Command): - description = ("install/upgrade Versioneer files:" - "__init__.py SRC/_version.py") - user_options = [] - boolean_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - print(" creating %s" % versionfile_source) - with open(versionfile_source, "w") as f: - assert VCS is not None, "please set versioneer.VCS" - LONG = LONG_VERSION_PY[VCS] - f.write(LONG % {"DOLLAR": "$", - "TAG_PREFIX": tag_prefix, - "PARENTDIR_PREFIX": parentdir_prefix, - "VERSIONFILE_SOURCE": versionfile_source, - }) - - ipy = os.path.join(os.path.dirname(versionfile_source), "__init__.py") - if os.path.exists(ipy): - try: - with open(ipy, "r") as f: - old = f.read() - except EnvironmentError: - old = "" - if INIT_PY_SNIPPET not in old: - print(" appending to %s" % ipy) - with open(ipy, "a") as f: - f.write(INIT_PY_SNIPPET) - else: - print(" %s unmodified" % ipy) - else: - print(" %s doesn't exist, ok" % ipy) - ipy = None - - # Make sure both the top-level "versioneer.py" and versionfile_source - # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so - # they'll be copied into source distributions. Pip won't be able to - # install the package without this. - manifest_in = os.path.join(get_root(), "MANIFEST.in") - simple_includes = set() - try: - with open(manifest_in, "r") as f: - for line in f: - if line.startswith("include "): - for include in line.split()[1:]: - simple_includes.add(include) - except EnvironmentError: - pass - # That doesn't cover everything MANIFEST.in can do - # (http://docs.python.org/2/distutils/sourcedist.html#commands), so - # it might give some false negatives. Appending redundant 'include' - # lines is safe, though. - if "versioneer.py" not in simple_includes: - print(" appending 'versioneer.py' to MANIFEST.in") - with open(manifest_in, "a") as f: - f.write("include versioneer.py\n") - else: - print(" 'versioneer.py' already in MANIFEST.in") - if versionfile_source not in simple_includes: - print(" appending versionfile_source ('%s') to MANIFEST.in" % - versionfile_source) - with open(manifest_in, "a") as f: - f.write("include %s\n" % versionfile_source) - else: - print(" versionfile_source already in MANIFEST.in") - - # Make VCS-specific changes. For git, this means creating/changing - # .gitattributes to mark _version.py for export-time keyword - # substitution. - do_vcs_install(manifest_in, versionfile_source, ipy) - - -def get_cmdclass(): - cmds = {'version': cmd_version, - 'versioneer': cmd_update_files, - 'build': cmd_build, - 'sdist': cmd_sdist, - } - if 'cx_Freeze' in sys.modules: # cx_freeze enabled? - cmds['build_exe'] = cmd_build_exe - del cmds['build'] - - return cmds