duecredit-0.4.5.3/0000700013464101346420000000000012641331574013262 5ustar yohyoh00000000000000duecredit-0.4.5.3/.travis.yml0000600013464101346420000000256612641330346015402 0ustar yohyoh00000000000000# vim ft=yaml # travis-ci.org definition for DueCredit build language: python sudo: false python: - "2.7" # - "3.2" - "3.3" - "3.4" # - "pypy" # - "pypy3" before_install: # - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then travis_retry sudo apt-get update -qq; fi git: depth: 99999 install: - if [[ $TRAVIS_PYTHON_VERSION == pypy ]] ; then dpkg --compare-versions $(pypy --version 2>&1 | awk '/PyPy/{print $2;}') ge 2.5.1 || { d=$PWD; cd /tmp; wget --no-check-certificate https://bitbucket.org/pypy/pypy/downloads/pypy-2.5.1-linux64.tar.bz2; tar -xjvf pypy*bz2; cd pypy-*/bin/; export PATH=$PWD:$PATH; cd $d; } ; fi - travis_retry pip install -q coveralls - pip install -r requirements.txt - python setup.py --help # just to trigger generation of .version - pip install -e . script: - nosetests --with-doctest --with-cov --cover-package duecredit --logging-level=INFO -v - python setup.py install # test installation after_success: - coveralls deploy: provider: pypi distributions: sdist user: yarikoptic password: secure: mTxbioGS+sdfxnJRbAGZCxjWlaGJx+KqXPfYGESKcg6IVaSUM9D4CUhxgHHW88FYSnkmCvwuu57w7AAot9FyG6Q/1q656gluCbEJzfDJerSH1S06HqAEmjSPJvIEG/zwvPIUm3RPc+8j9XtedztM3aVWkqBHAzvUzEnsX1jJpic= on: tags: true branch: master repo: duecredit/duecredit condition: "$TRAVIS_PYTHON_VERSION == 2.7 && $TRAVIS_TAG =~ ^[0-9][.][0-9][.0-9]*" duecredit-0.4.5.3/duecredit.egg-info/0000700013464101346420000000000012641331574016724 5ustar yohyoh00000000000000duecredit-0.4.5.3/duecredit.egg-info/top_level.txt0000600013464101346420000000001212641331574021451 0ustar yohyoh00000000000000duecredit duecredit-0.4.5.3/duecredit.egg-info/PKG-INFO0000600013464101346420000000420512641331574020024 0ustar yohyoh00000000000000Metadata-Version: 1.1 Name: duecredit Version: 0.4.5.3 Summary: Publications (and donations) tracer Home-page: https://github.com/duecredit/duecredit Author: Yaroslav Halchenko, Matteo Visconti di Oleggio Castello Author-email: yoh@onerussian.com License: 2-clause BSD License Download-URL: https://github.com/duecredit/duecredit/releases/tag/0.4.5.3 Description: duecredit is being conceived to address the problem of inadequate citation of scientific software and methods, and limited visibility of donation requests for open-source software. It provides a simple framework (at the moment for Python only) to embed publication or other references in the original code so they are automatically collected and reported to the user at the necessary level of reference detail, i.e. only references for actually used functionality will be presented back if software provides multiple citeable implementations. To get a sense of what duecredit is about, run for example shipped along example script, or your analysis script with `-m duecredit`, e.g. python -m duecredit examples/example_scipy.py Keywords: citation tracing Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Console Classifier: Environment :: Other Environment Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Education Classifier: Intended Audience :: End Users/Desktop Classifier: Intended Audience :: Legal Industry Classifier: Intended Audience :: Other Audience Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Documentation Classifier: Topic :: Printing Classifier: Topic :: Software Development :: Documentation Classifier: Topic :: Software Development :: Libraries :: Python Modules Provides: duecredit duecredit-0.4.5.3/duecredit.egg-info/requires.txt0000600013464101346420000000002512641331574021323 0ustar yohyoh00000000000000requests citeproc-py duecredit-0.4.5.3/duecredit.egg-info/SOURCES.txt0000600013464101346420000000355212641331574020617 0ustar yohyoh00000000000000.coveragerc .travis.yml CHANGELOG.md CONTRIBUTING.md LICENSE MANIFEST.in README.md requirements.txt setup.cfg setup.py tox.ini duecredit/__init__.py duecredit/__main__.py duecredit/collector.py duecredit/config.py duecredit/dueswitch.py duecredit/entries.py duecredit/io.py duecredit/log.py duecredit/parsers.py duecredit/stub.py duecredit/utils.py duecredit/version.py duecredit/versions.py duecredit.egg-info/PKG-INFO duecredit.egg-info/SOURCES.txt duecredit.egg-info/dependency_links.txt duecredit.egg-info/entry_points.txt duecredit.egg-info/pbr.json duecredit.egg-info/requires.txt duecredit.egg-info/top_level.txt duecredit/cmdline/__init__.py duecredit/cmdline/cmd_summary.py duecredit/cmdline/cmd_test.py duecredit/cmdline/common_args.py duecredit/cmdline/helpers.py duecredit/cmdline/main.py duecredit/injections/__init__.py duecredit/injections/injector.py duecredit/injections/mod_biosig.py duecredit/injections/mod_dipy.py duecredit/injections/mod_mdp.py duecredit/injections/mod_mne.py duecredit/injections/mod_nibabel.py duecredit/injections/mod_nipy.py duecredit/injections/mod_nipype.py duecredit/injections/mod_numpy.py duecredit/injections/mod_pandas.py duecredit/injections/mod_psychopy.py duecredit/injections/mod_scipy.py duecredit/injections/mod_skimage.py duecredit/injections/mod_sklearn.py duecredit/tests/__init__.py duecredit/tests/test__main__.py duecredit/tests/test_api.py duecredit/tests/test_cmdline.py duecredit/tests/test_collector.py duecredit/tests/test_dueswitch.py duecredit/tests/test_injections.py duecredit/tests/test_io.py duecredit/tests/test_utils.py duecredit/tests/test_versions.py duecredit/tests/envs/nolxml/lxml.py duecredit/tests/envs/stubbed/README.txt duecredit/tests/envs/stubbed/due.py duecredit/tests/envs/stubbed/script.py duecredit/tests/mod/__init__.py duecredit/tests/mod/imported.py duecredit/tests/mod/submod.py examples/example_scipy.pyduecredit-0.4.5.3/duecredit.egg-info/entry_points.txt0000600013464101346420000000007312641331574022224 0ustar yohyoh00000000000000[console_scripts] duecredit = duecredit.cmdline.main:main duecredit-0.4.5.3/duecredit.egg-info/pbr.json0000600013464101346420000000005612610734034020377 0ustar yohyoh00000000000000{"is_release": true, "git_version": "9bd3ff4"}duecredit-0.4.5.3/duecredit.egg-info/dependency_links.txt0000600013464101346420000000000112641331574022774 0ustar yohyoh00000000000000 duecredit-0.4.5.3/PKG-INFO0000600013464101346420000000420512641331574014362 0ustar yohyoh00000000000000Metadata-Version: 1.1 Name: duecredit Version: 0.4.5.3 Summary: Publications (and donations) tracer Home-page: https://github.com/duecredit/duecredit Author: Yaroslav Halchenko, Matteo Visconti di Oleggio Castello Author-email: yoh@onerussian.com License: 2-clause BSD License Download-URL: https://github.com/duecredit/duecredit/releases/tag/0.4.5.3 Description: duecredit is being conceived to address the problem of inadequate citation of scientific software and methods, and limited visibility of donation requests for open-source software. It provides a simple framework (at the moment for Python only) to embed publication or other references in the original code so they are automatically collected and reported to the user at the necessary level of reference detail, i.e. only references for actually used functionality will be presented back if software provides multiple citeable implementations. To get a sense of what duecredit is about, run for example shipped along example script, or your analysis script with `-m duecredit`, e.g. python -m duecredit examples/example_scipy.py Keywords: citation tracing Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Console Classifier: Environment :: Other Environment Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Education Classifier: Intended Audience :: End Users/Desktop Classifier: Intended Audience :: Legal Industry Classifier: Intended Audience :: Other Audience Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Documentation Classifier: Topic :: Printing Classifier: Topic :: Software Development :: Documentation Classifier: Topic :: Software Development :: Libraries :: Python Modules Provides: duecredit duecredit-0.4.5.3/MANIFEST.in0000600013464101346420000000023412641331417015015 0ustar yohyoh00000000000000include LICENSE README.md CHANGELOG.md requirements.txt tox.ini CONTRIBUTING.md setup.cfg include .coveragerc .travis.yml include examples/example_scipy.py duecredit-0.4.5.3/tox.ini0000600013464101346420000000116112555725350014601 0ustar yohyoh00000000000000[tox] envlist = py27,py33 #,flake8 [testenv] commands = nosetests {posargs} deps = -r{toxinidir}/requirements.txt [testenv:cover] commands = nosetests --with-coverage {posargs} [testenv:flake8] commands = flake8 {posargs} [testenv:venv] commands = {posargs} [flake8] #show-source = True # E265 = comment blocks like @{ section, which it can't handle # E266 = too many leading '#' for block comment # E731 = do not assign a lambda expression, use a def # W293 = Blank line contains whitespace #ignore = E265,W293,E266,E731 max-line-length = 120 include = duecredit exclude = .tox,.venv,venv-debug,build,dist,doc,git/ext/ duecredit-0.4.5.3/requirements.txt0000600013464101346420000000007412555330243016545 0ustar yohyoh00000000000000mock nose>=1.3.4 vcrpy contextlib2 requests citeproc-py six duecredit-0.4.5.3/CHANGELOG.md0000600013464101346420000000015212541422503015063 0ustar yohyoh00000000000000Changelog ========= 0.1.0 ----- - Initial release. Provides API prototype and basic `summary` command. duecredit-0.4.5.3/LICENSE0000600013464101346420000000304212537123554014271 0ustar yohyoh00000000000000Copyright 2015 Yaroslav Halchenko, Matteo Visconti di Oleggio Castello. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the copyright holder. duecredit-0.4.5.3/setup.cfg0000600013464101346420000000036112641331574015105 0ustar yohyoh00000000000000[metadata] description-file = README.md [bdist_rpm] release = 1 packager = Yaroslav Halchenko doc_files = README.md CHANGELOG.md LICENSE CONTRIBUTING.md [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 duecredit-0.4.5.3/README.md0000600013464101346420000002011212617561304014536 0ustar yohyoh00000000000000duecredit ========= [![Build Status](https://travis-ci.org/duecredit/duecredit.svg?branch=master)](https://travis-ci.org/duecredit/duecredit) [![Coverage Status](https://coveralls.io/repos/duecredit/duecredit/badge.svg)](https://coveralls.io/r/duecredit/duecredit) duecredit is being conceived to address the problem of inadequate citation of scientific software and methods, and limited visibility of donation requests for open-source software. It provides a simple framework (at the moment for Python only) to embed publication or other references in the original code so they are automatically collected and reported to the user at the necessary level of reference detail, i.e. only references for actually used functionality will be presented back if software provides multiple citeable implementations. duecredit 101 ============= You can already start "registering" citations using duecredit in your Python modules and even registering citations (we call this approach "injections") for modules which do not (yet) use duecredit. duecredit will remain an optional dependency, i.e. your software will work correctly even without duecredit installed. "Native" use of duecredit (recommended) --------------------------------------- For using duecredit in your software 1. copy `duecredit/stub.py` to your codebase, e.g. wget -q -O /path/tomodule/yourmodule/due.py \ https://raw.githubusercontent.com/duecredit/duecredit/master/duecredit/stub.py **Note** that it might be better to avoid naming it duecredit.py to avoid shadowing installed duecredit. 2. Then use `duecredit` import due and necessary entries in your code as from .due import due, Doi to provide reference for the entire module just use e.g. due.cite(Doi("1.2.3/x.y.z"), description="Solves all your problems", path="magicpy") To provide a reference for a function or a method, use dcite decorator @due.dcite(Doi("1.2.3/x.y.z"), description="Resolves constipation issue") def pushit(): ... Add injections for other existing modules ----------------------------------------- We hope that eventually this somewhat cruel approach will not be necessary. But until other packages support duecredit "natively" we have provided a way to "inject" citations for modules and/or functions and methods via injections: citations will be added to the corresponding functionality upon those modules import. All injections are collected under [duecredit/injections](https://github.com/duecredit/duecredit/tree/master/duecredit/injections). See any file there with `mod_` prefix for a complete example. But overall it is just a regular Python module defining a function `inject(injector)` which will then add new entries to the injector, which will in turn add those entries to the duecredit whenever the corresponding module gets imported. User-view --------- By default `duecredit` does exactly nothing -- all decorators do not decorate, all `cite` functions just return, so there should be no fear that it would break anything. Then whenever anyone runs their analysis which uses your code and sets `DUECREDIT_ENABLE=yes` environment variable or uses `python -m duecredit`, and invokes any of the cited function/methods, at the end of the run all collected bibliography will be presented to the screen and pickled into `.duecredit.p` file in current directory: $> python -m duecredit examples/example_scipy.py I: Simulating 4 blobs I: Done clustering 4 blobs DueCredit Report: - scipy (v 0.14.1) [1] - scipy.cluster.hierarchy:linkage (Single linkage hierarchical clustering) [2] - numpy (v 1.8.2) [3] 2 modules cited 1 functions cited References ---------- [1] Jones, E. et al., 2001. SciPy: Open source scientific tools for Python. [2] Sibson, R., 1973. SLINK: an optimally efficient algorithm for the single-link cluster method. The Computer Journal, 16(1), pp.30–34. [3] Van Der Walt, S., Colbert, S.C. & Varoquaux, G., 2011. The NumPy array: a structure for efficient numerical computation. Computing in Science & Engineering, 13(2), pp.22–30. Incremental runs of various software would keep enriching that file. Then you can use `duecredit summary` command to show that information again (stored in `.duecredit.p` file) or export it as a BibTeX file ready for reuse, e.g.: $> venv/bin/duecredit summary --format=bibtex @book{sokal1958statistical, author = {Sokal, R R and Michener, C D and {University of Kansas}}, title = {{A Statistical Method for Evaluating Systematic Relationships}}, publisher = {University of Kansas}, year = {1958}, series = {University of Kansas science bulletin} } @book{jain1988algorithms, title={Algorithms for clustering data}, author={Jain, Anil K and Dubes, Richard C}, year={1988}, publisher={Prentice-Hall, Inc.} } ... and if by default only references for "implementation" are listed, we can enable listing of references for other tags as well (e.g. "edu" depicting instructional materials -- textbooks etc on the topic): $> DUECREDIT_REPORT_TAGS=* duecredit summary DueCredit Report: - scipy (v 0.14.1) [1, 2, 3, 4, 5, 6, 7, 8] - scipy.cluster.hierarchy:linkage (Single linkage hierarchical clustering) [9] - numpy (v 1.8.2) [10] 2 modules cited 1 functions cited References ---------- [1] Sokal, R.R., Michener, C.D. & University of Kansas, 1958. A Statistical Method for Evaluating Systematic Relationships, University of Kansas. [2] Jain, A.K. & Dubes, R.C., 1988. Algorithms for clustering data, Prentice-Hall, Inc.. [3] Johnson, S.C., 1967. Hierarchical clustering schemes. Psychometrika, 32(3), pp.241–254. ... Ultimate goals ============== Reduce demand for prima ballerina projects ------------------------------------------ **Problem**: Scientific software is often developed to gain citations for original publication through the use of the software implementing it. Unfortunately such established procedure discourages contributions to existing projects and fosters new projects to be developed from scratch. **Solution**: With easy ways to provide all-and-only relevant references for used functionality within a large(r) framework, scientific developers will prefer to contribute to already existing projects. **Benefits**: As a result, scientific developers will immediately benefit from adhering to proper development procedures (codebase structuring, testing, etc) and already established delivery and deployment channels existing projects already have. This will increase efficiency and standardization of scientific software development, thus addressing many (if not all) core problems with scientific software development everyone likes to bash about (reproducibility, longevity, etc.). Adequately reference core libraries ----------------------------------- **Problem**: Scientific software often, if not always, uses 3rd party libraries (e.g., NumPy, SciPy, atlas) which might not even be visible at the user level. Therefore they are rarely referenced in the publications despite providing the fundamental core for solving a scientific problem at hands. **Solution**: With automated bibliography compilation for all used libraries, such projects and their authors would get a chance to receive adequate citability. **Benefits**: Adequate appreciation of the scientific software developments. Coupled with a solution for "prima ballerina" problem, more contributions will flow into the core/foundational projects making new methodological developments readily available to even wider audiences without proliferation of the low quality scientific software. Similar/related projects ======================== [sempervirens](https://github.com/njsmith/sempervirens) -- *an experimental prototype for gathering anonymous, opt-in usage data for open scientific software*. Eventually in duecredit we aim either to provide similar functionality (since we are collecting such information as well) or just interface/report to sempervirens. duecredit-0.4.5.3/CONTRIBUTING.md0000600013464101346420000001573512552216351015524 0ustar yohyoh00000000000000Contributing to DueCredit ========================= [gh-duecredit]: http://github.com/duecredit/duecredit Files organization ------------------ - `duecredit/` is the main Python module where major development is happening, with major submodules being: - `cmdline/` contains commands for the command line interface. See any of the `cmd_*.py` files here for an example - `tests/` all unit- and regression- tests - `utils.py` provides convenience helpers used by unit-tests such as `@with_tree`, `@serve_path_via_http` and other decorators - `tools/` might contain helper utilities used during development, testing, and benchmarking of DueCredit. Implemented in any most appropriate language (Python, bash, etc.) How to contribute ----------------- The preferred way to contribute to the DueCredit code base is to fork the [main repository][gh-duecredit] on GitHub. Here we outline the workflow used by the developers: 0. Have a clone of our main [project repository][gh-duecredit] as `origin` remote in your git: git clone git://github.com/duecredit/duecredit 1. Fork the [project repository][gh-duecredit]: click on the 'Fork' button near the top of the page. This creates a copy of the code base under your account on the GitHub server. 2. Add your forked clone as a remote to the local clone you already have on your local disk: git remote add gh-YourLogin git@github.com:YourLogin/duecredit.git git fetch gh-YourLogin To ease addition of other github repositories as remotes, here is a little bash function/script to add to your `~/.bashrc`: ghremote () { url="$1" proj=${url##*/} url_=${url%/*} login=${url_##*/} git remote add gh-$login $url git fetch gh-$login } thus you could simply run: ghremote git@github.com:YourLogin/duecredit.git to add the above `gh-YourLogin` remote. 3. Create a branch (generally off the `origin/master`) to hold your changes: git checkout -b nf-my-feature and start making changes. Ideally, use a prefix signaling the purpose of the branch - `nf-` for new features - `bf-` for bug fixes - `rf-` for refactoring - `doc-` for documentation contributions (including in the code docstrings). We recommend to not work in the ``master`` branch! 4. Work on this copy on your computer using Git to do the version control. When you're done editing, do: git add modified_files git commit to record your changes in Git. Ideally, prefix your commit messages with the `NF`, `BF`, `RF`, `DOC` similar to the branch name prefixes, but you could also use `TST` for commits concerned solely with tests, and `BK` to signal that the commit causes a breakage (e.g. of tests) at that point. Multiple entries could be listed joined with a `+` (e.g. `rf+doc-`). See `git log` for examples. If a commit closes an existing DueCredit issue, then add to the end of the mesage `(Closes #ISSUE_NUMER)` 5. Push to GitHub with: git push -u gh-YourLogin nf-my-feature Finally, go to the web page of your fork of the DueCredit repo, and click 'Pull request' (PR) to send your changes to the maintainers for review. This will send an email to the committers. You can commit new changes to this branch and keep pushing to your remote -- github automagically adds them to your previously opened PR. (If any of the above seems like magic to you, then look up the [Git documentation](http://git-scm.com/documentation) on the web.) Quality Assurance ----------------- It is recommended to check that your contribution complies with the following rules before submitting a pull request: - All public methods should have informative docstrings with sample usage presented as doctests when appropriate. - All other tests pass when everything is rebuilt from scratch. - New code should be accompanied by tests. ### Tests All tests are available under `duecredit/tests`. To execute tests, the codebase needs to be "installed" in order to generate scripts for the entry points. For that, the recommended course of action is to use `virtualenv`, e.g. ```sh virtualenv --system-site-packages venv-tests source venv-tests/bin/activate pip install -r requirements.txt python setup.py develop ``` On Debian-based systems you might need to install some C-libraries to guarantee installation (building) of some Python modules we use. So for `lxml` please first ```sh sudo apt-get install libxml2-dev libxslt1-dev ``` On Mac OS X Yosemite additional steps are required to make `lxml` work properly (see [this stackoverflow answer](https://stackoverflow.com/questions/19548011/cannot-install-lxml-on-mac-os-x-10-9/26544099#26544099?newreg=d3394d8210cc4779accfac05fe5c9b21)). We recommend using homebrew to install the same dependencies (see the [Homebrew website](http://brew.sh/) to install it), then run ```sh brew install libxml2 libxslt brew link libxml2 --force brew link libxslt --force ``` note that this will ovverride the default libraries installed with Mac OS X. Then use that virtual environment to run the tests, via ```sh python -m nose -s -v duecredit ``` or similiarly, ```sh nosetests -s -v duecredit ``` then to later deactivate the virtualenv just simply enter ```sh deactivate ``` ### Coverage You can also check for common programming errors with the following tools: - Code with good unittest coverage (at least 80%), check with: pip install nose coverage nosetests --with-coverage duecredit ### Linting We are not (yet) fully PEP8 compliant, so please use these tools as guidelines for your contributions, but not to PEP8 entire code base. [beyond-pep8]: https://www.youtube.com/watch?v=wf-BqAjZb8M *Sidenote*: watch [Raymond Hettinger - Beyond PEP 8][beyond-pep8] - No pyflakes warnings, check with: pip install pyflakes pyflakes path/to/module.py - No PEP8 warnings, check with: pip install pep8 pep8 path/to/module.py - AutoPEP8 can help you fix some of the easy redundant errors: pip install autopep8 autopep8 path/to/pep8.py Also, some team developers use [PyCharm community edition](https://www.jetbrains.com/pycharm) which provides built-in PEP8 checker and handy tools such as smart splits/joins making it easier to maintain code following the PEP8 recommendations. NeuroDebian provides `pycharm-community-sloppy` package to ease pycharm installation even further. Easy Issues ----------- A great way to start contributing to DueCredit is to pick an item from the list of [Easy issues](https://github.com/duecredit/duecredit/labels/easy) in the issue tracker. Resolving these issues allows you to start contributing to the project without much prior knowledge. Your assistance in this area will be greatly appreciated by the more experienced developers as it helps free up their time to concentrate on other issues. duecredit-0.4.5.3/duecredit/0000700013464101346420000000000012641331574015232 5ustar yohyoh00000000000000duecredit-0.4.5.3/duecredit/tests/0000700013464101346420000000000012641331574016374 5ustar yohyoh00000000000000duecredit-0.4.5.3/duecredit/tests/test__main__.py0000600013464101346420000000300712562736560021376 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## import atexit import sys from mock import patch from six.moves import StringIO from tempfile import NamedTemporaryFile from nose.tools import assert_raises, assert_equal from .. import __main__, __version__ from .. import due @patch('sys.stdout', new_callable=StringIO) def test_main_help(stdout): assert_raises(SystemExit, __main__.main, ['__main__.py', '--help']) assert( stdout.getvalue().startswith( "Usage: %s -m duecredit [OPTIONS] [ARGS]\n" % sys.executable )) @patch('sys.stdout', new_callable=StringIO) def test_main_version(stdout): assert_raises(SystemExit, __main__.main, ['__main__.py', '--version']) assert_equal(stdout.getvalue().rstrip(), "duecredit %s" % __version__) @patch.object(due, 'activate') @patch('sys.stdout', new_callable=StringIO) def test_main_run_a_script(stdout, mock_activate): f = NamedTemporaryFile() f.write('print("Running the script")\n'.encode()); f.flush() __main__.main(['__main__.py', f.name]) assert_equal(stdout.getvalue().rstrip(), "Running the script") # And we have "activated" the due mock_activate.assert_called_once_with(True) duecredit-0.4.5.3/duecredit/tests/test_injections.py0000600013464101346420000002051012601330232022134 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## import gc import sys from six import viewvalues, PY2 if PY2: import __builtin__ else: import builtins as __builtin__ _orig__import__ = __builtin__.__import__ from duecredit.collector import DueCreditCollector, InactiveDueCreditCollector from duecredit.entries import BibTeX, Doi from ..injections.injector import DueCreditInjector, find_object, get_modules_for_injection from .. import __version__ from nose import SkipTest from nose.tools import assert_equal from nose.tools import assert_false from nose.tools import assert_true try: import mvpa2 _have_mvpa2 = True except ImportError: _have_mvpa2 = False class TestActiveInjector(object): def setup(self): self._cleanup_modules() self.due = DueCreditCollector() self.injector = DueCreditInjector(collector=self.due) self.injector.activate(retrospect=False) # numpy might be already loaded... def teardown(self): # gc might not pick up inj after some tests complete # so we will always deactivate explicitly self.injector.deactivate() assert_true(__builtin__.__import__ is _orig__import__) self._cleanup_modules() def _cleanup_modules(self): if 'duecredit.tests.mod' in sys.modules: sys.modules.pop('duecredit.tests.mod') def _test_simple_injection(self, func, import_stmt, func_call=None): assert_false('duecredit.tests.mod' in sys.modules) self.injector.add('duecredit.tests.mod', func, Doi('1.2.3.4'), description="Testing %s" % func, min_version='0.1', max_version='1.0', tags=["implementation", "very custom"]) assert_false('duecredit.tests.mod' in sys.modules) # no import happening assert_equal(len(self.due._entries), 0) assert_equal(len(self.due.citations), 0) exec(import_stmt) assert_equal(len(self.due._entries), 1) # we should get an entry now assert_equal(len(self.due.citations), 0) # but not yet a citation import duecredit.tests.mod as mod _, _, obj = find_object(mod, func) assert_true(obj.__duecredited__) # we wrapped assert_false(obj.__duecredited__ is obj) # and it is not pointing to the same func assert_equal(obj.__doc__, "custom docstring") # we preserved docstring # TODO: test decoration features -- preserver __doc__ etc exec('ret = %s(None, "somevalue")' % (func_call or func)) # XXX: awkwardly 'ret' is not found in the scope while running nosetests # under python3.4, although present in locals()... WTF? assert_equal(locals()['ret'], "%s: None, somevalue" % func) assert_equal(len(self.due._entries), 1) assert_equal(len(self.due.citations), 1) # TODO: there must be a cleaner way to get first value citation = list(viewvalues(self.due.citations))[0] # TODO: ATM we don't allow versioning of the submodules -- we should # assert_equal(citation.version, '0.5') # ATM it will be the duecredit's version assert_equal(citation.version, __version__) assert(citation.tags == ['implementation', 'very custom']) def test_simple_injection(self): yield self._test_simple_injection, "testfunc1", 'from duecredit.tests.mod import testfunc1' yield self._test_simple_injection, "TestClass1.testmeth1", \ 'from duecredit.tests.mod import TestClass1; c = TestClass1()', 'c.testmeth1' yield self._test_simple_injection, "TestClass12.Embed.testmeth1", \ 'from duecredit.tests.mod import TestClass12; c = TestClass12.Embed()', 'c.testmeth1' def test_delayed_entries(self): # verify that addition of delayed injections happened modules_for_injection = get_modules_for_injection() assert_equal(len(self.injector._delayed_injections), len(modules_for_injection)) assert_equal(self.injector._entry_records, {}) # but no entries were added assert('scipy' in self.injector._delayed_injections) # We must have it ATM try: # We do have injections for scipy import scipy except ImportError as e: raise SkipTest("scipy was not found: %s" % (e,)) def test_import_mvpa2_suite(self): if not _have_mvpa2: raise SkipTest("no mvpa2 found") # just a smoke test for now import mvpa2.suite as mv def _test_incorrect_path(self, mod, obj): ref = Doi('1.2.3.4') # none of them should lead to a failure self.injector.add(mod, obj, ref) # now cause the import handling -- it must not fail # TODO: catch/analyze warnings exec('from duecredit.tests.mod import testfunc1') def test_incorrect_path(self): yield self._test_incorrect_path, "nonexistingmodule", None yield self._test_incorrect_path, "duecredit.tests.mod.nonexistingmodule", None yield self._test_incorrect_path, "duecredit.tests.mod", "nonexisting" yield self._test_incorrect_path, "duecredit.tests.mod", "nonexisting.whocares" def _test_find_object(mod, path, parent, obj_name, obj): assert_equal(find_object(mod, path), (parent, obj_name, obj)) def test_find_object(): import duecredit.tests.mod as mod yield _test_find_object, mod, 'testfunc1', mod, 'testfunc1', mod.testfunc1 yield _test_find_object, mod, 'TestClass1', mod, 'TestClass1', mod.TestClass1 yield _test_find_object, mod, 'TestClass1.testmeth1', mod.TestClass1, 'testmeth1', mod.TestClass1.testmeth1 yield _test_find_object, mod, 'TestClass12.Embed.testmeth1', \ mod.TestClass12.Embed, 'testmeth1', mod.TestClass12.Embed.testmeth1 def test_no_double_activation(): orig__import__ = __builtin__.__import__ try: due = DueCreditCollector() injector = DueCreditInjector(collector=due) injector.activate() assert_false(__builtin__.__import__ is orig__import__) duecredited__import__ = __builtin__.__import__ # TODO: catch/analyze/swallow warning injector.activate() assert_true(__builtin__.__import__ is duecredited__import__) # we didn't decorate again finally: injector.deactivate() __builtin__.__import__ = orig__import__ def test_get_modules_for_injection(): assert_equal(get_modules_for_injection(), [ 'mod_biosig', 'mod_dipy', 'mod_mdp', 'mod_mne', 'mod_nibabel', 'mod_nipy', 'mod_nipype', 'mod_numpy', 'mod_pandas', 'mod_psychopy', 'mod_scipy', 'mod_skimage', 'mod_sklearn']) def test_cover_our_injections(): # this one tests only import/syntax/api for the injections due = DueCreditCollector() inj = DueCreditInjector(collector=due) for modname in get_modules_for_injection(): mod = __import__('duecredit.injections.' + modname, fromlist=["duecredit.injections"]) mod.inject(inj) def test_no_harm_from_deactivate(): # if we have not activated one -- shouldn't blow if we deactivate it # TODO: catch warning being spitted out DueCreditInjector().deactivate() def test_injector_del(): orig__import__ = __builtin__.__import__ try: due = DueCreditCollector() inj = DueCreditInjector(collector=due) del inj # delete inactive assert_true(__builtin__.__import__ is orig__import__) inj = DueCreditInjector(collector=due) inj.activate(retrospect=False) assert_false(__builtin__.__import__ is orig__import__) assert_false(inj._orig_import is None) del inj # delete active but not used inj = None __builtin__.__import__ = None # We need to do that since otherwise gc will not pick up inj gc.collect() # To cause __del__ assert_true(__builtin__.__import__ is orig__import__) import abc # and new imports work just fine finally: __builtin__.__import__ = orig__import__ duecredit-0.4.5.3/duecredit/tests/test_cmdline.py0000600013464101346420000000262212562736560021432 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## import sys from mock import patch from six.moves import StringIO from nose.tools import assert_raises, assert_equal from .. import __version__ from ..cmdline import main def test_import(): import duecredit.cmdline import duecredit.cmdline.main @patch('sys.stdout', new_callable=StringIO) def test_main_help(stdout): assert_raises(SystemExit, main.main, ['--help']) assert(stdout.getvalue().lstrip().startswith("Usage: ")) # differs among Python versions -- catch both @patch('sys.std' + ('err' if sys.version_info < (3, 4) else 'out'), new_callable=StringIO) def test_main_version(out): assert_raises(SystemExit, main.main, ['--version']) assert_equal((out.getvalue()).split('\n')[0], "duecredit %s" % __version__) # smoke test the cmd_summary # TODO: carry sample .duecredit.p, point to that file, mock TextOutput and BibTeXOutput .dumps def test_smoke_cmd_summary(): main.main(['summary']) # test the not implemented cmd_test def test_cmd_test(): assert_raises(SystemExit, main.main, ['test'])duecredit-0.4.5.3/duecredit/tests/test_utils.py0000600013464101346420000000226212641323734021150 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. Originates from datalad package distributed # under MIT license # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## import sys from mock import patch from ..utils import is_interactive from nose.tools import assert_false, assert_true def test_is_interactive_crippled_stdout(): class mocked_out(object): """the one which has no isatty """ def write(self, *args, **kwargs): pass class mocked_isatty(mocked_out): def isatty(self): return True for inout in ('in', 'out', 'err'): with patch('sys.std%s' % inout, mocked_out()): assert_false(is_interactive()) # just for paranoids with patch('sys.stdin', mocked_isatty()), \ patch('sys.stdout', mocked_isatty()), \ patch('sys.stderr', mocked_isatty()): assert_true(is_interactive())duecredit-0.4.5.3/duecredit/tests/mod/0000700013464101346420000000000012641331574017153 5ustar yohyoh00000000000000duecredit-0.4.5.3/duecredit/tests/mod/submod.py0000600013464101346420000000037712552503722021024 0ustar yohyoh00000000000000"""Some test submodule""" def testfunc(arg1, kwarg1=None): """testfunc docstring""" return "testfunc: %s, %s" % (arg1, kwarg1) class TestClass(object): def testmeth(self, arg1, kwarg1=None): return "testmeth: %s, %s" % (arg1, kwarg1)duecredit-0.4.5.3/duecredit/tests/mod/__init__.py0000600013464101346420000000016412555330243021263 0ustar yohyoh00000000000000"""Module to test various duecredit functionality, e.g. injections""" from .imported import * __version__ = '0.5' duecredit-0.4.5.3/duecredit/tests/mod/imported.py0000600013464101346420000000111312552503722021343 0ustar yohyoh00000000000000def testfunc1(arg1, kwarg1=None): """custom docstring""" return "testfunc1: %s, %s" % (arg1, kwarg1) class TestClass1(object): """wrong custom docstring""" def testmeth1(self, arg1, kwarg1=None): """custom docstring""" return "TestClass1.testmeth1: %s, %s" % (arg1, kwarg1) class TestClass12(object): """wrong custom docstring""" class Embed(object): """wrong custom docstring""" def testmeth1(self, arg1, kwarg1=None): """custom docstring""" return "TestClass12.Embed.testmeth1: %s, %s" % (arg1, kwarg1)duecredit-0.4.5.3/duecredit/tests/test_io.py0000600013464101346420000002200212617543660020416 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## from ..collector import DueCreditCollector, Citation from .test_collector import _sample_bibtex, _sample_doi from ..entries import BibTeX, DueCreditEntry, Doi from ..io import TextOutput, PickleOutput, import_doi, EnumeratedEntries, get_text_rendering from nose.tools import assert_equal, assert_is_instance, assert_raises, \ assert_true, assert_false from six.moves import StringIO from six import text_type from mock import patch import random import re import sys import pickle import tempfile from .test_collector import _sample_bibtex, _sample_bibtex2 try: import vcr @vcr.use_cassette() def test_import_doi(): doi_good = '10.1038/nrd842' assert_is_instance(import_doi(doi_good), text_type) doi_bad = 'fasljfdldaksj' assert_raises(ValueError, import_doi, doi_bad) except ImportError: # no vcr, and that is in 2015! pass def test_pickleoutput(): #entry = BibTeX('@article{XXX0, ...}') entry = BibTeX("@article{Atkins_2002,\n" "title=title,\n" "volume=1, \n" "url=http://dx.doi.org/10.1038/nrd842, \n" "DOI=10.1038/nrd842, \n" "number=7, \n" "journal={Nat. Rev. Drug Disc.}, \n" "publisher={Nature Publishing Group}, \n" "author={Atkins, Joshua H. and Gershell, Leland J.}, \n" "year={2002}, \n" "month={Jul}, \n" "pages={491--492}\n}") collector_ = DueCreditCollector() collector_.add(entry) collector_.cite(entry, path='module') # test it doesn't puke with an empty collector collectors = [collector_, DueCreditCollector()] for collector in collectors: with tempfile.NamedTemporaryFile() as fn: pickler = PickleOutput(collector, fn=fn.name) assert_equal(pickler.fn, fn.name) assert_equal(pickler.dump(), None) collector_loaded = pickle.load(fn) assert_equal(collector.citations.keys(), collector_loaded.citations.keys()) # TODO: implement comparison of citations assert_equal(collector._entries.keys(), collector_loaded._entries.keys()) def test_text_output(): entry = BibTeX(_sample_bibtex) collector = DueCreditCollector() collector.cite(entry, path='module') strio = StringIO() TextOutput(strio, collector).dump(tags=['*']) value = strio.getvalue() assert_true("Halchenko, Y.O." in value, msg="value was %s" % value) assert_true(value.strip().endswith("Frontiers in Neuroinformatics, 6(22).")) def test_text_output_dump_formatting(): due = DueCreditCollector() # XXX: atm just to see if it spits out stuff @due.dcite(BibTeX(_sample_bibtex), description='solution to life', path='mymodule', version='0.0.16') def mymodule(arg1, kwarg2="blah"): """docstring""" assert_equal(arg1, "magical") assert_equal(kwarg2, 1) @due.dcite(BibTeX(_sample_bibtex2), description='solution to life', path='mymodule:myfunction') def myfunction(arg42): pass myfunction('argh') return "load" # check we don't have anything output strio = StringIO() TextOutput(strio, due).dump(tags=['*']) value = strio.getvalue() assert_true('0 modules cited' in value, msg='value was {0}'.format(value)) assert_true('0 functions cited' in value, msg='value was {0}'.format(value)) # now we call it -- check it prints stuff mymodule('magical', kwarg2=1) TextOutput(strio, due).dump(tags=['*']) value = strio.getvalue() assert_true('1 packages cited' in value, msg='value was {0}'.format(value)) assert_true('1 functions cited' in value, msg='value was {0}'.format(value)) assert_true('(v 0.0.16)' in value, msg='value was {0}'.format(value)) assert_equal(len(value.split('\n')), 21, msg='value was {0}'.format(value)) # test we get the reference numbering right samples_bibtex = [_generate_sample_bibtex() for x in range(5)] # this sucks but at the moment it's the only way to have multiple # references for a function @due.dcite(BibTeX(samples_bibtex[0]), description='another solution', path='myothermodule', version='0.0.666') def myothermodule(arg1, kwarg2="blah"): """docstring""" assert_equal(arg1, "magical") assert_equal(kwarg2, 1) @due.dcite(BibTeX(samples_bibtex[1]), description='solution to life', path='myothermodule:myotherfunction') @due.dcite(BibTeX(samples_bibtex[2]), description='solution to life', path='myothermodule:myotherfunction') @due.dcite(BibTeX(samples_bibtex[3]), description='solution to life', path='myothermodule:myotherfunction') @due.dcite(BibTeX(samples_bibtex[4]), description='solution to life', path='myothermodule:myotherfunction') @due.dcite(BibTeX(_sample_bibtex2), description='solution to life', path='myothermodule:myotherfunction') def myotherfunction(arg42): pass myotherfunction('argh') return "load" myothermodule('magical', kwarg2=1) strio = StringIO() TextOutput(strio, due).dump(tags=['*']) value = strio.getvalue() lines = value.split('\n') citation_numbers = [] reference_numbers = [] references = [] for line in lines: match_citation = re.search('\[([0-9, ]+)\]$', line) match_reference = re.search('^\[([0-9])\]', line) if match_citation: citation_numbers.extend(match_citation.group(1).split(', ')) elif match_reference: reference_numbers.append(match_reference.group(1)) references.append(line.replace(match_reference.group(), "")) assert_equal(set(citation_numbers), set(reference_numbers)) assert_equal(len(set(references)), len(set(citation_numbers))) assert_equal(len(citation_numbers), 8) # verify that we have returned to previous state of filters import warnings assert_true(('ignore', None, UserWarning, None, 0) not in warnings.filters) def _generate_sample_bibtex(): """ Generate a random sample bibtex to test multiple references """ letters = 'abcdefghilmnopqrstuvxz' numbers = '0123456789' letters_numbers = letters + letters.upper() + numbers letters_numbers_spaces = letters_numbers + ' ' key = "".join(random.sample(letters_numbers, 7)) title = "".join(random.sample(letters_numbers_spaces, 20)) journal = "".join(random.sample(letters_numbers_spaces, 20)) publisher = "".join(random.sample(letters_numbers_spaces, 10)) author = "".join(random.sample(letters, 6)) + ', ' + \ "".join(random.sample(letters, 4)) year = "".join(random.sample(numbers, 4)) elements = [('title', title), ('journal', journal), ('publisher', publisher), ('author', author), ('year', year)] sample_bibtex = "@ARTICLE{%s,\n" % key for string, value in elements: sample_bibtex += "%s={%s},\n" % (string, value) sample_bibtex += "}" return sample_bibtex def test_enumeratedentries(): enumentries = EnumeratedEntries() assert_false(enumentries) # add some entries entries = [('ciao', 1), ('miao', 2), ('bau', 3)] for entry, _ in entries: enumentries.add(entry) assert_equal(len(enumentries), 3) for entry, nr in entries: assert_equal(nr, enumentries[entry]) assert_equal(entry, enumentries.fromrefnr(nr)) assert_raises(KeyError, enumentries.__getitem__, 'boh') assert_raises(KeyError, enumentries.fromrefnr, 666) assert_equal(entries, sorted(enumentries, key=lambda x: x[1])) @patch('duecredit.io.get_bibtex_rendering') @patch('duecredit.io.format_bibtex') def test_get_text_rendering(mock_format_bibtex, mock_get_bibtex_rendering): # mock get_bibtex_rendering to return the same bibtex entry sample_bibtex = BibTeX(_sample_bibtex) mock_get_bibtex_rendering.return_value = sample_bibtex # test if bibtex type is passed citation_bibtex = Citation(sample_bibtex, path='mypath') bibtex_output = get_text_rendering(citation_bibtex) mock_format_bibtex.assert_called_with(citation_bibtex.entry, style='harvard1') mock_format_bibtex.reset_mock() # test if doi type is passed citation_doi = Citation(Doi(_sample_doi), path='mypath') doi_output = get_text_rendering(citation_doi) mock_format_bibtex.assert_called_with(citation_bibtex.entry, style='harvard1') assert_equal(bibtex_output, doi_output) duecredit-0.4.5.3/duecredit/tests/envs/0000700013464101346420000000000012641331574017347 5ustar yohyoh00000000000000duecredit-0.4.5.3/duecredit/tests/envs/stubbed/0000700013464101346420000000000012641331574020777 5ustar yohyoh00000000000000duecredit-0.4.5.3/duecredit/tests/envs/stubbed/README.txt0000600013464101346420000000017412627740445022506 0ustar yohyoh00000000000000A sample script with our stub to verify that it runs without complete failure even if there are internal duecredit problems duecredit-0.4.5.3/duecredit/tests/envs/stubbed/script.py0000600013464101346420000000047712627740445022674 0ustar yohyoh00000000000000from due import due, Doi kwargs = dict( entry=Doi("10.1007/s12021-008-9041-y"), description="Multivariate pattern analysis of neural data", tags=["use"] ) import numpy as np due.cite(path="test", **kwargs) @due.dcite(**kwargs) def method(arg): return arg+1 assert(method(1) == 2) print("done123") duecredit-0.4.5.3/duecredit/tests/envs/stubbed/due.py0000777013464101346420000000000012627740445024332 2../../../stub.pyustar yohyoh00000000000000duecredit-0.4.5.3/duecredit/tests/envs/nolxml/0000700013464101346420000000000012641331574020660 5ustar yohyoh00000000000000duecredit-0.4.5.3/duecredit/tests/envs/nolxml/lxml.py0000600013464101346420000000002212627740445022207 0ustar yohyoh00000000000000raise ImportError duecredit-0.4.5.3/duecredit/tests/__init__.py0000600013464101346420000000000012251723514020471 0ustar yohyoh00000000000000duecredit-0.4.5.3/duecredit/tests/test_versions.py0000600013464101346420000000620712601033155021652 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## from os import linesep from ..version import __version__ from ..versions import ExternalVersions from nose.tools import assert_true, assert_false from nose.tools import assert_equal, assert_greater_equal, assert_greater from nose.tools import assert_raises from nose import SkipTest from six import PY3 if PY3: # just to ease testing def cmp(a, b): return (a > b) - (a < b) def test_external_versions_basic(): ev = ExternalVersions() assert_equal(ev._versions, {}) assert_equal(ev['duecredit'], __version__) # and it could be compared assert_greater_equal(ev['duecredit'], __version__) assert_greater(ev['duecredit'], '0.1') assert_equal(list(ev.keys()), ['duecredit']) assert_true('duecredit' in ev) assert_false('unknown' in ev) assert_equal(ev.dumps(), "Versions: duecredit=%s" % __version__) # For non-existing one we get None assert_equal(ev['duecreditnonexisting'], None) # and nothing gets added to _versions for nonexisting assert_equal(set(ev._versions.keys()), {'duecredit'}) # but if it is a module without version, we get it set to UNKNOWN assert_equal(ev['os'], ev.UNKNOWN) # And get a record on that inside assert_equal(ev._versions.get('os'), ev.UNKNOWN) # And that thing is "True", i.e. present assert(ev['os']) # but not comparable with anything besides itself (was above) assert_raises(TypeError, cmp, ev['os'], '0') assert_raises(TypeError, assert_greater, ev['os'], '0') # And we can get versions based on modules themselves from duecredit.tests import mod assert_equal(ev[mod], mod.__version__) # Check that we can get a copy of the verions versions_dict = ev.versions versions_dict['duecredit'] = "0.0.1" assert_equal(versions_dict['duecredit'], "0.0.1") assert_equal(ev['duecredit'], __version__) def test_external_versions_unknown(): assert_equal(str(ExternalVersions.UNKNOWN), 'UNKNOWN') def test_external_versions_popular_packages(): ev = ExternalVersions() def _test_external(modname): try: exec("import %s" % modname, locals(), globals()) except ImportError: raise SkipTest("External %s not present" % modname) except Exception as e: raise SkipTest("External %s fails to import: %s" % (modname, e)) assert(ev[modname] is not ev.UNKNOWN) assert_greater(ev[modname], '0.0.1') assert_greater('1000000.0', ev[modname]) # unlikely in our lifetimes for modname in ('scipy', 'numpy', 'mvpa2', 'sklearn', 'statsmodels', 'pandas', 'matplotlib', 'psychopy'): yield _test_external, modname # more of a smoke test assert_false(linesep in ev.dumps()) assert_true(ev.dumps(indent=True).endswith(linesep))duecredit-0.4.5.3/duecredit/tests/test_dueswitch.py0000600013464101346420000000230712562736560022016 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## from mock import patch import atexit from nose import SkipTest from ..injections.injector import DueCreditInjector from ..dueswitch import due @patch.object(DueCreditInjector, 'activate') @patch.object(atexit, 'register') def test_dueswitch_activate(mock_register, mock_activate): was_active = due.active # atexit.register(crap) # injector.activate() due.activate() if was_active: # we can only test that mocked methods do not invoked second time mock_activate.assert_not_called() mock_register.assert_not_called() raise SkipTest("due is already active, can't test more at this point") # was not active, so should have called activate of the injector class mock_activate.assert_called_once_with() mock_register.assert_called_once_with(due._dump_collector_summary) duecredit-0.4.5.3/duecredit/tests/test_api.py0000600013464101346420000001165412627740445020574 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## from duecredit.collector import DueCreditCollector from duecredit.stub import InactiveDueCreditCollector from duecredit.entries import BibTeX, Doi from nose.tools import assert_equal from nose.tools import assert_in from nose import SkipTest def _test_api(due): # add references due.add(BibTeX('@article{XXX00, ...}')) # could even be by DOI -- we need to fetch and cache those due.add(Doi("xxx.yyy/zzz.1", key="XXX01")) # and/or load multiple from a file due.load('/home/siiioul/deep/good_intentions.bib') # Cite entire module due.cite('XXX00', description="Answers to existential questions", path="module") # Cita some method within some submodule due.cite('XXX01', description="More answers to existential questions", path="module.submodule:class1.whoknowswhat2.func1") # dcite for decorator cite # cite specific functionality if/when it gets called up @due.dcite('XXX00', description="Provides an answer for meaningless existence") def purpose_of_life(): return None class Child(object): # Conception process is usually way too easy to be referenced def __init__(self): pass # including functionality within/by the methods @due.dcite('XXX00') def birth(self, gender): return "Rachel was born" kid = Child() kid.birth("female") def test_api(): yield _test_api, DueCreditCollector() yield _test_api, InactiveDueCreditCollector() import os import sys from os.path import dirname, join as pathjoin, pardir, normpath from mock import patch from subprocess import Popen, PIPE badlxml_path = pathjoin(dirname(__file__), 'envs', 'nolxml') stubbed_script = pathjoin(dirname(__file__), 'envs', 'stubbed', 'script.py') def run_python_command(cmd=None, script=None): """Just a tiny helper which runs command and returns exit code, stdout, stderr""" assert(bool(cmd) != bool(script)) # one or another, not both args = ['-c', cmd] if cmd else [script] python = Popen([sys.executable] + args, stdout=PIPE, stderr=PIPE) ret = python.wait() return ret, python.stdout.read().decode(), python.stderr.read().decode() mock_env_nolxml = {'PYTHONPATH': "%s:%s" % (badlxml_path, os.environ.get('PYTHONPATH', ''))} # Since duecredit and possibly lxml already loaded, let's just test # ability to import in absence of lxml via external call to python def test_noincorrect_import_if_no_lxml(): with patch.dict(os.environ, mock_env_nolxml): # make sure out mocking works here ret, out, err = run_python_command('import lxml') assert_equal(ret, 1) assert_in('ImportError', err) # # make sure out mocking works here ret, out, err = run_python_command('import duecredit') assert_equal(err, '') assert_equal(out, '') assert_equal(ret, 0) def check_noincorrect_import_if_no_lxml_numpy(kwargs, env): # Now make sure that we would not crash entire process at the end when unable to # produce sensible output when we have something to cite # we do inject for numpy try: import numpy except ImportError: raise SkipTest("We need to have numpy to test correct operation") mock_env_nolxml_ = mock_env_nolxml.copy() mock_env_nolxml_.update(env) with patch.dict(os.environ, mock_env_nolxml_): ret, out, err = run_python_command(**kwargs) assert_equal(err, '') if os.environ.get('DUECREDIT_ENABLE', False): # we enabled duecredit assert_in('For formatted output we need citeproc', out) assert_in('done123', out) elif os.environ.get('DUECREDIT_TEST_EARLY_IMPORT_ERROR'): assert_in('ImportError', out) assert_in('DUECREDIT_TEST_EARLY_IMPORT_ERROR', out) assert_in('done123', out) else: assert_equal('done123\n', out) assert_equal(ret, 0) # but we must not fail overall regardless def test_noincorrect_import_if_no_lxml_numpy(): for kwargs in ( # direct command to evaluate {'cmd': 'import duecredit; import numpy as np; print("done123")'}, # script with decorated funcs etc -- should be importable {'script': stubbed_script} ): yield check_noincorrect_import_if_no_lxml_numpy, kwargs, {} yield check_noincorrect_import_if_no_lxml_numpy, kwargs, {'DUECREDIT_ENABLE': 'yes'} yield check_noincorrect_import_if_no_lxml_numpy, kwargs, {'DUECREDIT_TEST_EARLY_IMPORT_ERROR': 'yes'} if __name__ == '__main__': from duecredit import due _test_api(due)duecredit-0.4.5.3/duecredit/tests/test_collector.py0000600013464101346420000002523012572050172021772 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## from ..collector import DueCreditCollector, InactiveDueCreditCollector, \ CollectorSummary, Citation from ..entries import BibTeX, Doi from ..io import PickleOutput from mock import patch from nose.tools import assert_equal, assert_is_instance, assert_raises, assert_true from nose.tools import assert_false import os import tempfile def _test_entry(due, entry): due.add(entry) _sample_bibtex = """ @ARTICLE{XXX0, author = {Halchenko, Yaroslav O. and Hanke, Michael}, title = {Open is not enough. Let{'}s take the next step: An integrated, community-driven computing platform for neuroscience}, journal = {Frontiers in Neuroinformatics}, year = {2012}, volume = {6}, number = {00022}, doi = {10.3389/fninf.2012.00022}, issn = {1662-5196}, localfile = {HH12.pdf}, } """ _sample_bibtex2 = """ @ARTICLE{Atkins_2002, title = {title}, volume = {666}, url = {http://dx.doi.org/10.1038/nrd842}, DOI = {10.1038/nrd842}, number = {3009}, journal = {My Fancy. Journ.}, publisher = {The Publisher}, author = {Atkins, Joshua H. and Gershell, Leland J.}, year = {2002}, month = {Jul}, } """ _sample_doi = "10.3389/fninf.2012.00022" def test_citation_paths(): entry = BibTeX(_sample_bibtex) cit1 = Citation(entry, path="somemodule") assert_true(cit1.cites_module) assert_equal(cit1.module, "somemodule") cit2 = Citation(entry, path="somemodule.submodule") assert_true(cit2.cites_module) assert_equal(cit2.module, "somemodule.submodule") assert_true(cit1 in cit1) assert_true(cit2 in cit1) assert_false(cit1 in cit2) cit3 = Citation(entry, path="somemodule.submodule:class2.func2") assert_false(cit3.cites_module) assert_equal(cit3.module, "somemodule.submodule") assert_true(cit2 in cit1) assert_true(cit3 in cit1) assert_true(cit3 in cit2) assert_false(cit2 in cit3) cit4 = Citation(entry, path="somemodule2:class2.func2") assert_false(cit4.cites_module) assert_equal(cit4.module, "somemodule2") assert_false(cit1 in cit4) assert_false(cit4 in cit1) def test_entry(): entry = BibTeX(_sample_bibtex) yield _test_entry, DueCreditCollector(), entry entries = [BibTeX(_sample_bibtex), BibTeX(_sample_bibtex), Doi(_sample_doi)] yield _test_entry, DueCreditCollector(), entries def _test_dcite_basic(due, callable): assert_equal(callable("magical", 1), "load") # verify that @wraps correctly passes all the docstrings etc assert_equal(callable.__name__, "method") assert_equal(callable.__doc__, "docstring") def test_dcite_method(): # Test basic wrapping that we don't mask out the arguments for due in [DueCreditCollector(), InactiveDueCreditCollector()]: active = isinstance(due, DueCreditCollector) due.add(BibTeX(_sample_bibtex)) @due.dcite("XXX0", path='method') def method(arg1, kwarg2="blah"): """docstring""" assert_equal(arg1, "magical") assert_equal(kwarg2, 1) return "load" class SomeClass(object): @due.dcite("XXX0", path='someclass:method') def method(self, arg1, kwarg2="blah"): """docstring""" assert_equal(arg1, "magical") assert_equal(kwarg2, 1) return "load" if active: assert_equal(due.citations, {}) assert_equal(len(due._entries), 1) yield _test_dcite_basic, due, method if active: assert_equal(len(due.citations), 1) assert_equal(len(due._entries), 1) citation = due.citations[("method", "XXX0")] assert_equal(citation.count, 1) # TODO: this is probably incomplete path but unlikely we would know # any better assert_equal(citation.path, "method") instance = SomeClass() yield _test_dcite_basic, due, instance.method if active: assert_equal(len(due.citations), 2) assert_equal(len(due._entries), 1) # TODO: we should actually get path/counts pairs so here citation = due.citations[("someclass:method", "XXX0")] assert_equal(citation.path, "someclass:method") assert_equal(citation.count, 1) # And we explicitly stated that module need to be cited assert_true(citation.cite_module) class SomeClass2(object): @due.dcite("XXX0", path="some.module.without.method") def method2(self, arg1, kwarg2="blah"): assert_equal(arg1, "magical") return "load" # and a method pointing to the module instance2 = SomeClass() yield _test_dcite_basic, due, instance2.method if active: assert_equal(len(due.citations), 2) # different paths assert_equal(len(due._entries), 1) # the same entry # TODO: we should actually get path/counts pairs so here # it is already a different path # And we still explicitly stated that module need to be cited assert_true(citation.cite_module) def _test_args_match_conditions(conds): args_match_conditions = DueCreditCollector._args_match_conditions assert_true(args_match_conditions(conds)) assert_true(args_match_conditions(conds, None)) assert_true(args_match_conditions(conds, someirrelevant=True)) assert_true(args_match_conditions(conds, method='purge')) assert_true(args_match_conditions(conds, method='fullpurge')) assert_true(args_match_conditions(conds, None, 'purge')) assert_true(args_match_conditions(conds, None, 'fullpurge')) assert_true(args_match_conditions(conds, None, 'fullpurge', someirrelevant="buga")) assert_false(args_match_conditions(conds, None, 'push')) assert_false(args_match_conditions(conds, method='push')) if len(conds) < 2: return # got compound case assert_true(args_match_conditions(conds, scope='life')) assert_false(args_match_conditions(conds, scope='someother')) # should be "and", so if one not matching -- both not matchin assert_false(args_match_conditions(conds, method="wrong", scope='life')) assert_false(args_match_conditions(conds, method="purge", scope='someother')) #assert_true(args_match_conditions(conds, None, None, 'life')) # ambigous/conflicting def test_args_match_conditions(): yield _test_args_match_conditions, {(1, 'method'): {'purge', 'fullpurge', 'DC_DEFAULT'}} yield _test_args_match_conditions, {(1, 'method'): {'purge', 'fullpurge', 'DC_DEFAULT'}, (2, 'scope'): {'life', 'DC_DEFAULT'}} def _test_dcite_match_conditions(due, callable, path): assert_equal(due.citations, {}) assert_equal(len(due._entries), 1) assert_equal(callable("magical", "unknown"), "load unknown") assert_equal(due.citations, {}) assert_equal(len(due._entries), 1) assert_equal(callable("magical"), "load blah") assert_equal(len(due.citations), 1) assert_equal(len(due._entries), 1) entry = due._entries['XXX0'] assert_equal(due.citations[(path, 'XXX0')].count, 1) # Cause the same citation assert_equal(callable("magical", "blah"), "load blah") # Nothing should change assert_equal(len(due.citations), 1) assert_equal(len(due._entries), 1) assert_equal(due.citations[(path, 'XXX0')].count, 2) # Besides the count # Now cause new citation given another value assert_equal(callable("magical", "boo"), "load boo") assert_equal(len(due.citations), 2) assert_equal(len(due._entries), 2) assert_equal(due.citations[(path, 'XXX0')].count, 2) # Count should stay the same for XXX0 assert_equal(due.citations[(path, "10.3389/fninf.2012.00022")].count, 1) # but we get a new one def test_dcite_match_conditions_function(): due = DueCreditCollector() due.add(BibTeX(_sample_bibtex)) @due.dcite("XXX0", path='callable', conditions={(1, "kwarg2"): {"blah", "DC_DEFAULT"}}) @due.dcite(Doi(_sample_doi), path='callable', conditions={(1, "kwarg2"): {"boo"}}) def method(arg1, kwarg2="blah"): """docstring""" assert_equal(arg1, "magical") return "load %s" % kwarg2 _test_dcite_match_conditions(due, method, 'callable') def test_dcite_match_conditions_method(): due = DueCreditCollector() due.add(BibTeX(_sample_bibtex)) class Citeable(object): def __init__(self, param=None): self.param = param @due.dcite("XXX0", path='obj.callable', conditions={(2, "kwarg2"): {"blah", "DC_DEFAULT"}, (0, 'self.param'): {"paramvalue"} # must be matched }) @due.dcite(Doi(_sample_doi), path='obj.callable', conditions={(2, "kwarg2"): {"boo"}}) def method(self, arg1, kwarg2="blah"): """docstring""" assert_equal(arg1, "magical") return "load %s" % kwarg2 citeable = Citeable(param="paramvalue") _test_dcite_match_conditions(due, citeable.method, 'obj.callable') # now test for self.param - def test_get_output_handler_method(): with patch.dict(os.environ, {'DUECREDIT_OUTPUTS': 'pickle'}): entry = BibTeX(_sample_bibtex) collector = DueCreditCollector() collector.cite(entry, path='module') with tempfile.NamedTemporaryFile() as f: summary = CollectorSummary(collector, fn=f.name) handlers = [summary._get_output_handler(type_, collector) for type_ in ['pickle']] #assert_is_instance(handlers[0], TextOutput) assert_is_instance(handlers[0], PickleOutput) assert_raises(NotImplementedError, summary._get_output_handler, 'nothing', collector) def test_collectors_uniform_API(): get_api = lambda obj: [x for x in sorted(dir(obj)) if not x.startswith('_') or x in ('__call__')] assert_equal(get_api(DueCreditCollector), get_api(InactiveDueCreditCollector)) def _test__docs__(method): assert("entry:" in method.__doc__) assert("tags: " in method.__doc__) def test__docs__(): yield _test__docs__, DueCreditCollector.cite yield _test__docs__, DueCreditCollector.dcite yield _test__docs__, Citation.__init__ duecredit-0.4.5.3/duecredit/__main__.py0000600013464101346420000000554712562736560017347 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Helper to use duecredit as a "runnable" module with -m duecredit""" import sys from . import due, __version__ from .log import lgr def usage(outfile, executable=sys.argv[0]): if '__main__.py' in executable: # That was -m duecredit way to launch executable = "%s -m duecredit" % sys.executable outfile.write("""Usage: %s [OPTIONS] [ARGS] Meta-options: --help Display this help then exit. --version Output version information then exit. """ % executable) def runctx(cmd, globals=None, locals=None): if globals is None: globals = {} if locals is None: locals = {} try: exec(cmd, globals, locals) finally: # good opportunity to avoid atexit I guess. pass for now pass def main(argv=None): import os import getopt if argv is None: argv = sys.argv try: opts, prog_argv = getopt.getopt(argv[1:], "", ["help", "version"]) # TODO: support options for whatever we would support ;) # probably needs to hook in somehow into commands/options available # under cmdline/ except getopt.error as msg: sys.stderr.write("%s: %s\n" % (sys.argv[0], msg)) sys.stderr.write("Try `%s --help' for more information\n" % sys.argv[0]) sys.exit(1) # and now we need to execute target script "manually" # Borrowing up on from trace.py for opt, val in opts: if opt == "--help": usage(sys.stdout, executable=argv[0]) sys.exit(0) if opt == "--version": sys.stdout.write("duecredit %s\n" % __version__) sys.exit(0) sys.argv = prog_argv progname = prog_argv[0] sys.path[0] = os.path.split(progname)[0] try: with open(progname) as fp: code = compile(fp.read(), progname, 'exec') # try to emulate __main__ namespace as much as possible globs = { '__file__': progname, '__name__': '__main__', '__package__': None, '__cached__': None, } # Since used explicitly -- activate the beast due.activate(True) runctx(code, globs, globs) # TODO: see if we could hide our presence from the final tracebacks if execution fails except IOError as err: lgr.error("Cannot run file %r because: %s" % (sys.argv[0], err)) sys.exit(1) except SystemExit: pass if __name__ == '__main__': main()duecredit-0.4.5.3/duecredit/stub.py0000600013464101346420000000337612627740445016601 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """ Stub file for a guaranteed safe import of duecredit constructs: if duecredit is not available. To use it, just place it into your project codebase to be imported, e.g. copy as cp stub.py /path/tomodule/module/due.py Note that it might be better to avoid naming it duecredit.py to avoid shadowing installed duecredit. Then use in your code as from .due import due, Doi, BibTeX Examples -------- TODO License: Originally a part of the duecredit, which is distributed under BSD-2 license. """ __version__ = '0.0.4' class InactiveDueCreditCollector(object): """Just a stub at the Collector which would not do anything""" def _donothing(self, *args, **kwargs): """Perform no good and no bad""" pass def dcite(self, *args, **kwargs): """If I could cite I would""" def nondecorating_decorator(func): return func return nondecorating_decorator cite = load = add = _donothing def __repr__(self): return self.__class__.__name__ + '()' def _donothing_func(*args, **kwargs): """Perform no good and no bad""" pass try: from duecredit import * if 'due' in locals() and not hasattr(due, 'cite'): raise RuntimeError("Imported due lacks .cite. DueCredit is now disabled") except Exception as e: if type(e).__name__ != 'ImportError': import logging logging.getLogger("duecredit").error( "Failed to import duecredit due to %s" % str(e)) # Initiate due stub due = InactiveDueCreditCollector() BibTeX = Doi = Url = _donothing_func duecredit-0.4.5.3/duecredit/versions.py0000600013464101346420000000753012602301000017435 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Module to help maintain a registry of versions for external modules etc """ import sys from os import linesep from six import string_types from distutils.version import StrictVersion, LooseVersion # To depict an unknown version, which can't be compared by mistake etc class UnknownVersion: """For internal use """ def __str__(self): return "UNKNOWN" def __cmp__(self, other): if other is self: return 0 raise TypeError("UNKNOWN version is not comparable") class ExternalVersions(object): """Helper to figure out/use versions of the external modules. It maintains a dictionary of `distuil.version.StrictVersion`s to make comparisons easy. If version string doesn't conform the StrictVersion LooseVersion will be used. If version can't be deduced for the module, 'None' is assigned """ UNKNOWN = UnknownVersion() def __init__(self): self._versions = {} @classmethod def _deduce_version(klass, module): version = None for attr in ('__version__', 'version'): if hasattr(module, attr): version = getattr(module, attr) break if isinstance(version, tuple) or isinstance(version, list): # Generate string representation version = ".".join(str(x) for x in version) if version: try: return StrictVersion(version) except ValueError: # let's then go with Loose one return LooseVersion(version) else: return klass.UNKNOWN def __getitem__(self, module): # when ran straight in its source code -- fails to discover nipy's version.. TODO #if module == 'nipy': # import pdb; pdb.set_trace() if not isinstance(module, string_types): modname = module.__name__ else: modname = module module = None if modname not in self._versions: if module is None: if modname not in sys.modules: try: module = __import__(modname) except ImportError: return None else: module = sys.modules[modname] self._versions[modname] = self._deduce_version(module) return self._versions.get(modname, self.UNKNOWN) def keys(self): """Return names of the known modules""" return self._versions.keys() def __contains__(self, item): return item in self._versions @property def versions(self): """Return dictionary (copy) of versions""" return self._versions.copy() def dumps(self, indent=False, preamble="Versions:"): """Return listing of versions as a string Parameters ---------- indent: bool or str, optional If set would instruct on how to indent entries (if just True, ' ' is used). Otherwise returned in a single line preamble: str, optional What preamble to the listing to use """ if indent and (indent is True): indent = ' ' items = ["%s=%s" % (k, self._versions[k]) for k in sorted(self._versions)] out = "%s" % preamble if indent: out += (linesep + indent).join([''] + items) + linesep else: out += " " + ' '.join(items) return out external_versions = ExternalVersions() duecredit-0.4.5.3/duecredit/collector.py0000600013464101346420000004216212627710636017605 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Citation and citations Collector classes""" import os import sys from functools import wraps from six import iteritems, itervalues from .config import DUECREDIT_FILE from .entries import DueCreditEntry from .stub import InactiveDueCreditCollector from .io import TextOutput, PickleOutput from .utils import never_fail, borrowdoc from .versions import external_versions from collections import namedtuple import logging lgr = logging.getLogger('duecredit.collector') CitationKey = namedtuple('CitationKey', ['path', 'entry_key']) class Citation(object): """Encapsulates citations and information on their use""" def __init__(self, entry, description=None, path=None, version=None, cite_module=False, tags=['implementation']): """Cite a reference Parameters ---------- entry: str or DueCreditEntry The entry to use, either identified by its id or a new one (to be added) description: str, optional Description of what this functionality provides path: str Path to the object which this citation associated with. Format is "module[.submodules][:[class.]method]", i.e. ":" is used to separate module path from the path within the module. version: str or tuple, version Version of the beast (e.g. of the module) where applicable cite_module: bool, optional If it is a module citation, setting it to True would make that module citeable even without internal duecredited functionality invoked. Should be used only for core packages whenever it is reasonable to assume that its import constitute its use (e.g. numpy) tags: list of str, optional Tags to associate with the given code/reference combination. Some tags have associated semantics in duecredit, e.g. - "implementation" [default] tag describes as an implementation of the cited method - "reference-implementation" tag describes as the original implementation (ideally by the authors of the paper) of the cited method - "another-implementation" tag describes some other implementation of the method - "use" tag points to publications demonstrating a worthwhile noting use the method - "edu" references to tutorials, textbooks and other materials useful to learn more - "donate" should be commonly used with Url entries to point to the websites describing how to contribute some funds to the referenced project """ if path is None: raise ValueError('Must specify path') self._entry = entry self._description = description # We might want extract all the relevant functionality into a separate class self._path = path self._cite_module = cite_module self.tags = tags or [] self.version = version self.count = 0 def __repr__(self): args = [repr(self._entry)] if self._description: args.append("description={0}".format(repr(self._description))) if self._path: args.append("path={0}".format(repr(self._path))) if self._cite_module: args.append("cite_module={0}".format(repr(self._cite_module))) if args: args = ", ".join(args) else: args = "" return self.__class__.__name__ + '({0})'.format(args) @property def path(self): return self._path @property def cite_module(self): return self._cite_module @path.setter def path(self, path): # TODO: verify value, if we are not up for it -- just make _path public self._path = path @property def entry(self): return self._entry @property def description(self): return self._description @property def cites_module(self): if not self.path: return None return not (':' in self.path) @property def module(self): if not self.path: return None return self.path.split(':', 1)[0] @property def package(self): module = self.module if not module: return None return module.split('.', 1)[0] @property def objname(self): if not self.path: return None spl = self.path.split(':', 1) if len(spl) > 1: return spl[1] else: return None def __contains__(self, entry): """Checks if provided entry 'contained' in this one given its path If current entry is associated with a module, contained will be an entry of - the same module - submodule of the current module or function within If current entry is associated with a specific function/class, it can contain another entry if it really contains it as an attribute """ if self.cites_module: return ((self.path == entry.path) or (entry.path.startswith(self.path + '.')) or (entry.path.startswith(self.path + ':'))) else: return entry.path.startswith(self.path + '.') @property def key(self): return CitationKey(self.path, self.entry.get_key()) @staticmethod def get_key(path, entry_key): return CitationKey(path, entry_key) def set_entry(self, newentry): self._entry = newentry class DueCreditCollector(object): """Collect the references The mighty beast which will might become later a proxy on the way to talk to a real collector Parameters ---------- entries : list of DueCreditEntry, optional List of reference items (BibTeX, Doi, etc) known to the collector citations : list of Citation, optional List of citations -- associations between references and particular code, with a description for its use, tags etc """ # TODO? rename "entries" to "references"? or "references" is closer to "citations" def __init__(self, entries=None, citations=None): self._entries = entries or {} self.citations = citations or {} @never_fail def add(self, entry): """entry should be a DueCreditEntry object""" if isinstance(entry, list): for e in entry: self.add(e) else: key = entry.get_key() self._entries[key] = entry lgr.log(1, "Collector added entry %s", key) @never_fail def load(self, src): """Loads references from a file or other recognizable source ATM supported only - .bib files """ # raise NotImplementedError if isinstance(src, str): if src.endswith('.bib'): self._load_bib(src) else: raise NotImplementedError('Format not yet supported') else: raise ValueError('Must be a string') def _load_bib(self, src): lgr.debug("Loading %s" % src) # # TODO: figure out what would be the optimal use for the __call__ # def __call__(self, *args, **kwargs): # # TODO: how to determine and cite originating module??? # # - we could use inspect but many people complain # # that it might not work with other Python # # implementations # pass # raise NotImplementedError @never_fail @borrowdoc(Citation, "__init__") def cite(self, entry, **kwargs): # TODO: if cite is invoked but no path is provided -- we must figure it out # I guess from traceback, otherwise how would we know later to associate it # with modules??? path = kwargs.get('path', None) if path is None: raise ValueError('path must be provided') if isinstance(entry, DueCreditEntry): # new one -- add it self.add(entry) entry_ = self._entries[entry.get_key()] else: entry_ = self._entries[entry] entry_key = entry_.get_key() citation_key = Citation.get_key(path=path, entry_key=entry_key) try: citation = self.citations[citation_key] except KeyError: self.citations[citation_key] = citation = Citation(entry_, **kwargs) assert(citation.key == citation_key) # update citation count citation.count += 1 # TODO: theoretically version shouldn't differ if we don't preload previous results if not citation.version: version = kwargs.get('version', None) if not version and citation.path: modname = citation.path.split('.', 1)[0] if '.' in modname: package = modname.split('.', 1)[0] else: package = modname # package_loaded = sys.modules.get(package) # if package_loaded: # # find the citation for that module # for citation in itervalues(self.citations): # if citation.package == package \ # and not citation.version: version = external_versions[package] citation.version = version return citation def _citations_fromentrykey(self): """Return a dictionary with the current citations indexed by the entry key""" citations_key = dict() for (path, entry_key), citation in iteritems(self.citations): if entry_key not in citations_key: citations_key[entry_key] = citation return citations_key @staticmethod def _args_match_conditions(conditions, *fargs, **fkwargs): """Helper to identify when to trigger citation given parameters to the function call """ for (argpos, kwarg), values in iteritems(conditions): # main logic -- assess default and if get to the next one if # given argument is not present if not ((len(fargs) > argpos) or (kwarg in fkwargs)): if not ('DC_DEFAULT' in values): # if value was specified but not provided and not default # conditions are not satisfied return False continue # "extract" the value. Must be defined here value = "__duecredit_magical_undefined__" if len(fargs) > argpos: value = fargs[argpos] if kwarg in fkwargs: value = fkwargs[kwarg] assert(value != "__duecredit_magical_undefined__") if '.' in kwarg: # we were requested to condition based on the value of the attribute # of the value. So get to the attribute(s) value for attr in kwarg.split('.')[1:]: value = getattr(value, attr) # Value is present but not matching if not (value in values): return False # if checks passed -- we must have matched conditions return True @never_fail @borrowdoc(Citation, "__init__", replace="PLUGDOCSTRING") def dcite(self, *args, **kwargs): """Decorator for references. PLUGDOCSTRING Parameters ---------- conditions: dict, optional If reference should be cited whenever parameters to the function call satisfy given values (all of the specified). Each key in the dictionary is a 2 element tuple with first element, integer, pointing to a position of the argument in the original function call signature, while second provides the name, thus if used as a keyword argument. Use "DC_DEFAULT" keyword as a value to depict default value (e.g. if no explicit value was provided for that positional or keyword argument). If "keyword argument" is of the form "obj.attr1.attr2", then actual value for comparison would be taken by extracting attr1 (and then attr2) attributes from the provided value. So, if desired to condition of the state of the object, you can use `(0, "self.attr1") : {...values...}` Examples -------- >>> from duecredit import due >>> @due.dcite('XXX00', description="Provides an answer for meaningless existence") ... def purpose_of_life(): ... return None Conditional citation given argument to the function >>> @due.dcite('XXX00', description="Relief through the movement", ... conditions={(1, 'method'): {'purge', 'DC_DEFAULT'}}) ... @due.dcite('XXX01', description="Relief through the drug treatment", ... conditions={(1, 'method'): {'drug'}}) ... def relief(x, method='purge'): ... if method == 'purge': return "crap" ... elif method == 'drug': return "swallow" >>> relief("doesn't matter") 'crap' Conditional based on the state of the object >>> class Citeable(object): ... def __init__(self, param=None): ... self.param = param ... @due.dcite('XXX00', description="The same good old relief", ... conditions={(0, 'self.param'): {'magic'}}) ... def __call__(self, data): ... return sum(data) >>> Citeable('magic')([1, 2]) 3 """ def func_wrapper(func): conditions = kwargs.pop('conditions', {}) path = kwargs.get('path', None) if not path: # deduce path from the actual function which was decorated # TODO: must include class name but can't !!!??? modname = func.__module__ path = kwargs['path'] = '%s:%s' % (modname, func.__name__) else: # TODO: we indeed need to separate path logic outside modname = path.split(':', 1)[0] # if decorated function was invoked, it means that we need # to cite that even if it is a module. But do not override # value if user explicitely stated if 'cite_module' not in kwargs: kwargs['cite_module'] = True # TODO: might make use of inspect.getmro # see e.g. # http://stackoverflow.com/questions/961048/get-class-that-defined-method lgr.debug("Decorating func %s within module %s" % (func.__name__, modname)) # TODO: unittest for all the __version__ madness # TODO: check if we better use wrapt module which provides superior "correctness" # of decorating. vcrpy uses wrapt, and that thing seems to wrap @wraps(func) def cite_wrapper(*fargs, **fkwargs): try: if not conditions \ or self._args_match_conditions(conditions, *fargs, **fkwargs): citation = self.cite(*args, **kwargs) except Exception as e: lgr.warning("Failed to cite due to %s" % (e,)) return func(*fargs, **fkwargs) cite_wrapper.__duecredited__ = func return cite_wrapper return func_wrapper @never_fail def __repr__(self): args = [] if self.citations: args.append("citations={0}".format(repr(self.citations))) if self._entries: args.append("entries={0}".format(repr(self._entries))) if args: args = ", ".join(args) else: args = "" return self.__class__.__name__ + '({0})'.format(args) @never_fail def __str__(self): return self.__class__.__name__ + \ ' {0:d} entries, {1:d} citations'.format( len(self._entries), len(self.citations)) # TODO: redo heavily -- got very messy class CollectorSummary(object): """A helper which would take care about exporting citations upon its Death """ def __init__(self, collector, outputs="stdout,pickle", fn=DUECREDIT_FILE): self._due = collector self.fn = fn # for now decide on output "format" right here self._outputs = [ self._get_output_handler( type_.lower().strip(), collector, fn=fn) for type_ in os.environ.get('DUECREDIT_OUTPUTS', outputs).split(',') if type_ ] @staticmethod def _get_output_handler(type_, collector, fn=None): # just a little factory if type_ in ("stdout", "stderr"): return TextOutput(getattr(sys, type_), collector) elif type_ == "pickle": return PickleOutput(collector, fn=fn) else: raise NotImplementedError() def dump(self): for output in self._outputs: output.dump() # TODO: provide HTML, MD, RST etc formattings duecredit-0.4.5.3/duecredit/config.py0000600013464101346420000000112512552503722017047 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Configuration handling for duecredit""" import os # For now just hardcoded variables CACHE_DIR = os.path.expanduser(os.path.join('~', '.cache', 'duecredit', 'bibtex')) DUECREDIT_FILE = '.duecredit.p' duecredit-0.4.5.3/duecredit/dueswitch.py0000600013464101346420000001035212627726754017622 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Provides an adapter to switch between two (active, inactive) collectors """ import os import atexit from .log import lgr from .utils import never_fail def _get_duecredit_enable(): env_enable = os.environ.get('DUECREDIT_ENABLE', 'no') if not env_enable.lower() in ('0', '1', 'yes', 'no', 'true', 'false'): lgr.warning("Misunderstood value %s for DUECREDIT_ENABLE. " "Use 'yes' or 'no', or '0' or '1'") return env_enable.lower() in ('1', 'yes', 'true') @never_fail def _get_inactive_due(): # keeping duplicate but separate so later we could even place it into a separate # submodule to possibly minimize startup time impact even more from .collector import InactiveDueCreditCollector return InactiveDueCreditCollector() @never_fail def _get_active_due(): from .config import CACHE_DIR, DUECREDIT_FILE from duecredit.collector import CollectorSummary, DueCreditCollector from .io import load_due import atexit # where to cache bibtex entries if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR) # TODO: this needs to move to atexit handling, that we load previous # one and them merge with new ones. Informative bits could be -- how # many new citations we got if os.path.exists(DUECREDIT_FILE): try: due_ = load_due(DUECREDIT_FILE) except Exception as e: lgr.warning("Failed to load previously collected %s. " "DueCredit will not be active for this session." % DUECREDIT_FILE) return _get_inactive_due() else: due_ = DueCreditCollector() return due_ class DueSwitch(object): """Adapter between two types of collectors -- Inactive and Active Once activated though, cannot be fully deactivated since it would inject duecredit decorators and register an event atexit. """ def __init__(self, inactive, active, activate=False): self.__active = None self.__collectors = {False: inactive, True: active} self.__activations_done = False self.activate(activate) @property def active(self): return self.__active @never_fail def _dump_collector_summary(self): from duecredit.collector import CollectorSummary due_summary = CollectorSummary(self.__collectors[True]) due_summary.dump() def __prepare_exit_and_injections(self): # Wrapper to create and dump summary... passing method doesn't work: # probably removes instance too early atexit.register(self._dump_collector_summary) # Deal with injector from .injections import DueCreditInjector injector = DueCreditInjector(collector=self.__collectors[True]) injector.activate() @never_fail def activate(self, activate=True): # 1st step -- if activating/deactivating switch between the two collectors if self.__active is not activate: # we need to switch the state is_public = lambda x: not x.startswith('_') # Clean up current bindings first for k in filter(is_public, dir(self)): if k not in ('activate', 'active'): delattr(self, k) new_due = self.__collectors[activate] for k in filter(is_public, dir(new_due)): setattr(self, k, getattr(new_due, k)) self.__active = activate # 2nd -- if activating, we might still need to have activations done if activate and not self.__activations_done: try: self.__prepare_exit_and_injections() except Exception as e: lgr.error("Failed to prepare injections etc: %s" % str(e)) finally: self.__activations_done = True due = DueSwitch(_get_inactive_due(), _get_active_due(), _get_duecredit_enable())duecredit-0.4.5.3/duecredit/log.py0000600013464101346420000001747212555330243016376 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. This file originates from datalad distributed # under MIT license. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## import logging, os, sys, platform import logging.handlers from .utils import is_interactive __all__ = ['ColorFormatter', 'log'] # Snippets from traceback borrowed from PyMVPA upstream/2.4.0-39-g69ad545 MIT license import traceback import re from os.path import basename, dirname def mbasename(s): """Custom function to include directory name if filename is too common Also strip .py at the end """ base = basename(s) if base.endswith('.py'): base = base[:-3] if base in set(['base', '__init__']): base = basename(dirname(s)) + '.' + base return base class TraceBack(object): """Customized traceback to be included in debug messages """ def __init__(self, collide=False): """Initialize TrackBack metric Parameters ---------- collide : bool if True then prefix common with previous invocation gets replaced with ... """ self.__prev = "" self.__collide = collide def __call__(self): ftb = traceback.extract_stack(limit=100)[:-2] entries = [[mbasename(x[0]), str(x[1])] for x in ftb if mbasename(x[0]) != 'logging.__init__'] entries = [ e for e in entries if e[0] != 'unittest' ] # lets make it more consize entries_out = [entries[0]] for entry in entries[1:]: if entry[0] == entries_out[-1][0]: entries_out[-1][1] += ',%s' % entry[1] else: entries_out.append(entry) sftb = '>'.join(['%s:%s' % (mbasename(x[0]), x[1]) for x in entries_out]) if self.__collide: # lets remove part which is common with previous invocation prev_next = sftb common_prefix = os.path.commonprefix((self.__prev, sftb)) common_prefix2 = re.sub('>[^>]*$', '', common_prefix) if common_prefix2 != "": sftb = '...' + sftb[len(common_prefix2):] self.__prev = prev_next return sftb # Recipe from http://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output # by Brandon Thomson # Adjusted for automagic determination either coloring is needed and # prefixing of multiline log lines class ColorFormatter(logging.Formatter): BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) RESET_SEQ = "\033[0m" COLOR_SEQ = "\033[1;%dm" BOLD_SEQ = "\033[1m" COLORS = { 'WARNING': YELLOW, 'INFO': WHITE, 'DEBUG': BLUE, 'CRITICAL': YELLOW, 'ERROR': RED } def __init__(self, use_color=None, log_name=False): if use_color is None: # if 'auto' - use color only if all streams are tty use_color = is_interactive() self.use_color = use_color and platform.system() != 'Windows' # don't use color on windows msg = self.formatter_msg(self._get_format(log_name), self.use_color) self._tb = TraceBack(collide=os.environ.get('DUECREDIT_LOGTRACEBACK', '') == 'collide') \ if os.environ.get('DUECREDIT_LOGTRACEBACK', False) else None logging.Formatter.__init__(self, msg) def _get_format(self, log_name=False): return ("$BOLD%(asctime)-15s$RESET " + ("%(name)-15s " if log_name else "") + "[%(levelname)s] " "%(message)s " "($BOLD%(filename)s$RESET:%(lineno)d)") def formatter_msg(self, fmt, use_color=False): if use_color: fmt = fmt.replace("$RESET", self.RESET_SEQ).replace("$BOLD", self.BOLD_SEQ) else: fmt = fmt.replace("$RESET", "").replace("$BOLD", "") return fmt def format(self, record): if record.msg.startswith('| '): # If we already log smth which supposed to go without formatting, like # output for running a command, just return the message and be done return record.msg levelname = record.levelname if self.use_color and levelname in self.COLORS: fore_color = 30 + self.COLORS[levelname] levelname_color = self.COLOR_SEQ % fore_color + "%-7s" % levelname + self.RESET_SEQ record.levelname = levelname_color record.msg = record.msg.replace("\n", "\n| ") if self._tb: record.msg = self._tb() + " " + record.msg return logging.Formatter.format(self, record) class LoggerHelper(object): """Helper to establish and control a Logger""" def __init__(self, name='duecredit'): self.name = name self.lgr = logging.getLogger(name) def _get_environ(self, var, default=None): return os.environ.get(self.name.upper() + '_%s' % var.upper(), default) def set_level(self, level=None, default='WARNING'): """Helper to set loglevel for an arbitrary logger By default operates for 'duecredit'. TODO: deduce name from upper module name so it could be reused without changes """ if level is None: # see if nothing in the environment level = self._get_environ('LOGLEVEL') if level is None: level = default try: # it might be a string which still represents an int log_level = int(level) except ValueError: # or a string which corresponds to a constant;) log_level = getattr(logging, level.upper()) self.lgr.setLevel(log_level) def get_initialized_logger(self, logtarget=None): """Initialize and return the logger Parameters ---------- target: string, optional Which log target to request logger for logtarget: { 'stdout', 'stderr', str }, optional Where to direct the logs. stdout and stderr stand for standard streams. Any other string is considered a filename. Multiple entries could be specified comma-separated Returns ------- logging.Logger """ # By default mimic previously talkative behavior logtarget = self._get_environ('LOGTARGET', logtarget or 'stdout') # Allow for multiple handlers being specified, comma-separated if ',' in logtarget: for handler_ in logtarget.split(','): self.get_initialized_logger(logtarget=handler_) return self.lgr if logtarget.lower() in ('stdout', 'stderr') : loghandler = logging.StreamHandler(getattr(sys, logtarget.lower())) use_color = is_interactive() # explicitly decide here else: # must be a simple filename # Use RotatingFileHandler for possible future parametrization to keep # log succinct and rotating loghandler = logging.handlers.RotatingFileHandler(logtarget) use_color = False # I had decided not to guard this call and just raise exception to go # out happen that specified file location is not writable etc. # But now improve with colors and useful information such as time loghandler.setFormatter( ColorFormatter(use_color=use_color, log_name=self._get_environ("LOGNAME", False))) #logging.Formatter('%(asctime)-15s %(levelname)-6s %(message)s')) self.lgr.addHandler(loghandler) self.set_level() # set default logging level return self.lgr lgr = LoggerHelper().get_initialized_logger() duecredit-0.4.5.3/duecredit/injections/0000700013464101346420000000000012641331574017377 5ustar yohyoh00000000000000duecredit-0.4.5.3/duecredit/injections/mod_pandas.py0000600013464101346420000000222012617543571022061 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Automatic injection of bibliography entries for pandas module """ from ..entries import Doi, BibTeX, Url # If defined, would determine from which to which version of the corresponding # module to care about min_version = None max_version = None def inject(injector): injector.add('pandas', None, BibTeX(""" @InProceedings{ mckinney-proc-scipy-2010, author = { McKinney, Wes }, title = { Data Structures for Statistical Computing in Python }, booktitle = { Proceedings of the 9th Python in Science Conference }, pages = { 51 -- 56 }, year = { 2010 }, editor = { van der Walt, St\'efan and Millman, Jarrod } } """), description="Data analysis library for tabular data")duecredit-0.4.5.3/duecredit/injections/mod_nipy.py0000600013464101346420000000221212602300070021546 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Automatic injection of bibliography entries for nipy module """ from ..entries import Doi # If defined, would determine from which to which version of the corresponding # module to care about min_version = None max_version = None def inject(injector): injector.add('nipy', None, Doi('10.1016/S1053-8119(09)72223-2'), description="Library fMRI data analysis", tags=['implementation']) for f, d in [('spectral_decomposition', 'PCA decomposition of symbolic HRF shifted over time'), ('taylor_approx', 'A Taylor series approximation of an HRF shifted over time')]: injector.add('nipy.modalities.fmri.fmristat.hrf', f, Doi('10.1006/nimg.2002.1096'), description=d, tags=['implementation'])duecredit-0.4.5.3/duecredit/injections/mod_sklearn.py0000600013464101346420000001124712617534523022260 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Automatic injection of bibliography entries for numpy module """ from ..entries import Doi, BibTeX, Url # If defined, would determine from which to which version of the corresponding # module to care about min_version = None max_version = None def inject(injector): injector.add('sklearn', None, BibTeX(""" @article{pedregosa2011scikit, title={Scikit-learn: Machine learning in Python}, author={Pedregosa, Fabian and Varoquaux, Ga{\"e}l and Gramfort, Alexandre and Michel, Vincent and Thirion, Bertrand and Grisel, Olivier and Blondel, Mathieu and Prettenhofer, Peter and Weiss, Ron and Dubourg, Vincent and others}, journal={The Journal of Machine Learning Research}, volume={12}, pages={2825--2830}, year={2011}, publisher={JMLR.org} } """), description="Machine Learning library") # sklearn.cluster.affinity_propagation_ injector.add('sklearn.cluster.affinity_propagation_', None, Doi('10.1126/science.1136800'), description="Affinity propagation clustering algorithm", tags=['implementation']) # sklearn.cluster.bicluster injector.add('sklearn.cluster.bicluster', 'SpectralCoclustering._fit', Doi('10.1101/gr.648603'), description="Spectral Coclustering algorithm", tags=['implementation']) injector.add('sklearn.cluster.bicluster', 'SpectralBiclustering._fit', Doi('10.1101/gr.648603'), description="Spectral Biclustering algorithm", tags=['implementation']) # sklearn.cluster.birch injector.add('sklearn.cluster.birch', 'Birch._fit', Doi('10.1145/233269.233324'), description="BIRCH clustering algorithm", tags=['implementation']) injector.add('sklearn.cluster.birch', 'Birch._fit', Url('https://code.google.com/p/jbirch/'), description="Java implementation of BIRCH clustering algorithm", tags=['another-implementation']) # sklearn.cluster.dbscan_ injector.add('sklearn.cluster.dbscan_', 'dbscan', BibTeX("""@inproceedings{ester1996density, title={A density-based algorithm for discovering clusters in large spatial databases with noise.}, author={Ester, Martin and Kriegel, Hans-Peter and Sander, J{\"o}rg and Xu, Xiaowei}, booktitle={Kdd}, volume={96}, number={34}, pages={226--231}, year={1996} }"""), description="dbscan clustering algorithm", tags=['implementation']) # sklearn.cluster.mean_shift_ injector.add('sklearn.cluster.mean_shift_', 'mean_shift', Doi('10.1109/34.1000236'), description="Mean shift clustering algorithm", tags=['implementation']) # sklearn.cluster.spectral injector.add('sklearn.cluster.spectral', 'discretize', Doi('10.1109/ICCV.2003.1238361'), description="Multiclass spectral clustering", tags=['reference']) injector.add('sklearn.cluster.spectral', 'spectral_clustering', Doi('10.1109/34.868688'), description="Spectral clustering", tags=['implementation']) injector.add('sklearn.cluster.spectral', 'spectral_clustering', Doi('10.1007/s11222-007-9033-z'), description="Spectral clustering", tags=['implementation']) # sklearn.ensemble.forest and tree Breiman_2001 = Doi("10.1023/A:1010933404324") Breiman_1984 = BibTeX("""@BOOK{breiman-friedman-olshen-stone-1984, author = {L. Breiman and J. Friedman and R. Olshen and C. Stone}, title = {{Classification and Regression Trees}}, publisher = {Wadsworth and Brooks}, address = {Monterey, CA}, year = {1984}, }""") # Not clear here though if those are the original publication on the topic # or just an educational references (books), most probably both ;) injector.add('sklearn.ensemble.forest', 'RandomForestClassifier.predict_proba', Breiman_2001, description="Random forest classifiers", tags=['implementation', 'edu']) injector.add('sklearn.ensemble.forest', 'RandomForestRegressor.predict', Breiman_2001, description="Random forest regressions", tags=['implementation', 'edu']) injector.add('sklearn.tree.tree', 'DecisionTreeClassifier.predict_proba', Breiman_1984, description="Classification and regression trees", tags=['implementation', 'edu']) duecredit-0.4.5.3/duecredit/injections/mod_dipy.py0000600013464101346420000000163312601270335021553 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """ Automatic injection of bibliography entries for dipy module """ from ..entries import Doi, BibTeX # If defined, would determine from which to which version of the corresponding # module to care about min_version = None max_version = None def inject(injector): #http://nipy.org/dipy/cite.html#a-note-on-citing-our-work injector.add('dipy', None, Doi('10.3389/fninf.2014.00008'), description='Dipy, a library for the analysis of diffusion MRI data.', tags=['implementation']) duecredit-0.4.5.3/duecredit/injections/mod_numpy.py0000600013464101346420000000223612627740445021772 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Automatic injection of bibliography entries for numpy module """ from ..entries import Doi, BibTeX # If defined, would determine from which to which version of the corresponding # module to care about min_version = None max_version = None def inject(injector): injector.add('numpy', None, BibTeX(""" @article{van2011numpy, title={The NumPy array: a structure for efficient numerical computation}, author={Van Der Walt, Stefan and Colbert, S Chris and Varoquaux, Gael}, journal={Computing in Science \& Engineering}, volume={13}, number={2}, pages={22--30}, year={2011}, publisher={AIP Publishing} } """), tags=['implementation'], cite_module=True, description="Scientific tools library") duecredit-0.4.5.3/duecredit/injections/mod_scipy.py0000600013464101346420000001443612562750222021746 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Automatic injection of bibliography entries for scipy module """ from ..entries import Doi, BibTeX # If defined, would determine from which to which version of the corresponding # module to care about min_version = None max_version = None def inject(injector): injector.add('scipy', None, BibTeX(""" @Misc{JOP+01, author = {Eric Jones and Travis Oliphant and Pearu Peterson and others}, title = {{SciPy}: Open source scientific tools for {Python}}, year = {2001--}, url = "http://www.scipy.org/", note = {[Online; accessed 2015-07-13]} }"""), description="Scientific tools library", tags=['implementation']) # scipy.cluster.hierarchy general references # TODO: we should allow to pass a list of entries injector.add('scipy.cluster.hierarchy', None, BibTeX(""" @article{johnson1967hierarchical, title={Hierarchical clustering schemes}, author={Johnson, Stephen C}, journal={Psychometrika}, volume={32}, number={3}, pages={241--254}, year={1967}, publisher={Springer} }"""), min_version='0.4.3', description="Hierarchical clustering", tags=['edu']) injector.add('scipy.cluster.hierarchy', None, BibTeX(""" @article{sneath1962numerical, title={Numerical taxonomy}, author={Sneath, Peter HA and Sokal, Robert R}, journal={Nature}, volume={193}, number={4818}, pages={855--860}, year={1962}, publisher={Nature Publishing Group} }"""), description="Hierarchical clustering", min_version='0.4.3', tags=['edu']) injector.add('scipy.cluster.hierarchy', None, BibTeX(""" @article{batagelj1995comparing, title={Comparing resemblance measures}, author={Batagelj, Vladimir and Bren, Matevz}, journal={Journal of classification}, volume={12}, number={1}, pages={73--90}, year={1995}, publisher={Springer} }"""), description="Hierarchical clustering", min_version='0.4.3', tags=['edu']) injector.add('scipy.cluster.hierarchy', None, BibTeX(""" @book{sokal1958statistical, author = {Sokal, R R and Michener, C D and {University of Kansas}}, title = {{A Statistical Method for Evaluating Systematic Relationships}}, publisher = {University of Kansas}, year = {1958}, series = {University of Kansas science bulletin} }"""), description="Hierarchical clustering", min_version='0.4.3', tags=['edu']) injector.add('scipy.cluster.hierarchy', None, BibTeX(""" @article{edelbrock1979mixture, title={Mixture model tests of hierarchical clustering algorithms: the problem of classifying everybody}, author={Edelbrock, Craig}, journal={Multivariate Behavioral Research}, volume={14}, number={3}, pages={367--384}, year={1979}, publisher={Taylor \& Francis} }"""), description="Hierarchical clustering", min_version='0.4.3', tags=['edu']) injector.add('scipy.cluster.hierarchy', None, BibTeX(""" @book{jain1988algorithms, title={Algorithms for clustering data}, author={Jain, Anil K and Dubes, Richard C}, year={1988}, publisher={Prentice-Hall, Inc.} }"""), description="Hierarchical clustering", min_version='0.4.3', tags=['edu']) injector.add('scipy.cluster.hierarchy', None, BibTeX(""" @article{fisher1936use, title={The use of multiple measurements in taxonomic problems}, author={Fisher, Ronald A}, journal={Annals of eugenics}, volume={7}, number={2}, pages={179--188}, year={1936}, publisher={Wiley Online Library} }"""), description="Hierarchical clustering", min_version='0.4.3', tags=['edu']) # Here options for linkage injector.add('scipy.cluster.hierarchy', 'linkage', BibTeX(""" @article{ward1963hierarchical, title={Hierarchical grouping to optimize an objective function}, author={Ward Jr, Joe H}, journal={Journal of the American statistical association}, volume={58}, number={301}, pages={236--244}, year={1963}, publisher={Taylor \& Francis} }"""), conditions={(1, 'method'): {'ward'}}, description="Ward hierarchical clustering", min_version='0.4.3', tags=['reference']) injector.add('scipy.cluster.hierarchy', 'linkage', BibTeX(""" @article{gower1969minimum, title={Minimum spanning trees and single linkage cluster analysis}, author={Gower, John C and Ross, GJS}, journal={Applied statistics}, pages={54--64}, year={1969}, publisher={JSTOR} }"""), conditions={(1, 'method'): {'single', 'DC_DEFAULT'}}, description="Single linkage hierarchical clustering", min_version='0.4.3', tags=['reference']) injector.add('scipy.cluster.hierarchy', 'linkage', BibTeX(""" @article{sibson1973slink, title={SLINK: an optimally efficient algorithm for the single-link cluster method}, author={Sibson, Robin}, journal={The Computer Journal}, volume={16}, number={1}, pages={30--34}, year={1973}, publisher={Br Computer Soc} }"""), conditions={(1, 'method'): {'single', 'DC_DEFAULT'}}, description="Single linkage hierarchical clustering", min_version='0.4.3', tags=['implementation']) duecredit-0.4.5.3/duecredit/injections/mod_mne.py0000600013464101346420000000210012601270335021353 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """ Automatic injection of bibliography entries for mne module """ from ..entries import Doi, BibTeX # If defined, would determine from which to which version of the corresponding # module to care about min_version = None max_version = None def inject(injector): #http://martinos.org/mne/stable/cite.html injector.add('mne', None, Doi('10.1016/j.neuroimage.2013.10.027'), description='MNE software for processing MEG and EEG data.', tags=['implementation']) injector.add('mne', None, Doi('10.3389/fnins.2013.00267'), description='MEG and EEG data analysis with MNE-Python.', tags=['implementation']) duecredit-0.4.5.3/duecredit/injections/mod_biosig.py0000600013464101346420000000142012601033155022051 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Automatic injection of bibliography entries for biosig module """ from ..entries import Doi # If defined, would determine from which to which version of the corresponding # module to care about min_version = None max_version = None def inject(injector): injector.add('biosig', None, Doi("10.1109/MC.2008.407"), description="I/O library for biosignal data formats") duecredit-0.4.5.3/duecredit/injections/mod_psychopy.py0000600013464101346420000000205312601270335022461 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """ Automatic injection of bibliography entries for psychopy module """ from ..entries import Doi, BibTeX, Url # If defined, would determine from which to which version of the corresponding # module to care about min_version = None max_version = None def inject(injector): injector.add('psychopy', None, Doi('doi:10.1016/j.jneumeth.2006.11.017'), description="PsychoPy -- Psychophysics software in Python.", tags=['implementation']) injector.add('psychopy', None, Doi('10.3389/neuro.11.010.2008'), description="Generating stimuli for neuroscience using PsychoPy.", tags=['implementation']) duecredit-0.4.5.3/duecredit/injections/mod_skimage.py0000600013464101346420000000155212601270335022226 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """ Automatic injection of bibliography entries for skimage module """ from ..entries import Doi, BibTeX # If defined, would determine from which to which version of the corresponding # module to care about min_version = None max_version = None def inject(injector): #http://scikit-image.org injector.add('skimage', None, Doi('10.7717/peerj.453'), description='scikit-image: Image processing in Python.', tags=['implementation']) duecredit-0.4.5.3/duecredit/injections/__init__.py0000600013464101346420000000103512552503722021506 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Facility to automagically decorate with references other modules """ __docformat__ = 'restructuredtext' from .injector import DueCreditInjector duecredit-0.4.5.3/duecredit/injections/mod_nipype.py0000600013464101346420000000510112601330232022075 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """ Automatic injection of bibliography entries for nipype module """ from ..entries import Doi, BibTeX # If defined, would determine from which to which version of the corresponding # module to care about min_version = None max_version = None def inject(injector): #http://nipy.org/nipype/about.html injector.add('nipype', None, Doi('10.3389/fninf.2011.00013'), description='Nipype: a flexible, lightweight and extensible neuroimaging data processing framework in Python', tags=['implementation']) #http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/ injector.add('nipype.interfaces', 'fsl', Doi('10.1016/j.neuroimage.2004.07.051'), description='Advances in functional and structural MR image analysis and implementation as FSL', tags=['implementation']) injector.add('nipype.interfaces', 'fsl', Doi('10.1016/j.neuroimage.2008.10.055'), description='Bayesian analysis of neuroimaging data in FSL', tags=['implementation']) injector.add('nipype.interfaces', 'fsl', Doi('10.1016/j.neuroimage.2011.09.015'), description='FSL.', tags=['implementation']) #http://www.fil.ion.ucl.ac.uk/spm injector.add('nipype.interfaces', 'spm', BibTeX(""" @book{FrackowiakFristonFrithDolanMazziotta1997, author={R.S.J. Frackowiak, K.J. Friston, C.D. Frith, R.J. Dolan, and J.C. Mazziotta}, title={Human Brain Function}, publisher={Academic Press USA} year={1997}, } """), description='The fundamental text on Statistical Parametric Mapping (SPM)', tags=['implementation']) #http://surfer.nmr.mgh.harvard.edu/fswiki/FreeSurferMethodsCitation # there are a bunch, not sure what is primary #injector.add('nipype.interfaces', 'freesurfer', Doi(''), #description='', #tags=['implementation']) #http://afni.nimh.nih.gov/afni/about/citations/ # there are a bunch, not sure what is primary #injector.add('nipype.interfaces', 'afni', Doi(''), #description='', #tags=['implementation']) duecredit-0.4.5.3/duecredit/injections/mod_mdp.py0000600013464101346420000001074512617543702021402 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """ Automatic injection of bibliography entries for mdp module """ from ..entries import Doi, BibTeX, Url # If defined, would determine from which to which version of the corresponding # module to care about min_version = None max_version = None def inject(injector): injector.add('mdp', None, Doi('10.3389/neuro.11.008.2008'), description="Modular toolkit for Data Processing (MDP): a Python data processing framework", tags=['implementation']) injector.add('mdp.nodes', 'PCANode.train', Doi('10.1007/b98835'), description="Principal Component Analysis (and filtering)", tags=['implementation']) injector.add('mdp.nodes', 'NIPALSNode.train', BibTeX(""" @incollection{Word1966, author={Wold, H.}, title={Nonlinear estimation by iterative least squares procedures.}, booktitle={Research Papers in Statistics}, publisher={Wiley} year={1966}, editor={David, F.}, pages={411--444}, } """), description="Principal Component Analysis using the NIPALS algorithm.", tags=['edu']) injector.add('mdp.nodes', 'FastICANode.train', Doi('10.1109/72.761722'), description="Independent Component Analysis using the FastICA algorithm", tags=['implementation']) injector.add('mdp.nodes', 'CuBICANode.train', Doi('10.1109/TSP.2004.826173'), description='Independent Component Analysis using the CuBICA algorithm.', tags=['implementation']) injector.add('mdp.nodes', 'NIPALSNode.train', BibTeX(""" @conference{ZieheMuller1998, author={Ziehe, Andreas and Muller, Klaus-Robert}, title={TDSEP an efficient algorithm for blind separation using time structure.}, booktitle={Proc. 8th Int. Conf. Artificial Neural Networks}, year={1998}, editor={Niklasson, L, Boden, M, and Ziemke, T}, publisher={ICANN} } """), description='Independent Component Analysis using the TDSEP algorithm', tags=['edu']) injector.add('mdp.nodes', 'JADENode.train', Doi('10.1049/ip-f-2.1993.0054'), description='Independent Component Analysis using the JADE algorithm', tags=['implementation']) injector.add('mdp.nodes', 'JADENode.train', Doi('10.1162/089976699300016863'), description='Independent Component Analysis using the JADE algorithm', tags=['implementation']) injector.add('mdp.nodes', 'SFANode.train', Doi('10.1162/089976602317318938'), description='Slow Feature Analysis', tags=['implementation']) injector.add('mdp.nodes', 'SFA2Node.train', Doi('10.1162/089976602317318938'), description='Slow Feature Analysis (via the space of inhomogeneous polynomials)', tags=['implementation']) injector.add('mdp.nodes', 'ISFANode.train', Doi('10.1007/978-3-540-30110-3_94'), description='Independent Slow Feature Analysis', tags=['implementation']) injector.add('mdp.nodes', 'XSFANode.train', BibTeX(""" @article{SprekelerZitoWiskott2009, author={Sprekeler, H., Zito, T., and Wiskott, L.}, title={An Extension of Slow Feature Analysis for Nonlinear Blind Source Separation.}, journal={Journal of Machine Learning Research.}, year={2009}, volume={15}, pages={921--947}, } """), description="Non-linear Blind Source Separation using Slow Feature Analysis", tags=['edu']) injector.add('mdp.nodes', 'FDANode.train', BibTeX(""" @book{Bishop2011, author={Bishop, Christopher M.}, title={Neural Networks for Pattern Recognition}, publisher={Oxford University Press, Inc} year={2011}, pages={105--112}, } """), description="(generalized) Fisher Discriminant Analysis", tags=['edu']) # etc... duecredit-0.4.5.3/duecredit/injections/mod_nibabel.py0000600013464101346420000000151412601330232022171 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Automatic injection of bibliography entries for nibabel module """ from ..entries import Url # If defined, would determine from which to which version of the corresponding # module to care about min_version = None max_version = None def inject(injector): injector.add('nibabel', None, Url('http://nipy.org/nibabel'), description="Access a cacophony of neuro-imaging file formats", tags=['implementation']) duecredit-0.4.5.3/duecredit/injections/injector.py0000600013464101346420000003675412613712266021607 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Importer which would also call decoration on a module upon import """ __docformat__ = 'restructuredtext' import pdb import os from os.path import basename, join as pathjoin, dirname from glob import glob import sys from functools import wraps from ..log import lgr from six import iteritems if sys.version_info < (3,): import __builtin__ else: import builtins as __builtin__ __all__ = ['DueCreditInjector', 'find_object'] # TODO: move elsewhere def _short_str(obj, l=30): """Return a shortened str of an object -- for logging""" s = str(obj) if len(s) > l: return s[:l-3] + "..." else: return s def get_modules_for_injection(): """Get local modules which provide "inject" method to provide delayed population of injector """ return sorted([basename(x)[:-3] for x in glob(pathjoin(dirname(__file__), "mod_*.py")) ]) def find_object(mod, path): """Finds object among present within module "mod" given path specification within Returns ------- parent, obj_name, obj """ obj = mod # we will look first within module for obj_name in path.split('.'): parent = obj obj = getattr(parent, obj_name) return parent, obj_name, obj # We will keep a very original __import__ to mitigate cases of buggy python # behavior, see e.g. # https://github.com/duecredit/duecredit/issues/40 # But we will also keep the __import__ as of 'activate' call state so we could # stay friendly to anyone else who might decorate __import__ as well _very_orig_import = __builtin__.__import__ class DueCreditInjector(object): """Takes care about "injecting" duecredit references into 3rd party modules upon their import First entries to be "injected" need to be add'ed to the instance. To not incure significant duecredit startup penalty, those entries are added for a corresponding package only whenever corresponding top-level module gets imported. """ # Made as a class variable to assure being singleton and available upon del _orig_import = None def __init__(self, collector=None): if collector is None: from duecredit import due collector = due self._collector = collector self._delayed_injections = {} self._entry_records = {} # dict: modulename: {object: [('entry', cite kwargs)]} self._processed_modules = set() # We need to process modules only after we are done with all nested imports, otherwise we # might be trying to prcess them too early -- whenever they are not yet linked to their # parent's namespace. So we will keep track of import level and set of modules which # would need to be processed whenever we are back at __import_level == 1 self.__import_level = 0 self.__queue_to_process = set() self.__processing_queue = False def _populate_delayed_injections(self): self._delayed_injections = {} for inj_mod_name in get_modules_for_injection(): assert(inj_mod_name.startswith('mod_')) mod_name = inj_mod_name[4:] lgr.debug("Adding delayed injection for %s", (mod_name,)) self._delayed_injections[mod_name] = inj_mod_name def add(self, modulename, obj, entry, min_version=None, max_version=None, **kwargs): """Add a citation for a given module or object within it Parameters ---------- modulename : string Name of the module (possibly a sub-module) obj : string or None Name of the object (function, method within a class) or None (if for entire module) min_version, max_version : string or tuple, optional Min (inclusive) / Max (exclusive) version of the module where this citation is applicable **kwargs Keyword arguments to be passed into cite. Note that "path" will be automatically set if not provided """ lgr.debug("Adding citation entry %s for %s:%s", _short_str(entry), modulename, obj) if modulename not in self._entry_records: self._entry_records[modulename] = {} if obj not in self._entry_records[modulename]: self._entry_records[modulename][obj] = [] obj_entries = self._entry_records[modulename][obj] if 'path' not in kwargs: kwargs['path'] = modulename + ((":%s" % obj) if obj else "") obj_entries.append({'entry': entry, 'kwargs': kwargs, 'min_version': min_version, 'max_version': max_version}) @property def _import_level_prefix(self): return "." * self.__import_level def _process_delayed_injection(self, mod_name): lgr.debug("%sProcessing delayed injection for %s", self._import_level_prefix, mod_name) inj_mod_name = self._delayed_injections[mod_name] assert(not hasattr(self._orig_import, '__duecredited__')) try: inj_mod_name_full = "duecredit.injections." + inj_mod_name lgr.log(3, "Importing %s", inj_mod_name_full) # Mark it is a processed already, to avoid its processing etc self._processed_modules.add(inj_mod_name_full) inj_mod = self._orig_import(inj_mod_name_full, fromlist=["duecredit.injections"]) except Exception as e: if os.environ.get('DUECREDIT_ALLOW_FAIL', False): raise raise RuntimeError("Failed to import %s: %r" % (inj_mod_name, e)) # TODO: process min/max_versions etc assert(hasattr(inj_mod, 'inject')) lgr.log(3, "Calling injector of %s", inj_mod_name_full) inj_mod.inject(self) def process(self, mod_name): """Process import of the module, possibly decorating some methods with duecredit entries """ assert(self.__import_level == 0) # we should never process while nested within imports # We need to mark that module as processed EARLY, so we don't try to re-process it # while doing _process_delayed_injection self._processed_modules.add(mod_name) if mod_name in self._delayed_injections: # should be hit only once, "theoretically" unless I guess reimport is used etc self._process_delayed_injection(mod_name) if mod_name not in self._entry_records: return lgr.debug("Process %d injections for module %s", len(self._entry_records[mod_name]), mod_name) try: mod = sys.modules[mod_name] except KeyError: lgr.warning("Failed to access module %s among sys.modules" % mod_name) return # go through the known entries and register them within the collector, and # decorate corresponding methods # There could be multiple records per module for obj_path, obj_entry_records in iteritems(self._entry_records[mod_name]): parent, obj_name = None, None if obj_path: # so we point to an object within the mod try: parent, obj_name, obj = find_object(mod, obj_path) except (KeyError, AttributeError) as e: lgr.warning("Could not find %s in module %s: %s" % (obj_path, mod, e)) continue # there could be multiple per func lgr.log(4, "Considering %d records for decoration of %s:%s", len(obj_entry_records), parent, obj_name) for obj_entry_record in obj_entry_records: entry = obj_entry_record['entry'] # Add entry explicitly self._collector.add(entry) if obj_path: # if not entire module -- decorate! decorator = self._collector.dcite(entry.get_key(), **obj_entry_record['kwargs']) lgr.debug("Decorating %s:%s with %s", parent, obj_name, decorator) setattr(parent, obj_name, decorator(obj)) else: lgr.log(3, "Citing directly %s:%s since obj_path is empty", parent, obj_name) self._collector.cite(entry.get_key(), **obj_entry_record['kwargs']) lgr.log(3, "Done processing injections for module %s", mod_name) def _mitigate_None_orig_import(self, name, *args, **kwargs): lgr.error("For some reason self._orig_import is None" ". Importing using stock importer to mitigate and adjusting _orig_import") DueCreditInjector._orig_import = _very_orig_import return _very_orig_import(name, *args, **kwargs) def activate(self, retrospect=True): """ Parameters ---------- retrospect : bool, optional Either consider already loaded modules """ if not self._orig_import: # for paranoid Yarik so we have assurance we are not somehow # overriding our decorator if hasattr(__builtin__.__import__, '__duecredited__'): raise RuntimeError("__import__ is already duecredited") DueCreditInjector._orig_import = __builtin__.__import__ @wraps(__builtin__.__import__) def __import(name, *args, **kwargs): if self.__processing_queue or name in self._processed_modules or name in self.__queue_to_process: lgr.debug("Performing undecorated import of %s", name) # return right away without any decoration in such a case if self._orig_import: return _very_orig_import(name, *args, **kwargs) else: return self._mitigate_None_orig_import(name, *args, **kwargs) import_level_prefix = self._import_level_prefix lgr.log(1, "%sProcessing request to import %s", import_level_prefix, name) # importing submodule might result in importing a new one and # name here is not sufficient to determine which module would actually # get imported unless level=0 (absolute import), but that one rarely used # could be old-style or new style relative import! # args[0] -> globals, [1] -> locals(), [2] -> fromlist, [3] -> level level = args[3] if len(args) >= 4 else kwargs.get('level', -1) # fromlist = args[2] if len(args) >= 3 else kwargs.get('fromlist', []) if not retrospect and not self._processed_modules: # we were asked to not consider those modules which were already loaded # so let's assume that they were all processed already self._processed_modules = set(sys.modules) mod = None try: self.__import_level += 1 # TODO: safe-guard all our logic so # if anything goes wrong post-import -- we still return imported module if self._orig_import: mod = self._orig_import(name, *args, **kwargs) else: mod = self._mitigate_None_orig_import(name, *args, **kwargs) self._handle_fresh_imports(name, import_level_prefix, level) finally: self.__import_level -= 1 if self.__import_level == 0 and self.__queue_to_process: self._process_queue() lgr.log(1, "%sReturning %s", import_level_prefix, mod) return mod __import.__duecredited__ = True self._populate_delayed_injections() if retrospect: lgr.debug("Considering previously loaded %d modules", len(sys.modules)) # operate on keys() (not iterator) since we might end up importing delayed injection modules etc for mod_name in sys.modules.keys(): self.process(sys.modules[mod_name]) lgr.debug("Assigning our importer") __builtin__.__import__ = __import else: lgr.warning("Seems that we are calling duecredit_importer twice." " No harm is done but shouldn't happen") def _handle_fresh_imports(self, name, import_level_prefix, level): """Check which modules were imported since last point we checked and add them to the queue """ new_imported_modules = set(sys.modules.keys()) - self._processed_modules - self.__queue_to_process if new_imported_modules: lgr.log(4, "%s%d new modules were detected upon import of %s (level=%s)", import_level_prefix, len(new_imported_modules), name, level) # lgr.log(2, "%s%d new modules were detected: %s, upon import of %s (level=%s)", # import_level_prefix, len(new_imported_modules), new_imported_modules, name, level) for imported_mod in new_imported_modules: if imported_mod in self.__queue_to_process: # we saw it already continue # lgr.log(1, "Name %r was imported as %r (path: %s). fromlist: %s, level: %s", # name, mod.__name__, getattr(mod, '__path__', None), fromlist, level) # package package = imported_mod.split('.', 1)[0] if package != imported_mod \ and package not in self._processed_modules \ and package not in self.__queue_to_process: # if its parent package wasn't yet imported before lgr.log(3, "%sParent of %s, %s wasn't yet processed, adding to the queue", import_level_prefix, imported_mod, package) self.__queue_to_process.add(package) self.__queue_to_process.add(imported_mod) def _process_queue(self): """Process the queue of collected imported modules """ # process the queue lgr.debug("Processing queue of imported %d modules", len(self.__queue_to_process)) # We need first to process top-level modules etc, so delayed injections get picked up, # let's sort by the level queue_with_levels = sorted([(m.count('.'), m) for m in self.__queue_to_process]) self.__processing_queue = True try: sorted_queue = [x[1] for x in queue_with_levels] while sorted_queue: mod_name = sorted_queue.pop(0) self.process(mod_name) self.__queue_to_process.remove(mod_name) assert (not len(self.__queue_to_process)) finally: self.__processing_queue = False def deactivate(self): if not self._orig_import: lgr.warning("_orig_import is not yet known, so we haven't decorated default importer yet." " Nothing TODO") return lgr.debug("Assigning original importer") __builtin__.__import__ = self._orig_import DueCreditInjector._orig_import = None def __del__(self): if self._orig_import is not None: self.deactivate() try: super(self.__class__, self)._del__() except Exception: pass duecredit-0.4.5.3/duecredit/__init__.py0000600013464101346420000000132512562736560017354 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Module/app to automate collection of relevant to analysis publications. Please see README.md shipped along with duecredit to get a better idea about its functionality """ from .entries import Doi, BibTeX, Url from .version import __version__, __release_date__ from .dueswitch import due __all__ = ['Doi', 'BibTeX', 'Url', 'due']duecredit-0.4.5.3/duecredit/utils.py0000600013464101346420000001120512641323734016744 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. Originates from datalad package distributed # under MIT license # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## import os import logging import sys from functools import wraps lgr = logging.getLogger("duecredit.utils") # # Little helpers # def is_interactive(): """Return True if all in/outs are tty""" if any(not hasattr(inout, 'isatty') for inout in (sys.stdin, sys.stdout, sys.stderr)): lgr.warning("Assuming non interactive session since isatty found missing") return False # TODO: check on windows if hasattr check would work correctly and add value: # return sys.stdin.isatty() and sys.stdout.isatty() and sys.stderr.isatty() # # Decorators # # Borrowed from pandas # Copyright: 2011-2014, Lambda Foundry, Inc. and PyData Development Team # Licese: BSD-3 def optional_args(decorator): """allows a decorator to take optional positional and keyword arguments. Assumes that taking a single, callable, positional argument means that it is decorating a function, i.e. something like this:: @my_decorator def function(): pass Calls decorator with decorator(f, *args, **kwargs)""" @wraps(decorator) def wrapper(*args, **kwargs): def dec(f): return decorator(f, *args, **kwargs) is_decorating = not kwargs and len(args) == 1 and callable(args[0]) if is_decorating: f = args[0] args = [] return dec(f) else: return dec return wrapper def never_fail(f): """Assure that function never fails -- all exceptions are caught""" @wraps(f) def wrapped_func(*args, **kwargs): try: return f(*args, **kwargs) except Exception as e: lgr.warning("DueCredit internal failure while running %s: %r. " "Please report to developers at https://github.com/duecredit/duecredit/issues" % (f, e)) if os.environ.get('DUECREDIT_ALLOW_FAIL', False): return f else: return wrapped_func def borrowdoc(cls, methodname=None, replace=None): """Return a decorator to borrow docstring from another `cls`.`methodname` Common use is to borrow a docstring from the class's method for an adapter function (e.g. sphere_searchlight borrows from Searchlight) Examples -------- To borrow `__repr__` docstring from parent class `Mapper`, do:: @borrowdoc(Mapper) def __repr__(self): ... Parameters ---------- cls Usually a parent class methodname : None or str Name of the method from which to borrow. If None, would use the same name as of the decorated method replace : None or str, optional If not None, then not entire docstring gets replaced but only the matching to "replace" value string """ def _borrowdoc(method): """Decorator which assigns to the `method` docstring from another """ if methodname is None: other_method = getattr(cls, method.__name__) else: other_method = getattr(cls, methodname) if hasattr(other_method, '__doc__'): if not replace: method.__doc__ = other_method.__doc__ else: method.__doc__ = method.__doc__.replace(replace, other_method.__doc__) return method return _borrowdoc # # Context Managers # # # Additional handlers # _sys_excepthook = sys.excepthook # Just in case we ever need original one def setup_exceptionhook(): """Overloads default sys.excepthook with our exceptionhook handler. If interactive, our exceptionhook handler will invoke pdb.post_mortem; if not interactive, then invokes default handler. """ def _duecredit_pdb_excepthook(type, value, tb): if is_interactive(): import traceback, pdb traceback.print_exception(type, value, tb) print pdb.post_mortem(tb) else: lgr.warn("We cannot setup exception hook since not in interactive mode") # we are in interactive mode or we don't have a tty-like # device, so we call the default hook #sys.__excepthook__(type, value, tb) _sys_excepthook(type, value, tb) sys.excepthook = _duecredit_pdb_excepthook duecredit-0.4.5.3/duecredit/cmdline/0000700013464101346420000000000012641331574016645 5ustar yohyoh00000000000000duecredit-0.4.5.3/duecredit/cmdline/helpers.py0000600013464101346420000000727312537412770020676 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """ """ __docformat__ = 'restructuredtext' import argparse import re import sys from ..utils import is_interactive class HelpAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): # import pydb; pydb.debugger() if is_interactive() and option_string == '--help': # lets use the manpage on mature systems ... try: import subprocess subprocess.check_call( 'man %s 2> /dev/null' % parser.prog.replace(' ', '-'), shell=True) sys.exit(0) except (subprocess.CalledProcessError, OSError): # ...but silently fall back if it doesn't work pass if option_string == '-h': helpstr = "%s\n%s" \ % (parser.format_usage(), "Use '--help' to get more comprehensive information.") else: helpstr = parser.format_help() # better for help2man helpstr = re.sub(r'optional arguments:', 'options:', helpstr) # yoh: TODO for duecredit + help2man #helpstr = re.sub(r'positional arguments:\n.*\n', '', helpstr) # convert all heading to have the first character uppercase headpat = re.compile(r'^([a-z])(.*):$', re.MULTILINE) helpstr = re.subn(headpat, lambda match: r'{0}{1}:'.format(match.group(1).upper(), match.group(2)), helpstr)[0] # usage is on the same line helpstr = re.sub(r'^usage:', 'Usage:', helpstr) if option_string == '--help-np': usagestr = re.split(r'\n\n[A-Z]+', helpstr, maxsplit=1)[0] usage_length = len(usagestr) usagestr = re.subn(r'\s+', ' ', usagestr.replace('\n', ' '))[0] helpstr = '%s\n%s' % (usagestr, helpstr[usage_length:]) print(helpstr) sys.exit(0) class LogLevelAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): from ..log import LoggerHelper LoggerHelper().set_level(level=values) def parser_add_common_args(parser, pos=None, opt=None, **kwargs): from . import common_args for i, args in enumerate((pos, opt)): if args is None: continue for arg in args: arg_tmpl = getattr(common_args, arg) arg_kwargs = arg_tmpl[2].copy() arg_kwargs.update(kwargs) if i: parser.add_argument(*arg_tmpl[i], **arg_kwargs) else: parser.add_argument(arg_tmpl[i], **arg_kwargs) def parser_add_common_opt(parser, opt, names=None, **kwargs): from . import common_args opt_tmpl = getattr(common_args, opt) opt_kwargs = opt_tmpl[2].copy() opt_kwargs.update(kwargs) if names is None: parser.add_argument(*opt_tmpl[1], **opt_kwargs) else: parser.add_argument(*names, **opt_kwargs) class RegexpType(object): """Factory for creating regular expression types for argparse DEPRECATED AFAIK -- now things are in the config file... but we might provide a mode where we operate solely from cmdline """ def __call__(self, string): if string: return re.compile(string) else: return None duecredit-0.4.5.3/duecredit/cmdline/common_args.py0000600013464101346420000000244012551334414021521 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """ """ __docformat__ = 'restructuredtext' # argument spec template # = ( # , # {} #) from ..cmdline.helpers import HelpAction, LogLevelAction help = ( 'help', ('-h', '--help', '--help-np'), dict(nargs=0, action=HelpAction, help="""show this help message and exit. --help-np forcefully disables the use of a pager for displaying the help.""") ) version = ( 'version', ('--version',), dict(action='version', help="show program's version and license information and exit") ) log_level = ( 'log-level', ('-l', '--log-level'), dict(action=LogLevelAction, choices=['critical', 'error', 'warning', 'info', 'debug'] + [str(x) for x in range(1, 10)], default='warning', help="""level of verbosity. Integers provide even more debugging information""") ) duecredit-0.4.5.3/duecredit/cmdline/main.py0000600013464101346420000001674012562736560020163 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. Originates from datalad package distributed # under MIT license # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """""" __docformat__ = 'restructuredtext' import argparse import logging import sys import textwrap from .. import __version__ from ..log import lgr import duecredit.cmdline as duecmd from . import helpers from ..utils import setup_exceptionhook def _license_info(): return """\ Copyright (c) 2015 Yaroslav Halchenko, Matteo Visconti di Oleggio Castello. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the copyright holder. """ def get_commands(): return sorted([c for c in dir(duecmd) if c.startswith('cmd_')]) def setup_parser(): # setup cmdline args parser # main parser parser = argparse.ArgumentParser( fromfile_prefix_chars='@', # usage="%(prog)s ...", description="""\ DueCredit simplifies citation of papers describing methods, software and data used by any given analysis script/pipeline. """, epilog='"Your Credit is Due"', formatter_class=argparse.RawDescriptionHelpFormatter, add_help=False ) # common options helpers.parser_add_common_opt(parser, 'help') helpers.parser_add_common_opt(parser, 'log_level') helpers.parser_add_common_opt(parser, 'version', version='duecredit %s\n\n%s' % (__version__, _license_info())) if __debug__: parser.add_argument( '--dbg', action='store_true', dest='common_debug', help="do not catch exceptions and show exception traceback") # yoh: atm we only dump to console. Might adopt the same separation later on # and for consistency will call it --verbose-level as well for now # log-level is set via common_opts ATM # parser.add_argument('--log-level', # choices=('critical', 'error', 'warning', 'info', 'debug'), # dest='common_log_level', # help="""level of verbosity in log files. By default # everything, including debug messages is logged.""") #parser.add_argument('-l', '--verbose-level', # choices=('critical', 'error', 'warning', 'info', 'debug'), # dest='common_verbose_level', # help="""level of verbosity of console output. By default # only warnings and errors are printed.""") # subparsers subparsers = parser.add_subparsers() # for all subcommand modules it can find cmd_short_description = [] for cmd in get_commands(): cmd_name = cmd[4:] subcmdmod = getattr(__import__('duecredit.cmdline', globals(), locals(), [cmd], 0), cmd) # deal with optional parser args if 'parser_args' in subcmdmod.__dict__: parser_args = subcmdmod.parser_args else: parser_args = dict() # use module description, if no explicit description is available if not 'description' in parser_args: parser_args['description'] = subcmdmod.__doc__ # create subparser, use module suffix as cmd name subparser = subparsers.add_parser(cmd_name, add_help=False, **parser_args) # all subparser can report the version helpers.parser_add_common_opt( subparser, 'version', version='duecredit %s %s\n\n%s' % (cmd_name, __version__, _license_info())) # our own custom help for all commands helpers.parser_add_common_opt(subparser, 'help') helpers.parser_add_common_opt(subparser, 'log_level') # let module configure the parser subcmdmod.setup_parser(subparser) # logger for command # configure 'run' function for this command subparser.set_defaults(func=subcmdmod.run, logger=logging.getLogger('duecredit.%s' % cmd)) # store short description for later sdescr = getattr(subcmdmod, 'short_description', parser_args['description'].split('\n')[0]) cmd_short_description.append((cmd_name, sdescr)) # create command summary cmd_summary = [] for cd in cmd_short_description: cmd_summary.append('%s\n%s\n\n' \ % (cd[0], textwrap.fill(cd[1], 75, initial_indent=' ' * 4, subsequent_indent=' ' * 4))) parser.description = '%s\n%s\n\n%s' \ % (parser.description, '\n'.join(cmd_summary), textwrap.fill("""\ Detailed usage information for individual commands is available via command-specific help options, i.e.: %s --help""" % sys.argv[0], 75, initial_indent='', subsequent_indent='')) return parser def main(args=None): parser = setup_parser() # parse cmd args args = parser.parse_args(args) # run the function associated with the selected command if args.common_debug: # So we could see/stop clearly at the point of failure setup_exceptionhook() args.func(args) else: # Otherwise - guard and only log the summary. Postmortem is not # as convenient if being caught in this ultimate except try: args.func(args) except Exception as exc: lgr.error('%s (%s)' % (str(exc), exc.__class__.__name__)) sys.exit(1) duecredit-0.4.5.3/duecredit/cmdline/__init__.py0000600013464101346420000000074512537412472020767 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """ """ __docformat__ = 'restructuredtext' from . import cmd_summary from . import cmd_test duecredit-0.4.5.3/duecredit/cmdline/cmd_summary.py0000600013464101346420000000331712555330243021541 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Spit out the summary of the citations which were collected. """ import sys import os from ..log import lgr from ..config import DUECREDIT_FILE from ..collector import CollectorSummary from ..io import TextOutput, BibTeXOutput __docformat__ = 'restructuredtext' # magic line for manpage summary # man: -*- % summary of collected citations def setup_parser(parser): parser.add_argument( "-f", "--filename", default=DUECREDIT_FILE, help="Filename containing collected citations. Default: %(default)s") parser.add_argument( "--style", choices=("apa", "harvard1"), default="harvard1", help="Style to be used for listing citations") parser.add_argument( "--format", choices=("text", "bibtex"), default="text", help="Way to present the summary") def run(args): from ..io import PickleOutput if not os.path.exists(args.filename): lgr.debug("File {0} doesn't exist. No summary available".format( args.filename)) return 1 due = PickleOutput.load(args.filename) #CollectorSummary(due).dump() if args.format == "text": out = TextOutput(sys.stdout, due, args.style) elif args.format == "bibtex": out = BibTeXOutput(sys.stdout, due) else: raise ValueError("unknown to treat %s" % args.format) out.dump() duecredit-0.4.5.3/duecredit/cmdline/cmd_test.py0000600013464101346420000000152112562736560021030 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Run internal DueCredit (unit)tests to verify correct operation on the system""" __docformat__ = 'restructuredtext' # magic line for manpage summary # man: -*- % run unit-tests from .helpers import parser_add_common_args import nose def setup_parser(parser): # TODO -- pass options such as verbosity etc pass def run(args): import duecredit raise NotImplementedError("Just use nosetests duecredit for now") #duecredit.test() duecredit-0.4.5.3/duecredit/version.py0000600013464101346420000000010312641331574017265 0ustar yohyoh00000000000000__version__ = '0.4.5.3' __release_date__ = 'Dec 31 2015, 18:00:12' duecredit-0.4.5.3/duecredit/parsers.py0000600013464101346420000000154112537131217017262 0ustar yohyoh00000000000000import re def extract_references_from_rst(rst): # for now will be very simple, just trying to separate # then up until the end or another section starting pass def test_extract_references_from_rst(): # some obscure examples of how people specify references samples = [ """ References ---------- .. [1] line1 line2 .. [2] line11 line12 """, """ References ---------- - line1 line2 """, """ References ---------- .. [xyz1] line1 line2 .. [xyz2] line11 line12 Buga duga --------- """, """ References ---------- line1 line2 line11 line12 """ ] extract_references_from_rst(samples[0]) duecredit-0.4.5.3/duecredit/io.py0000600013464101346420000002650212627740445016227 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # Just for testing of robust operation import os if 'DUECREDIT_TEST_EARLY_IMPORT_ERROR' in os.environ.keys(): raise ImportError("DUECREDIT_TEST_EARLY_IMPORT_ERROR") import time from collections import defaultdict, Iterator import copy from os.path import dirname, exists import pickle import requests import tempfile from six import PY2, itervalues, iteritems import warnings from .config import CACHE_DIR, DUECREDIT_FILE from .entries import BibTeX, Doi from .log import lgr def get_doi_cache_file(doi): return os.path.join(CACHE_DIR, doi) def import_doi(doi): cached = get_doi_cache_file(doi) if exists(cached): with open(cached) as f: doi = f.read() if PY2: return doi.decode('utf-8') return doi # else -- fetch it headers = {'Accept': 'text/bibliography; style=bibtex'} url = 'http://dx.doi.org/' + doi retries = 10 while retries > 0: r = requests.get(url, headers=headers) r.encoding = 'UTF-8' bibtex = r.text.strip() if bibtex.startswith('@'): # no more retries necessary break lgr.warning("Failed to obtain bibtex from doi.org, retrying...") time.sleep(0.5) # give some time to the server retries -= 1 status_code = r.status_code if not bibtex.startswith('@'): raise ValueError('Query %(url)s for BibTeX for a DOI %(doi)s (wrong doi?) has failed. ' 'Response code %(status_code)d. ' #'BibTeX response was: %(bibtex)s' % locals()) if not exists(cached): cache_dir = dirname(cached) if not exists(cache_dir): os.makedirs(cache_dir) with open(cached, 'w') as f: if PY2: f.write(bibtex.encode('utf-8')) else: f.write(bibtex) return bibtex class EnumeratedEntries(Iterator): """A container of entries enumerated referenced by their entry_key""" def __init__(self): self._keys2refnr = {} self._refnr2keys = {} self._refnr = 1 def add(self, entry_key): """Add entry_key and update refnr""" if entry_key not in self._keys2refnr: self._keys2refnr[entry_key] = self._refnr self._refnr2keys[self._refnr] = entry_key self._refnr += 1 def __getitem__(self, item): if item not in self._keys2refnr: raise KeyError('{0} not present'.format(item)) return self._keys2refnr[item] def fromrefnr(self, refnr): if refnr not in self._refnr2keys: raise KeyError('{0} not present'.format(refnr)) return self._refnr2keys[refnr] def __iter__(self): return iteritems(self._keys2refnr) # Python 3 def __next__(self): return self.next() def next(self): yield next(self.__iter__()) def __len__(self): return len(self._keys2refnr) class TextOutput(object): # TODO some parent class to do what...? def __init__(self, fd, collector, style=None): self.fd = fd self.collector = collector # TODO: check that CLS style actually exists self.style = style if 'DUECREDIT_STYLE' in os.environ.keys(): self.style = os.environ['DUECREDIT_STYLE'] else: self.style = 'harvard1' # TODO: refactor name to sth more intuitive def _model_citations(self, tags=None): if not tags: tags = os.environ.get('DUECREDIT_REPORT_TAGS', 'reference-implementation,implementation').split(',') tags = set(tags) citations = self.collector.citations if tags != {'*'}: # Filter out citations citations = dict((k, c) for k, c in iteritems(citations) if tags.intersection(c.tags)) packages = {} modules = {} objects = {} for key in ('citations', 'entry_keys'): packages[key] = defaultdict(list) modules[key] = defaultdict(list) objects[key] = defaultdict(list) # for each path store both a list of entry keys and of citations for (path, entry_key), citation in iteritems(citations): if ':' in path: target_dict = objects elif '.' in path: target_dict = modules else: target_dict = packages target_dict['citations'][path].append(citation) target_dict['entry_keys'][path].append(entry_key) return packages, modules, objects def dump(self, tags=None): # get 'model' of citations packages, modules, objects = self._model_citations(tags) # mapping key -> refnr enum_entries = EnumeratedEntries() citations_ordered = [] # set up view # package level sublevels = [modules, objects] for package in sorted(packages['entry_keys']): for entry_key in packages['entry_keys'][package]: enum_entries.add(entry_key) citations_ordered.append(package) # sublevels for sublevel in sublevels: for obj in sorted(filter(lambda x: package in x, sublevel['entry_keys'])): for entry_key_obj in sublevel['entry_keys'][obj]: enum_entries.add(entry_key_obj) citations_ordered.append(obj) # Now we can "render" different views of our "model" # Here for now just text BUT that is where we can "split" the logic and provide # different renderings given the model -- text, rest, md, tex+latex, whatever self.fd.write('\nDueCredit Report:\n') for path in citations_ordered: if ':' in path: self.fd.write(' ') target_dict = objects elif '.' in path: self.fd.write(' ') target_dict = modules else: target_dict = packages # TODO: absorb common logic into a common function citations = target_dict['citations'][path] entry_keys = target_dict['entry_keys'][path] descriptions = sorted(map(str, set(str(r.description) for r in citations))) versions = sorted(map(str, set(str(r.version) for r in citations))) refnrs = sorted([str(enum_entries[entry_key]) for entry_key in entry_keys]) self.fd.write('- {0} / {1} (v {2}) [{3}]\n'.format( ", ".join(descriptions), path, ', '.join(versions), ', '.join(refnrs))) # Print out some stats obj_names = ('packages', 'modules', 'functions') n_citations = map(len, (packages['citations'], modules['citations'], objects['citations'])) for citation_type, n in zip(obj_names, n_citations): self.fd.write('\n{0} {1} cited'.format(n, citation_type)) if enum_entries: citations_fromentrykey = self.collector._citations_fromentrykey() self.fd.write('\n\nReferences\n' + '-' * 10 + '\n') # collect all the entries used refnr_key = [(nr, enum_entries.fromrefnr(nr)) for nr in range(1, len(enum_entries)+1)] for nr, key in refnr_key: self.fd.write('\n[{0}] '.format(nr)) self.fd.write(get_text_rendering(citations_fromentrykey[key], style=self.style)) self.fd.write('\n') def get_text_rendering(citation, style='harvard1'): from .collector import Citation entry = citation.entry if isinstance(entry, Doi): bibtex_rendering = get_bibtex_rendering(entry) bibtex_citation = copy.copy(citation) bibtex_citation.set_entry(bibtex_rendering) return get_text_rendering(bibtex_citation) elif isinstance(entry, BibTeX): return format_bibtex(entry, style=style) else: return str(entry) def get_bibtex_rendering(entry): if isinstance(entry, Doi): return BibTeX(import_doi(entry.doi)) elif isinstance(entry, BibTeX): return entry else: raise ValueError("Have no clue how to get bibtex out of %s" % entry) def format_bibtex(bibtex_entry, style='harvard1'): try: from citeproc.source.bibtex import BibTeX as cpBibTeX import citeproc as cp except ImportError as e: raise RuntimeError( "For formatted output we need citeproc and all of its dependencies " "(such as lxml) but there is a problem while importing citeproc: %s" % str(e)) key = bibtex_entry.get_key() # need to save it temporarily to use citeproc-py fname = tempfile.mktemp(suffix='.bib') try: with open(fname, 'wt') as f: bibtex = bibtex_entry.rawentry bibtex = bibtex.replace(u'\u2013', '--') + "\n" # TODO: manage to save/use UTF-8 if PY2: bibtex = bibtex.encode('ascii', 'ignore') f.write(bibtex) # We need to avoid cpBibTex spitting out warnings old_filters = warnings.filters[:] # store a copy of filters warnings.simplefilter('ignore', UserWarning) try: bib_source = cpBibTeX(fname) except Exception as e: lgr.error("Failed to process BibTeX file %s: %s" % (fname, e)) return "ERRORED: %s" % str(e) finally: # return warnings back warnings.filters = old_filters bib_style = cp.CitationStylesStyle(style, validate=False) # TODO: specify which tags of formatter we want bibliography = cp.CitationStylesBibliography(bib_style, bib_source, cp.formatter.plain) citation = cp.Citation([cp.CitationItem(key)]) bibliography.register(citation) finally: if not os.environ.get("DUECREDIT_KEEPTEMP"): os.unlink(fname) biblio_out = bibliography.bibliography() assert(len(biblio_out) == 1) biblio_out = ''.join(biblio_out[0]) return biblio_out # if biblio_out else str(bibtex_entry) # TODO: harmonize order of arguments class PickleOutput(object): def __init__(self, collector, fn=DUECREDIT_FILE): self.collector = collector self.fn = fn def dump(self): with open(self.fn, 'wb') as f: pickle.dump(self.collector, f) @classmethod def load(cls, filename=DUECREDIT_FILE): with open(filename, 'rb') as f: return pickle.load(f) class BibTeXOutput(object): # TODO some parent class to do what...? def __init__(self, fd, collector): self.fd = fd self.collector = collector def dump(self): for citation in self.collector.citations.values(): try: bibtex = get_bibtex_rendering(citation.entry) except: lgr.warning("Failed to generate bibtex for %s" % citation.entry) continue self.fd.write(bibtex.rawentry + "\n") def load_due(filename): return PickleOutput.load(filename) duecredit-0.4.5.3/duecredit/entries.py0000600013464101346420000000375412565643167017301 0ustar yohyoh00000000000000# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- # ex: set sts=4 ts=4 sw=4 noet: # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the duecredit package for the # copyright and license terms. Originates from datalad package distributed # under MIT license # # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## import re from six import PY2 class DueCreditEntry(object): def __init__(self, rawentry, key=None): self._rawentry = rawentry self._key = key or rawentry.lower() def get_key(self): return self._key @property def rawentry(self): if PY2: return unicode(self._rawentry) else: return self._rawentry def _process_rawentry(self): pass def __repr__(self): args = [repr(self._rawentry), "key={0}".format(repr(self._key))] args = ", ".join(args) return self.__class__.__name__ + '({0})'.format(args) class BibTeX(DueCreditEntry): def __init__(self, bibtex, key=None): super(BibTeX, self).__init__(bibtex.strip()) self._key = None self._reference = None self._process_rawentry() def _process_rawentry(self): reg = re.match("\s*@(?P\S*)\s*\{\s*(?P\S*)\s*,.*", self._rawentry, flags=re.MULTILINE) assert(reg) matches = reg.groupdict() self._key = matches['key'] def format(self): # TODO: return nice formatting of the entry return str(self._rawentry) class FreeTextEntry(DueCreditEntry): pass # nothing special I guess class Doi(DueCreditEntry): def __init__(self, doi, key=None): super(Doi, self).__init__(doi, key) self.doi = doi # TODO class Url(DueCreditEntry): def __init__(self, url, key=None): super(Url, self).__init__(url, key) self.url = url duecredit-0.4.5.3/examples/0000700013464101346420000000000012641331574015100 5ustar yohyoh00000000000000duecredit-0.4.5.3/examples/example_scipy.py0000600013464101346420000000076612562736560020335 0ustar yohyoh00000000000000# A tiny analysis script to demostrate duecredit # # Import of duecredit is not necessary if you just run this script with # python -m duecredit # import duecredit # Just to enable duecredit from scipy.cluster.hierarchy import linkage from scipy.spatial.distance import pdist from sklearn.datasets import make_blobs print("I: Simulating 4 blobs") data, true_label = make_blobs(centers=4) dist = pdist(data, metric='euclidean') Z = linkage(dist, method='single') print("I: Done clustering 4 blobs") duecredit-0.4.5.3/.coveragerc0000600013464101346420000000011612555440105015376 0ustar yohyoh00000000000000[run] branch = True source = duecredit include = duecredit/* examples/* duecredit-0.4.5.3/setup.py0000700013464101346420000001212512641323734014777 0ustar yohyoh00000000000000#!/usr/bin/env python """ duecredit -- publications (donations, etc) tracer """ import re import os import sys import re from datetime import datetime from setuptools import setup from pkgutil import walk_packages from subprocess import Popen, PIPE from os.path import exists # Adopted from citeproc-py # License: BSD-2 # Copyright 2011-2013 Brecht Machiels PACKAGE = 'duecredit' PACKAGE_ABSPATH = os.path.abspath(PACKAGE) VERSION_FILE = PACKAGE + '/version.py' # retrieve the version number from git or VERSION_FILE # inspired by http://dcreager.net/2010/02/10/setuptools-git-version-numbers/ try: if exists('debian/copyright'): print('Generating version.py out of debian/copyright information') # building debian package. Deduce version from debian/copyright with open('debian/changelog', 'r') as f: lines = f.readlines() __version__ = re.sub('(.*)-(.*?)$', r'\1.debian\2', lines[0].split()[1].strip('()') ).replace('-', '.') # TODO: unify format whenever really bored ;) __release_date__ = re.sub('^ -- .*>\s*(.*)', r'\1', list(filter(lambda x: x.startswith(' -- '), lines))[0].rstrip()) else: print('Attempting to get version number from git...') git = Popen(['git', 'describe', '--abbrev=4', '--dirty'], stdout=PIPE, stderr=sys.stderr) if git.wait() != 0: raise OSError line = git.stdout.readlines()[0] __version__ = line.strip().decode('ascii') __release_date__ = datetime.now().strftime('%b %d %Y, %H:%M:%S') with open(VERSION_FILE, 'w') as version_file: version_file.write("__version__ = '{0}'\n".format(__version__)) version_file.write("__release_date__ = '{0}'\n".format(__release_date__)) except OSError as e: print('Assume we are running from a source distribution.') # read version from VERSION_FILE if os.path.exists(VERSION_FILE): with open(VERSION_FILE) as version_file: code = compile(version_file.read(), VERSION_FILE, 'exec') exec(code) else: __version__ = '0.unknown' with open('README.md') as file: README = file.read() def find_packages(path, prefix): yield prefix prefix = prefix + "." for _, name, ispkg in walk_packages(path, prefix): if ispkg: yield name setup( name=PACKAGE, version=__version__, packages=list(find_packages([PACKAGE_ABSPATH], PACKAGE)), # package_data={ # PACKAGE: [ # 'tests/envs/nolxml/lxml.py', # 'tests/envs/stubbed/README.txt', # 'tests/envs/stubbed/due.py', # 'tests/envs/stubbed/script.py', # ] # }, scripts=[], install_requires=['requests', 'citeproc-py'], include_package_data=True, provides=[PACKAGE], #test_suite='nose.collector', entry_points={ 'console_scripts': [ 'duecredit=duecredit.cmdline.main:main', ], }, author='Yaroslav Halchenko, Matteo Visconti di Oleggio Castello', author_email='yoh@onerussian.com', description='Publications (and donations) tracer', long_description="""\ duecredit is being conceived to address the problem of inadequate citation of scientific software and methods, and limited visibility of donation requests for open-source software. It provides a simple framework (at the moment for Python only) to embed publication or other references in the original code so they are automatically collected and reported to the user at the necessary level of reference detail, i.e. only references for actually used functionality will be presented back if software provides multiple citeable implementations. To get a sense of what duecredit is about, run for example shipped along example script, or your analysis script with `-m duecredit`, e.g. python -m duecredit examples/example_scipy.py """, url='https://github.com/duecredit/duecredit', # Download URL will point to the latest release, thus suffixes removed download_url='https://github.com/duecredit/duecredit/releases/tag/%s' % re.sub('-.*$', '', __version__), keywords=['citation tracing',], license='2-clause BSD License', classifiers=[ 'Development Status :: 3 - Alpha', 'Environment :: Console', 'Environment :: Other Environment', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'Intended Audience :: Education', 'Intended Audience :: End Users/Desktop', 'Intended Audience :: Legal Industry', 'Intended Audience :: Other Audience', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Topic :: Documentation', 'Topic :: Printing', 'Topic :: Software Development :: Documentation', 'Topic :: Software Development :: Libraries :: Python Modules', ] )