├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── pygdc ├── __init__.py ├── _version.py ├── api.py ├── cohort.py ├── fields.py ├── filters.py └── urls.py ├── requirements.txt ├── setup.cfg ├── setup.py └── versioneer.py /.gitattributes: -------------------------------------------------------------------------------- 1 | pygdc/_version.py export-subst 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | env 2 | venv 3 | *.pyc 4 | .ipynb_checkpoints 5 | cache 6 | ENV.sh 7 | .coverage 8 | generated/ 9 | .idea/ 10 | cohorts.egg-info/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | python: 4 | - '2.7' 5 | - '3.4' 6 | before_install: 7 | - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh 8 | -O miniconda.sh; else wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh 9 | -O miniconda.sh; fi 10 | - bash miniconda.sh -b -p $HOME/miniconda 11 | - export PATH="$HOME/miniconda/bin:$PATH" 12 | - hash -r 13 | - conda config --set always_yes yes --set changeps1 no 14 | - conda update -q conda 15 | - conda info -a 16 | install: 17 | - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION numpy scipy nose pandas 18 | - source activate test-environment 19 | - conda install -c bioconda pysam=0.9.0 20 | - conda install -c conda-forge pypandoc 21 | - pip install -r requirements.txt 22 | - pip install . 23 | script: nosetests 24 | after_success: coveralls 25 | deploy: 26 | provider: pypi 27 | user: hammerlab 28 | password: 29 | secure: N9c2l5GF7J77K7Nq9vWNRPI/WrX5PQwF2p0EkBnIKxni/ReB43A/HxVMSgRpdTimgx5DoZvbf/kVEyRm7zi6z0lv072D7tdDSN9O8836Obrs4yt1A6X2wvNRdA/KEcqseuqVns1eTK7YTkiftbtK0yBQIl7MCWnJuzA8UIvA+B+zpLsF1LIOhndo2RmNSftSD6wHahjJ8u9vznZ1TAkdPef1JOp4VZaWWKMRmd7MCb+i/Hq9e/prau6ghMARJ98eatY92omtWRPb+qZ4jfNAuKqsn67ck/e+WpeAO3jgzEaLrWjyjT2unjLmDFA673KhclBETBmy+TCs8wWpOkhJIl3zZE9UCYiS4XwRkX7j6/ZppQuvo8touc4HyKSxAriz8C6hX3ePJf3EAs+3n7VOBTCt/owrArxQQRHNt5V9cBawJDfpgmkdRtTB4y3bjM3u2QNErEZSHbYVuTwn2O3ib/Y+sQK5+B/0JvlZuEtAKrI6TvR87uoOq/DA92qiFTNAaIMgAJmj9/DFBlvPaRJQqZduMMW31ayzCOt3IX7fVcELHoabUdYw/4HVFjNdCmHET6uS++qRrtl3HzOg9zxDCWyD+u42BaxfDAwIo18Zpd/NjvrieczUjIvEE7fuxlYWXk0vSvetTeizgqVIJquWGowMFzMnnqmzDerBUxJYYNk= 30 | on: 31 | branch: master 32 | tags: true 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include versioneer.py 2 | include pygdc/_version.py 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyGDC 2 | 3 | Python API for accessing Genomic Data Commons 4 | 5 | ### Basic API 6 | 7 | #### Cases 8 | ```python 9 | from pygdc import get_cases 10 | from pygdc.filters import equals_filter 11 | 12 | 13 | results = get_cases( 14 | filters=equals_filter('project.primary_site', 'Lung'), 15 | fields='demographic.gender,project.disease_type', 16 | max_results=50 17 | ) 18 | ``` 19 | 20 | ### Cohorts Example 21 | 22 | Using [cohorts](http://github.com/hammerlab/cohorts) 23 | 24 | ```python 25 | import pygdc 26 | 27 | pygdc.build_cohort( 28 | primary_site='Lung', 29 | cohort_cache_dir='cache-dir') 30 | ``` 31 | -------------------------------------------------------------------------------- /pygdc/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import get_cases, get_files, get_projects 2 | from .cohort import build_cohort 3 | from ._version import get_versions 4 | __version__ = get_versions()['version'] 5 | del get_versions 6 | -------------------------------------------------------------------------------- /pygdc/_version.py: -------------------------------------------------------------------------------- 1 | 2 | # This file helps to compute a version number in source trees obtained from 3 | # git-archive tarball (such as those provided by githubs download-from-tag 4 | # feature). Distribution tarballs (built by setup.py sdist) and build 5 | # directories (produced by setup.py build) will contain a much shorter file 6 | # that just contains the computed version number. 7 | 8 | # This file is released into the public domain. Generated by 9 | # versioneer-0.16 (https://github.com/warner/python-versioneer) 10 | 11 | """Git implementation of _version.py.""" 12 | 13 | import errno 14 | import os 15 | import re 16 | import subprocess 17 | import sys 18 | 19 | 20 | def get_keywords(): 21 | """Get the keywords needed to look up the version information.""" 22 | # these strings will be replaced by git during git-archive. 23 | # setup.py/versioneer.py will grep for the variable names, so they must 24 | # each be defined on a line of their own. _version.py will just call 25 | # get_keywords(). 26 | git_refnames = " (HEAD -> master, tag: 0.0.2)" 27 | git_full = "2a9a1a50c39d4c3390cb71a3ee9a62af4a83bccb" 28 | keywords = {"refnames": git_refnames, "full": git_full} 29 | return keywords 30 | 31 | 32 | class VersioneerConfig: 33 | """Container for Versioneer configuration parameters.""" 34 | 35 | 36 | def get_config(): 37 | """Create, populate and return the VersioneerConfig() object.""" 38 | # these strings are filled in when 'setup.py versioneer' creates 39 | # _version.py 40 | cfg = VersioneerConfig() 41 | cfg.VCS = "git" 42 | cfg.style = "pep440" 43 | cfg.tag_prefix = "" 44 | cfg.parentdir_prefix = "None" 45 | cfg.versionfile_source = "pygdc/_version.py" 46 | cfg.verbose = False 47 | return cfg 48 | 49 | 50 | class NotThisMethod(Exception): 51 | """Exception raised if a method is not valid for the current scenario.""" 52 | 53 | 54 | LONG_VERSION_PY = {} 55 | HANDLERS = {} 56 | 57 | 58 | def register_vcs_handler(vcs, method): # decorator 59 | """Decorator to mark a method as the handler for a particular VCS.""" 60 | def decorate(f): 61 | """Store f in HANDLERS[vcs][method].""" 62 | if vcs not in HANDLERS: 63 | HANDLERS[vcs] = {} 64 | HANDLERS[vcs][method] = f 65 | return f 66 | return decorate 67 | 68 | 69 | def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): 70 | """Call the given command(s).""" 71 | assert isinstance(commands, list) 72 | p = None 73 | for c in commands: 74 | try: 75 | dispcmd = str([c] + args) 76 | # remember shell=False, so use git.cmd on windows, not just git 77 | p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, 78 | stderr=(subprocess.PIPE if hide_stderr 79 | else None)) 80 | break 81 | except EnvironmentError: 82 | e = sys.exc_info()[1] 83 | if e.errno == errno.ENOENT: 84 | continue 85 | if verbose: 86 | print("unable to run %s" % dispcmd) 87 | print(e) 88 | return None 89 | else: 90 | if verbose: 91 | print("unable to find command, tried %s" % (commands,)) 92 | return None 93 | stdout = p.communicate()[0].strip() 94 | if sys.version_info[0] >= 3: 95 | stdout = stdout.decode() 96 | if p.returncode != 0: 97 | if verbose: 98 | print("unable to run %s (error)" % dispcmd) 99 | return None 100 | return stdout 101 | 102 | 103 | def versions_from_parentdir(parentdir_prefix, root, verbose): 104 | """Try to determine the version from the parent directory name. 105 | 106 | Source tarballs conventionally unpack into a directory that includes 107 | both the project name and a version string. 108 | """ 109 | dirname = os.path.basename(root) 110 | if not dirname.startswith(parentdir_prefix): 111 | if verbose: 112 | print("guessing rootdir is '%s', but '%s' doesn't start with " 113 | "prefix '%s'" % (root, dirname, parentdir_prefix)) 114 | raise NotThisMethod("rootdir doesn't start with parentdir_prefix") 115 | return {"version": dirname[len(parentdir_prefix):], 116 | "full-revisionid": None, 117 | "dirty": False, "error": None} 118 | 119 | 120 | @register_vcs_handler("git", "get_keywords") 121 | def git_get_keywords(versionfile_abs): 122 | """Extract version information from the given file.""" 123 | # the code embedded in _version.py can just fetch the value of these 124 | # keywords. When used from setup.py, we don't want to import _version.py, 125 | # so we do it with a regexp instead. This function is not used from 126 | # _version.py. 127 | keywords = {} 128 | try: 129 | f = open(versionfile_abs, "r") 130 | for line in f.readlines(): 131 | if line.strip().startswith("git_refnames ="): 132 | mo = re.search(r'=\s*"(.*)"', line) 133 | if mo: 134 | keywords["refnames"] = mo.group(1) 135 | if line.strip().startswith("git_full ="): 136 | mo = re.search(r'=\s*"(.*)"', line) 137 | if mo: 138 | keywords["full"] = mo.group(1) 139 | f.close() 140 | except EnvironmentError: 141 | pass 142 | return keywords 143 | 144 | 145 | @register_vcs_handler("git", "keywords") 146 | def git_versions_from_keywords(keywords, tag_prefix, verbose): 147 | """Get version information from git keywords.""" 148 | if not keywords: 149 | raise NotThisMethod("no keywords at all, weird") 150 | refnames = keywords["refnames"].strip() 151 | if refnames.startswith("$Format"): 152 | if verbose: 153 | print("keywords are unexpanded, not using") 154 | raise NotThisMethod("unexpanded keywords, not a git-archive tarball") 155 | refs = set([r.strip() for r in refnames.strip("()").split(",")]) 156 | # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of 157 | # just "foo-1.0". If we see a "tag: " prefix, prefer those. 158 | TAG = "tag: " 159 | tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) 160 | if not tags: 161 | # Either we're using git < 1.8.3, or there really are no tags. We use 162 | # a heuristic: assume all version tags have a digit. The old git %d 163 | # expansion behaves like git log --decorate=short and strips out the 164 | # refs/heads/ and refs/tags/ prefixes that would let us distinguish 165 | # between branches and tags. By ignoring refnames without digits, we 166 | # filter out many common branch names like "release" and 167 | # "stabilization", as well as "HEAD" and "master". 168 | tags = set([r for r in refs if re.search(r'\d', r)]) 169 | if verbose: 170 | print("discarding '%s', no digits" % ",".join(refs-tags)) 171 | if verbose: 172 | print("likely tags: %s" % ",".join(sorted(tags))) 173 | for ref in sorted(tags): 174 | # sorting will prefer e.g. "2.0" over "2.0rc1" 175 | if ref.startswith(tag_prefix): 176 | r = ref[len(tag_prefix):] 177 | if verbose: 178 | print("picking %s" % r) 179 | return {"version": r, 180 | "full-revisionid": keywords["full"].strip(), 181 | "dirty": False, "error": None 182 | } 183 | # no suitable tags, so version is "0+unknown", but full hex is still there 184 | if verbose: 185 | print("no suitable tags, using unknown + full revision id") 186 | return {"version": "0+unknown", 187 | "full-revisionid": keywords["full"].strip(), 188 | "dirty": False, "error": "no suitable tags"} 189 | 190 | 191 | @register_vcs_handler("git", "pieces_from_vcs") 192 | def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): 193 | """Get version from 'git describe' in the root of the source tree. 194 | 195 | This only gets called if the git-archive 'subst' keywords were *not* 196 | expanded, and _version.py hasn't already been rewritten with a short 197 | version string, meaning we're inside a checked out source tree. 198 | """ 199 | if not os.path.exists(os.path.join(root, ".git")): 200 | if verbose: 201 | print("no .git in %s" % root) 202 | raise NotThisMethod("no .git directory") 203 | 204 | GITS = ["git"] 205 | if sys.platform == "win32": 206 | GITS = ["git.cmd", "git.exe"] 207 | # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] 208 | # if there isn't one, this yields HEX[-dirty] (no NUM) 209 | describe_out = run_command(GITS, ["describe", "--tags", "--dirty", 210 | "--always", "--long", 211 | "--match", "%s*" % tag_prefix], 212 | cwd=root) 213 | # --long was added in git-1.5.5 214 | if describe_out is None: 215 | raise NotThisMethod("'git describe' failed") 216 | describe_out = describe_out.strip() 217 | full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) 218 | if full_out is None: 219 | raise NotThisMethod("'git rev-parse' failed") 220 | full_out = full_out.strip() 221 | 222 | pieces = {} 223 | pieces["long"] = full_out 224 | pieces["short"] = full_out[:7] # maybe improved later 225 | pieces["error"] = None 226 | 227 | # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] 228 | # TAG might have hyphens. 229 | git_describe = describe_out 230 | 231 | # look for -dirty suffix 232 | dirty = git_describe.endswith("-dirty") 233 | pieces["dirty"] = dirty 234 | if dirty: 235 | git_describe = git_describe[:git_describe.rindex("-dirty")] 236 | 237 | # now we have TAG-NUM-gHEX or HEX 238 | 239 | if "-" in git_describe: 240 | # TAG-NUM-gHEX 241 | mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) 242 | if not mo: 243 | # unparseable. Maybe git-describe is misbehaving? 244 | pieces["error"] = ("unable to parse git-describe output: '%s'" 245 | % describe_out) 246 | return pieces 247 | 248 | # tag 249 | full_tag = mo.group(1) 250 | if not full_tag.startswith(tag_prefix): 251 | if verbose: 252 | fmt = "tag '%s' doesn't start with prefix '%s'" 253 | print(fmt % (full_tag, tag_prefix)) 254 | pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" 255 | % (full_tag, tag_prefix)) 256 | return pieces 257 | pieces["closest-tag"] = full_tag[len(tag_prefix):] 258 | 259 | # distance: number of commits since tag 260 | pieces["distance"] = int(mo.group(2)) 261 | 262 | # commit: short hex revision ID 263 | pieces["short"] = mo.group(3) 264 | 265 | else: 266 | # HEX: no tags 267 | pieces["closest-tag"] = None 268 | count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], 269 | cwd=root) 270 | pieces["distance"] = int(count_out) # total number of commits 271 | 272 | return pieces 273 | 274 | 275 | def plus_or_dot(pieces): 276 | """Return a + if we don't already have one, else return a .""" 277 | if "+" in pieces.get("closest-tag", ""): 278 | return "." 279 | return "+" 280 | 281 | 282 | def render_pep440(pieces): 283 | """Build up version string, with post-release "local version identifier". 284 | 285 | Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you 286 | get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty 287 | 288 | Exceptions: 289 | 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] 290 | """ 291 | if pieces["closest-tag"]: 292 | rendered = pieces["closest-tag"] 293 | if pieces["distance"] or pieces["dirty"]: 294 | rendered += plus_or_dot(pieces) 295 | rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) 296 | if pieces["dirty"]: 297 | rendered += ".dirty" 298 | else: 299 | # exception #1 300 | rendered = "0+untagged.%d.g%s" % (pieces["distance"], 301 | pieces["short"]) 302 | if pieces["dirty"]: 303 | rendered += ".dirty" 304 | return rendered 305 | 306 | 307 | def render_pep440_pre(pieces): 308 | """TAG[.post.devDISTANCE] -- No -dirty. 309 | 310 | Exceptions: 311 | 1: no tags. 0.post.devDISTANCE 312 | """ 313 | if pieces["closest-tag"]: 314 | rendered = pieces["closest-tag"] 315 | if pieces["distance"]: 316 | rendered += ".post.dev%d" % pieces["distance"] 317 | else: 318 | # exception #1 319 | rendered = "0.post.dev%d" % pieces["distance"] 320 | return rendered 321 | 322 | 323 | def render_pep440_post(pieces): 324 | """TAG[.postDISTANCE[.dev0]+gHEX] . 325 | 326 | The ".dev0" means dirty. Note that .dev0 sorts backwards 327 | (a dirty tree will appear "older" than the corresponding clean one), 328 | but you shouldn't be releasing software with -dirty anyways. 329 | 330 | Exceptions: 331 | 1: no tags. 0.postDISTANCE[.dev0] 332 | """ 333 | if pieces["closest-tag"]: 334 | rendered = pieces["closest-tag"] 335 | if pieces["distance"] or pieces["dirty"]: 336 | rendered += ".post%d" % pieces["distance"] 337 | if pieces["dirty"]: 338 | rendered += ".dev0" 339 | rendered += plus_or_dot(pieces) 340 | rendered += "g%s" % pieces["short"] 341 | else: 342 | # exception #1 343 | rendered = "0.post%d" % pieces["distance"] 344 | if pieces["dirty"]: 345 | rendered += ".dev0" 346 | rendered += "+g%s" % pieces["short"] 347 | return rendered 348 | 349 | 350 | def render_pep440_old(pieces): 351 | """TAG[.postDISTANCE[.dev0]] . 352 | 353 | The ".dev0" means dirty. 354 | 355 | Eexceptions: 356 | 1: no tags. 0.postDISTANCE[.dev0] 357 | """ 358 | if pieces["closest-tag"]: 359 | rendered = pieces["closest-tag"] 360 | if pieces["distance"] or pieces["dirty"]: 361 | rendered += ".post%d" % pieces["distance"] 362 | if pieces["dirty"]: 363 | rendered += ".dev0" 364 | else: 365 | # exception #1 366 | rendered = "0.post%d" % pieces["distance"] 367 | if pieces["dirty"]: 368 | rendered += ".dev0" 369 | return rendered 370 | 371 | 372 | def render_git_describe(pieces): 373 | """TAG[-DISTANCE-gHEX][-dirty]. 374 | 375 | Like 'git describe --tags --dirty --always'. 376 | 377 | Exceptions: 378 | 1: no tags. HEX[-dirty] (note: no 'g' prefix) 379 | """ 380 | if pieces["closest-tag"]: 381 | rendered = pieces["closest-tag"] 382 | if pieces["distance"]: 383 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) 384 | else: 385 | # exception #1 386 | rendered = pieces["short"] 387 | if pieces["dirty"]: 388 | rendered += "-dirty" 389 | return rendered 390 | 391 | 392 | def render_git_describe_long(pieces): 393 | """TAG-DISTANCE-gHEX[-dirty]. 394 | 395 | Like 'git describe --tags --dirty --always -long'. 396 | The distance/hash is unconditional. 397 | 398 | Exceptions: 399 | 1: no tags. HEX[-dirty] (note: no 'g' prefix) 400 | """ 401 | if pieces["closest-tag"]: 402 | rendered = pieces["closest-tag"] 403 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) 404 | else: 405 | # exception #1 406 | rendered = pieces["short"] 407 | if pieces["dirty"]: 408 | rendered += "-dirty" 409 | return rendered 410 | 411 | 412 | def render(pieces, style): 413 | """Render the given version pieces into the requested style.""" 414 | if pieces["error"]: 415 | return {"version": "unknown", 416 | "full-revisionid": pieces.get("long"), 417 | "dirty": None, 418 | "error": pieces["error"]} 419 | 420 | if not style or style == "default": 421 | style = "pep440" # the default 422 | 423 | if style == "pep440": 424 | rendered = render_pep440(pieces) 425 | elif style == "pep440-pre": 426 | rendered = render_pep440_pre(pieces) 427 | elif style == "pep440-post": 428 | rendered = render_pep440_post(pieces) 429 | elif style == "pep440-old": 430 | rendered = render_pep440_old(pieces) 431 | elif style == "git-describe": 432 | rendered = render_git_describe(pieces) 433 | elif style == "git-describe-long": 434 | rendered = render_git_describe_long(pieces) 435 | else: 436 | raise ValueError("unknown style '%s'" % style) 437 | 438 | return {"version": rendered, "full-revisionid": pieces["long"], 439 | "dirty": pieces["dirty"], "error": None} 440 | 441 | 442 | def get_versions(): 443 | """Get version information or return default if unable to do so.""" 444 | # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have 445 | # __file__, we can work backwards from there to the root. Some 446 | # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which 447 | # case we can only use expanded keywords. 448 | 449 | cfg = get_config() 450 | verbose = cfg.verbose 451 | 452 | try: 453 | return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, 454 | verbose) 455 | except NotThisMethod: 456 | pass 457 | 458 | try: 459 | root = os.path.realpath(__file__) 460 | # versionfile_source is the relative path from the top of the source 461 | # tree (where the .git directory might live) to this file. Invert 462 | # this to find the root from __file__. 463 | for i in cfg.versionfile_source.split('/'): 464 | root = os.path.dirname(root) 465 | except NameError: 466 | return {"version": "0+unknown", "full-revisionid": None, 467 | "dirty": None, 468 | "error": "unable to find root of source tree"} 469 | 470 | try: 471 | pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) 472 | return render(pieces, cfg.style) 473 | except NotThisMethod: 474 | pass 475 | 476 | try: 477 | if cfg.parentdir_prefix: 478 | return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) 479 | except NotThisMethod: 480 | pass 481 | 482 | return {"version": "0+unknown", "full-revisionid": None, 483 | "dirty": None, 484 | "error": "unable to compute version"} 485 | -------------------------------------------------------------------------------- /pygdc/api.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | import pandas as pd 4 | import requests 5 | 6 | from .urls import GDC_API_URL 7 | 8 | def _endpoint_url(endpoint): 9 | assert endpoint in set(['projects', 'files', 'cases']) 10 | return GDC_API_URL + '/' + endpoint 11 | 12 | def _get_endpoint(endpoint, filters, fields, sort, start=1, max_results=None): 13 | logging.info("Making request to endpoint {}".format(endpoint)) 14 | 15 | params = {'fields': fields, 'filters': filters, 'from': start, 'sort' : sort} 16 | if max_results: 17 | params['size'] = max_results 18 | 19 | response = requests.get( 20 | url=_endpoint_url(endpoint), 21 | params=params 22 | ) 23 | 24 | logging.info("Request returned with status code {}".format(response.status_code)) 25 | if response.status_code != 200: 26 | raise ValueError(response.text) 27 | 28 | parsed_response = json.loads(response.text)['data'] 29 | return parsed_response 30 | 31 | def _get_endpoint_results(endpoint, filters, fields, sort, max_results=None): 32 | 33 | start = 1 34 | results_count = 0 35 | results = [] 36 | while max_results is None or results_count < max_results: 37 | partial_results = _get_endpoint(endpoint, filters, fields, sort, start, max_results) 38 | 39 | pagination_info = partial_results['pagination'] 40 | total = int(pagination_info['total']) 41 | if max_results is None or max_results > total: 42 | max_results = total 43 | 44 | partial_results_count = int(pagination_info['count']) 45 | start += partial_results_count 46 | results_count += partial_results_count 47 | results += partial_results['hits'] 48 | 49 | logging.info("Result contains {} records".format(len(results))) 50 | 51 | return results 52 | 53 | 54 | def get_cases(filters, fields='', max_results=None): 55 | results = _get_endpoint_results('cases', filters=filters, fields=fields, sort='case_id', max_results=max_results) 56 | return results 57 | 58 | def get_projects(filters, fields=''): 59 | results = _get_endpoint_results('projects', filters=filters, fields=fields, sort='project_id',) 60 | return pd.DataFrame.from_dict(results) 61 | 62 | def get_files(filters, fields='', max_results=None): 63 | results = _get_endpoint_results('files', filters=filters, fields=fields, sort='file_id', max_results=max_results) 64 | return results -------------------------------------------------------------------------------- /pygdc/cohort.py: -------------------------------------------------------------------------------- 1 | from cohorts import Cohort, Patient 2 | import logging 3 | import json 4 | 5 | 6 | from .api import get_cases 7 | from .filters import and_filter, equals_filter 8 | from .fields import CASES_DEFAULT_FIELDS, CASES_DIAGNOSES_FIELDS, CASES_SAMPLE_FIELDS, CASES_EXPOSURE_FIELDS 9 | 10 | def build_cohort(primary_site, 11 | cohort_cache_dir, 12 | disease_type=None, 13 | only_tcga=True, 14 | with_diagnosis=True, 15 | with_samples=False, 16 | with_exposures=False, 17 | additional_fields=[], 18 | **kwargs): 19 | 20 | filters = json.dumps( 21 | and_filter( 22 | equals_filter('project.primary_site', primary_site), 23 | equals_filter('project.diesase_type', disease_type) if disease_type else None, 24 | equals_filter('project.program.name', 'TCGA') if only_tcga else None, 25 | *[equals_filter(k, v) for (k, v) in kwargs.items()] 26 | ) 27 | ) 28 | 29 | filter_fields = ['project.primary_site'] + list(kwargs.keys()) 30 | 31 | # Automatically include the filter fields in the output 32 | fields = list(CASES_DEFAULT_FIELDS) + filter_fields 33 | 34 | if with_diagnosis: 35 | fields += CASES_DIAGNOSES_FIELDS 36 | if with_samples: 37 | fields += CASES_SAMPLE_FIELDS 38 | if with_exposures: 39 | fields += CASES_EXPOSURE_FIELDS 40 | 41 | results = get_cases(filters=filters, fields=','.join(fields)) 42 | 43 | patients = [] 44 | for result in results: 45 | submitter_id = result['submitter_id'] 46 | 47 | if 'diagnoses' in result: 48 | diagnoses = result['diagnoses'] 49 | 50 | assert len(diagnoses) == 1, "Patient {} had multiple diagnoses".format(submitter_id) 51 | 52 | diagnosis = diagnoses[0] 53 | deceased = diagnosis['vital_status'] == 'dead' 54 | progressed = diagnosis['progression_or_recurrence'] == 'progression' 55 | censor_time = diagnosis['days_to_last_follow_up'] 56 | censor_time = censor_time or 0 57 | 58 | os = (diagnosis['days_to_death'] if deceased else censor_time) or 0 # TODO: support NA 59 | pfs = (diagnosis['days_to_recurrence'] if progressed else censor_time) or 0 # TODO: support NA 60 | 61 | patient = Patient( 62 | id=submitter_id, 63 | deceased=deceased, 64 | progressed=progressed, 65 | os=os, 66 | pfs=max(pfs, os), 67 | benefit=False, 68 | additional_data=result, 69 | ) 70 | patients.append(patient) 71 | else: 72 | logging.warn("No diagnoses found for {}".format(submitter_id)) 73 | 74 | cohort = Cohort( 75 | patients=patients, 76 | cache_dir=cohort_cache_dir 77 | ) 78 | 79 | return cohort -------------------------------------------------------------------------------- /pygdc/fields.py: -------------------------------------------------------------------------------- 1 | CASES_FIELDS = [ 2 | 'aliquot_ids', 3 | 'analyte_ids', 4 | 'annotations.annotation_id', 5 | 'annotations.case_id', 6 | 'annotations.case_submitter_id', 7 | 'annotations.category', 8 | 'annotations.classification', 9 | 'annotations.created_datetime', 10 | 'annotations.creator', 11 | 'annotations.entity_id', 12 | 'annotations.entity_submitter_id', 13 | 'annotations.entity_type', 14 | 'annotations.legacy_created_datetime', 15 | 'annotations.legacy_updated_datetime', 16 | 'annotations.notes', 17 | 'annotations.state', 18 | 'annotations.status', 19 | 'annotations.submitter_id', 20 | 'annotations.updated_datetime', 21 | 'case_id', 22 | 'created_datetime', 23 | 'days_to_index', 24 | 'demographic.created_datetime', 25 | 'demographic.demographic_id', 26 | 'demographic.ethnicity', 27 | 'demographic.gender', 28 | 'demographic.race', 29 | 'demographic.state', 30 | 'demographic.submitter_id', 31 | 'demographic.updated_datetime', 32 | 'demographic.year_of_birth', 33 | 'demographic.year_of_death', 34 | 'diagnoses.age_at_diagnosis', 35 | 'diagnoses.classification_of_tumor', 36 | 'diagnoses.created_datetime', 37 | 'diagnoses.days_to_birth', 38 | 'diagnoses.days_to_death', 39 | 'diagnoses.days_to_last_follow_up', 40 | 'diagnoses.days_to_last_known_disease_status', 41 | 'diagnoses.days_to_recurrence', 42 | 'diagnoses.diagnosis_id', 43 | 'diagnoses.last_known_disease_status', 44 | 'diagnoses.morphology', 45 | 'diagnoses.primary_diagnosis', 46 | 'diagnoses.prior_malignancy', 47 | 'diagnoses.progression_or_recurrence', 48 | 'diagnoses.site_of_resection_or_biopsy', 49 | 'diagnoses.state', 50 | 'diagnoses.submitter_id', 51 | 'diagnoses.tissue_or_organ_of_origin', 52 | 'diagnoses.treatments.created_datetime', 53 | 'diagnoses.treatments.days_to_treatment', 54 | 'diagnoses.treatments.state', 55 | 'diagnoses.treatments.submitter_id', 56 | 'diagnoses.treatments.therapeutic_agents', 57 | 'diagnoses.treatments.treatment_id', 58 | 'diagnoses.treatments.treatment_intent_type', 59 | 'diagnoses.treatments.treatment_or_therapy', 60 | 'diagnoses.treatments.updated_datetime', 61 | 'diagnoses.tumor_grade', 62 | 'diagnoses.tumor_stage', 63 | 'diagnoses.updated_datetime', 64 | 'diagnoses.vital_status', 65 | 'exposures.alcohol_history', 66 | 'exposures.alcohol_intensity', 67 | 'exposures.bmi', 68 | 'exposures.cigarettes_per_day', 69 | 'exposures.created_datetime', 70 | 'exposures.exposure_id', 71 | 'exposures.height', 72 | 'exposures.state', 73 | 'exposures.submitter_id', 74 | 'exposures.updated_datetime', 75 | 'exposures.weight', 76 | 'exposures.years_smoked', 77 | 'family_histories.created_datetime', 78 | 'family_histories.family_history_id', 79 | 'family_histories.relationship_age_at_diagnosis', 80 | 'family_histories.relationship_gender', 81 | 'family_histories.relationship_primary_diagnosis', 82 | 'family_histories.relationship_type', 83 | 'family_histories.relative_with_cancer_history', 84 | 'family_histories.state', 85 | 'family_histories.submitter_id', 86 | 'family_histories.updated_datetime', 87 | 'files.access', 88 | 'files.acl', 89 | 'files.analysis.analysis_id', 90 | 'files.analysis.analysis_type', 91 | 'files.analysis.created_datetime', 92 | 'files.analysis.input_files.created_datetime', 93 | 'files.analysis.input_files.data_category', 94 | 'files.analysis.input_files.data_format', 95 | 'files.analysis.input_files.data_type', 96 | 'files.analysis.input_files.error_type', 97 | 'files.analysis.input_files.experimental_strategy', 98 | 'files.analysis.input_files.file_id', 99 | 'files.analysis.input_files.file_name', 100 | 'files.analysis.input_files.file_size', 101 | 'files.analysis.input_files.file_state', 102 | 'files.analysis.input_files.md5sum', 103 | 'files.analysis.input_files.platform', 104 | 'files.analysis.input_files.revision', 105 | 'files.analysis.input_files.state', 106 | 'files.analysis.input_files.state_comment', 107 | 'files.analysis.input_files.submitter_id', 108 | 'files.analysis.input_files.updated_datetime', 109 | 'files.analysis.metadata.read_groups.adapter_name', 110 | 'files.analysis.metadata.read_groups.adapter_sequence', 111 | 'files.analysis.metadata.read_groups.base_caller_name', 112 | 'files.analysis.metadata.read_groups.base_caller_version', 113 | 'files.analysis.metadata.read_groups.created_datetime', 114 | 'files.analysis.metadata.read_groups.experiment_name', 115 | 'files.analysis.metadata.read_groups.flow_cell_barcode', 116 | 'files.analysis.metadata.read_groups.includes_spike_ins', 117 | 'files.analysis.metadata.read_groups.instrument_model', 118 | 'files.analysis.metadata.read_groups.is_paired_end', 119 | 'files.analysis.metadata.read_groups.library_name', 120 | 'files.analysis.metadata.read_groups.library_preparation_kit_catalog_number', 121 | 'files.analysis.metadata.read_groups.library_preparation_kit_name', 122 | 'files.analysis.metadata.read_groups.library_preparation_kit_vendor', 123 | 'files.analysis.metadata.read_groups.library_preparation_kit_version', 124 | 'files.analysis.metadata.read_groups.library_selection', 125 | 'files.analysis.metadata.read_groups.library_strand', 126 | 'files.analysis.metadata.read_groups.library_strategy', 127 | 'files.analysis.metadata.read_groups.platform', 128 | 'files.analysis.metadata.read_groups.read_group_id', 129 | 'files.analysis.metadata.read_groups.read_group_name', 130 | 'files.analysis.metadata.read_groups.read_length', 131 | 'files.analysis.metadata.read_groups.RIN', 132 | 'files.analysis.metadata.read_groups.sequencing_center', 133 | 'files.analysis.metadata.read_groups.sequencing_date', 134 | 'files.analysis.metadata.read_groups.size_selection_range', 135 | 'files.analysis.metadata.read_groups.spike_ins_concentration', 136 | 'files.analysis.metadata.read_groups.spike_ins_fasta', 137 | 'files.analysis.metadata.read_groups.state', 138 | 'files.analysis.metadata.read_groups.submitter_id', 139 | 'files.analysis.metadata.read_groups.target_capture_kit_catalog_number', 140 | 'files.analysis.metadata.read_groups.target_capture_kit_name', 141 | 'files.analysis.metadata.read_groups.target_capture_kit_target_region', 142 | 'files.analysis.metadata.read_groups.target_capture_kit_vendor', 143 | 'files.analysis.metadata.read_groups.target_capture_kit_version', 144 | 'files.analysis.metadata.read_groups.to_trim_adapter_sequence', 145 | 'files.analysis.metadata.read_groups.updated_datetime', 146 | 'files.analysis.state', 147 | 'files.analysis.submitter_id', 148 | 'files.analysis.updated_datetime', 149 | 'files.analysis.workflow_end_datetime', 150 | 'files.analysis.workflow_link', 151 | 'files.analysis.workflow_start_datetime', 152 | 'files.analysis.workflow_type', 153 | 'files.analysis.workflow_version', 154 | 'files.archive.archive_id', 155 | 'files.archive.created_datetime', 156 | 'files.archive.data_category', 157 | 'files.archive.data_format', 158 | 'files.archive.data_type', 159 | 'files.archive.error_type', 160 | 'files.archive.file_name', 161 | 'files.archive.file_size', 162 | 'files.archive.file_state', 163 | 'files.archive.md5sum', 164 | 'files.archive.revision', 165 | 'files.archive.state', 166 | 'files.archive.state_comment', 167 | 'files.archive.submitter_id', 168 | 'files.archive.updated_datetime', 169 | 'files.cases.aliquot_ids', 170 | 'files.cases.analyte_ids', 171 | 'files.cases.annotations.annotation_id', 172 | 'files.cases.annotations.case_id', 173 | 'files.cases.annotations.case_submitter_id', 174 | 'files.cases.annotations.category', 175 | 'files.cases.annotations.classification', 176 | 'files.cases.annotations.created_datetime', 177 | 'files.cases.annotations.creator', 178 | 'files.cases.annotations.entity_id', 179 | 'files.cases.annotations.entity_submitter_id', 180 | 'files.cases.annotations.entity_type', 181 | 'files.cases.annotations.legacy_created_datetime', 182 | 'files.cases.annotations.legacy_updated_datetime', 183 | 'files.cases.annotations.notes', 184 | 'files.cases.annotations.state', 185 | 'files.cases.annotations.status', 186 | 'files.cases.annotations.submitter_id', 187 | 'files.cases.annotations.updated_datetime', 188 | 'files.cases.case_id', 189 | 'files.cases.created_datetime', 190 | 'files.cases.days_to_index', 191 | 'files.cases.demographic.created_datetime', 192 | 'files.cases.demographic.demographic_id', 193 | 'files.cases.demographic.ethnicity', 194 | 'files.cases.demographic.gender', 195 | 'files.cases.demographic.race', 196 | 'files.cases.demographic.state', 197 | 'files.cases.demographic.submitter_id', 198 | 'files.cases.demographic.updated_datetime', 199 | 'files.cases.demographic.year_of_birth', 200 | 'files.cases.demographic.year_of_death', 201 | 'files.cases.diagnoses.age_at_diagnosis', 202 | 'files.cases.diagnoses.classification_of_tumor', 203 | 'files.cases.diagnoses.created_datetime', 204 | 'files.cases.diagnoses.days_to_birth', 205 | 'files.cases.diagnoses.days_to_death', 206 | 'files.cases.diagnoses.days_to_last_follow_up', 207 | 'files.cases.diagnoses.days_to_last_known_disease_status', 208 | 'files.cases.diagnoses.days_to_recurrence', 209 | 'files.cases.diagnoses.diagnosis_id', 210 | 'files.cases.diagnoses.last_known_disease_status', 211 | 'files.cases.diagnoses.morphology', 212 | 'files.cases.diagnoses.primary_diagnosis', 213 | 'files.cases.diagnoses.prior_malignancy', 214 | 'files.cases.diagnoses.progression_or_recurrence', 215 | 'files.cases.diagnoses.site_of_resection_or_biopsy', 216 | 'files.cases.diagnoses.state', 217 | 'files.cases.diagnoses.submitter_id', 218 | 'files.cases.diagnoses.tissue_or_organ_of_origin', 219 | 'files.cases.diagnoses.treatments.created_datetime', 220 | 'files.cases.diagnoses.treatments.days_to_treatment', 221 | 'files.cases.diagnoses.treatments.state', 222 | 'files.cases.diagnoses.treatments.submitter_id', 223 | 'files.cases.diagnoses.treatments.therapeutic_agents', 224 | 'files.cases.diagnoses.treatments.treatment_id', 225 | 'files.cases.diagnoses.treatments.treatment_intent_type', 226 | 'files.cases.diagnoses.treatments.treatment_or_therapy', 227 | 'files.cases.diagnoses.treatments.updated_datetime', 228 | 'files.cases.diagnoses.tumor_grade', 229 | 'files.cases.diagnoses.tumor_stage', 230 | 'files.cases.diagnoses.updated_datetime', 231 | 'files.cases.diagnoses.vital_status', 232 | 'files.cases.exposures.alcohol_history', 233 | 'files.cases.exposures.alcohol_intensity', 234 | 'files.cases.exposures.bmi', 235 | 'files.cases.exposures.cigarettes_per_day', 236 | 'files.cases.exposures.created_datetime', 237 | 'files.cases.exposures.exposure_id', 238 | 'files.cases.exposures.height', 239 | 'files.cases.exposures.state', 240 | 'files.cases.exposures.submitter_id', 241 | 'files.cases.exposures.updated_datetime', 242 | 'files.cases.exposures.weight', 243 | 'files.cases.exposures.years_smoked', 244 | 'files.cases.family_histories.created_datetime', 245 | 'files.cases.family_histories.family_history_id', 246 | 'files.cases.family_histories.relationship_age_at_diagnosis', 247 | 'files.cases.family_histories.relationship_gender', 248 | 'files.cases.family_histories.relationship_primary_diagnosis', 249 | 'files.cases.family_histories.relationship_type', 250 | 'files.cases.family_histories.relative_with_cancer_history', 251 | 'files.cases.family_histories.state', 252 | 'files.cases.family_histories.submitter_id', 253 | 'files.cases.family_histories.updated_datetime', 254 | 'files.cases.files.created_datetime', 255 | 'files.cases.files.error_type', 256 | 'files.cases.files.file_id', 257 | 'files.cases.files.file_name', 258 | 'files.cases.files.file_size', 259 | 'files.cases.files.file_state', 260 | 'files.cases.files.md5sum', 261 | 'files.cases.files.state', 262 | 'files.cases.files.state_comment', 263 | 'files.cases.files.submitter_id', 264 | 'files.cases.files.updated_datetime', 265 | 'files.cases.portion_ids', 266 | 'files.cases.project.dbgap_accession_number', 267 | 'files.cases.project.disease_type', 268 | 'files.cases.project.name', 269 | 'files.cases.project.primary_site', 270 | 'files.cases.project.program.dbgap_accession_number', 271 | 'files.cases.project.program.name', 272 | 'files.cases.project.program.program_id', 273 | 'files.cases.project.project_id', 274 | 'files.cases.project.released', 275 | 'files.cases.project.state', 276 | 'files.cases.sample_ids', 277 | 'files.cases.samples.annotations.annotation_id', 278 | 'files.cases.samples.annotations.case_id', 279 | 'files.cases.samples.annotations.case_submitter_id', 280 | 'files.cases.samples.annotations.category', 281 | 'files.cases.samples.annotations.classification', 282 | 'files.cases.samples.annotations.created_datetime', 283 | 'files.cases.samples.annotations.creator', 284 | 'files.cases.samples.annotations.entity_id', 285 | 'files.cases.samples.annotations.entity_submitter_id', 286 | 'files.cases.samples.annotations.entity_type', 287 | 'files.cases.samples.annotations.legacy_created_datetime', 288 | 'files.cases.samples.annotations.legacy_updated_datetime', 289 | 'files.cases.samples.annotations.notes', 290 | 'files.cases.samples.annotations.state', 291 | 'files.cases.samples.annotations.status', 292 | 'files.cases.samples.annotations.submitter_id', 293 | 'files.cases.samples.annotations.updated_datetime', 294 | 'files.cases.samples.composition', 295 | 'files.cases.samples.created_datetime', 296 | 'files.cases.samples.current_weight', 297 | 'files.cases.samples.days_to_collection', 298 | 'files.cases.samples.days_to_sample_procurement', 299 | 'files.cases.samples.freezing_method', 300 | 'files.cases.samples.initial_weight', 301 | 'files.cases.samples.intermediate_dimension', 302 | 'files.cases.samples.is_ffpe', 303 | 'files.cases.samples.longest_dimension', 304 | 'files.cases.samples.oct_embedded', 305 | 'files.cases.samples.pathology_report_uuid', 306 | 'files.cases.samples.portions.analytes.a260_a280_ratio', 307 | 'files.cases.samples.portions.analytes.aliquots.aliquot_id', 308 | 'files.cases.samples.portions.analytes.aliquots.amount', 309 | 'files.cases.samples.portions.analytes.aliquots.analyte_type', 310 | 'files.cases.samples.portions.analytes.aliquots.analyte_type_id', 311 | 'files.cases.samples.portions.analytes.aliquots.annotations.annotation_id', 312 | 'files.cases.samples.portions.analytes.aliquots.annotations.case_id', 313 | 'files.cases.samples.portions.analytes.aliquots.annotations.case_submitter_id', 314 | 'files.cases.samples.portions.analytes.aliquots.annotations.category', 315 | 'files.cases.samples.portions.analytes.aliquots.annotations.classification', 316 | 'files.cases.samples.portions.analytes.aliquots.annotations.created_datetime', 317 | 'files.cases.samples.portions.analytes.aliquots.annotations.creator', 318 | 'files.cases.samples.portions.analytes.aliquots.annotations.entity_id', 319 | 'files.cases.samples.portions.analytes.aliquots.annotations.entity_submitter_id', 320 | 'files.cases.samples.portions.analytes.aliquots.annotations.entity_type', 321 | 'files.cases.samples.portions.analytes.aliquots.annotations.legacy_created_datetime', 322 | 'files.cases.samples.portions.analytes.aliquots.annotations.legacy_updated_datetime', 323 | 'files.cases.samples.portions.analytes.aliquots.annotations.notes', 324 | 'files.cases.samples.portions.analytes.aliquots.annotations.state', 325 | 'files.cases.samples.portions.analytes.aliquots.annotations.status', 326 | 'files.cases.samples.portions.analytes.aliquots.annotations.submitter_id', 327 | 'files.cases.samples.portions.analytes.aliquots.annotations.updated_datetime', 328 | 'files.cases.samples.portions.analytes.aliquots.center.center_id', 329 | 'files.cases.samples.portions.analytes.aliquots.center.center_type', 330 | 'files.cases.samples.portions.analytes.aliquots.center.code', 331 | 'files.cases.samples.portions.analytes.aliquots.center.name', 332 | 'files.cases.samples.portions.analytes.aliquots.center.namespace', 333 | 'files.cases.samples.portions.analytes.aliquots.center.short_name', 334 | 'files.cases.samples.portions.analytes.aliquots.concentration', 335 | 'files.cases.samples.portions.analytes.aliquots.created_datetime', 336 | 'files.cases.samples.portions.analytes.aliquots.source_center', 337 | 'files.cases.samples.portions.analytes.aliquots.state', 338 | 'files.cases.samples.portions.analytes.aliquots.submitter_id', 339 | 'files.cases.samples.portions.analytes.aliquots.updated_datetime', 340 | 'files.cases.samples.portions.analytes.amount', 341 | 'files.cases.samples.portions.analytes.analyte_id', 342 | 'files.cases.samples.portions.analytes.analyte_type', 343 | 'files.cases.samples.portions.analytes.analyte_type_id', 344 | 'files.cases.samples.portions.analytes.annotations.annotation_id', 345 | 'files.cases.samples.portions.analytes.annotations.case_id', 346 | 'files.cases.samples.portions.analytes.annotations.case_submitter_id', 347 | 'files.cases.samples.portions.analytes.annotations.category', 348 | 'files.cases.samples.portions.analytes.annotations.classification', 349 | 'files.cases.samples.portions.analytes.annotations.created_datetime', 350 | 'files.cases.samples.portions.analytes.annotations.creator', 351 | 'files.cases.samples.portions.analytes.annotations.entity_id', 352 | 'files.cases.samples.portions.analytes.annotations.entity_submitter_id', 353 | 'files.cases.samples.portions.analytes.annotations.entity_type', 354 | 'files.cases.samples.portions.analytes.annotations.legacy_created_datetime', 355 | 'files.cases.samples.portions.analytes.annotations.legacy_updated_datetime', 356 | 'files.cases.samples.portions.analytes.annotations.notes', 357 | 'files.cases.samples.portions.analytes.annotations.state', 358 | 'files.cases.samples.portions.analytes.annotations.status', 359 | 'files.cases.samples.portions.analytes.annotations.submitter_id', 360 | 'files.cases.samples.portions.analytes.annotations.updated_datetime', 361 | 'files.cases.samples.portions.analytes.concentration', 362 | 'files.cases.samples.portions.analytes.created_datetime', 363 | 'files.cases.samples.portions.analytes.spectrophotometer_method', 364 | 'files.cases.samples.portions.analytes.state', 365 | 'files.cases.samples.portions.analytes.submitter_id', 366 | 'files.cases.samples.portions.analytes.updated_datetime', 367 | 'files.cases.samples.portions.analytes.well_number', 368 | 'files.cases.samples.portions.annotations.annotation_id', 369 | 'files.cases.samples.portions.annotations.case_id', 370 | 'files.cases.samples.portions.annotations.case_submitter_id', 371 | 'files.cases.samples.portions.annotations.category', 372 | 'files.cases.samples.portions.annotations.classification', 373 | 'files.cases.samples.portions.annotations.created_datetime', 374 | 'files.cases.samples.portions.annotations.creator', 375 | 'files.cases.samples.portions.annotations.entity_id', 376 | 'files.cases.samples.portions.annotations.entity_submitter_id', 377 | 'files.cases.samples.portions.annotations.entity_type', 378 | 'files.cases.samples.portions.annotations.legacy_created_datetime', 379 | 'files.cases.samples.portions.annotations.legacy_updated_datetime', 380 | 'files.cases.samples.portions.annotations.notes', 381 | 'files.cases.samples.portions.annotations.state', 382 | 'files.cases.samples.portions.annotations.status', 383 | 'files.cases.samples.portions.annotations.submitter_id', 384 | 'files.cases.samples.portions.annotations.updated_datetime', 385 | 'files.cases.samples.portions.center.center_id', 386 | 'files.cases.samples.portions.center.center_type', 387 | 'files.cases.samples.portions.center.code', 388 | 'files.cases.samples.portions.center.name', 389 | 'files.cases.samples.portions.center.namespace', 390 | 'files.cases.samples.portions.center.short_name', 391 | 'files.cases.samples.portions.created_datetime', 392 | 'files.cases.samples.portions.creation_datetime', 393 | 'files.cases.samples.portions.is_ffpe', 394 | 'files.cases.samples.portions.portion_id', 395 | 'files.cases.samples.portions.portion_number', 396 | 'files.cases.samples.portions.slides.annotations.annotation_id', 397 | 'files.cases.samples.portions.slides.annotations.case_id', 398 | 'files.cases.samples.portions.slides.annotations.case_submitter_id', 399 | 'files.cases.samples.portions.slides.annotations.category', 400 | 'files.cases.samples.portions.slides.annotations.classification', 401 | 'files.cases.samples.portions.slides.annotations.created_datetime', 402 | 'files.cases.samples.portions.slides.annotations.creator', 403 | 'files.cases.samples.portions.slides.annotations.entity_id', 404 | 'files.cases.samples.portions.slides.annotations.entity_submitter_id', 405 | 'files.cases.samples.portions.slides.annotations.entity_type', 406 | 'files.cases.samples.portions.slides.annotations.legacy_created_datetime', 407 | 'files.cases.samples.portions.slides.annotations.legacy_updated_datetime', 408 | 'files.cases.samples.portions.slides.annotations.notes', 409 | 'files.cases.samples.portions.slides.annotations.state', 410 | 'files.cases.samples.portions.slides.annotations.status', 411 | 'files.cases.samples.portions.slides.annotations.submitter_id', 412 | 'files.cases.samples.portions.slides.annotations.updated_datetime', 413 | 'files.cases.samples.portions.slides.created_datetime', 414 | 'files.cases.samples.portions.slides.number_proliferating_cells', 415 | 'files.cases.samples.portions.slides.percent_eosinophil_infiltration', 416 | 'files.cases.samples.portions.slides.percent_granulocyte_infiltration', 417 | 'files.cases.samples.portions.slides.percent_inflam_infiltration', 418 | 'files.cases.samples.portions.slides.percent_lymphocyte_infiltration', 419 | 'files.cases.samples.portions.slides.percent_monocyte_infiltration', 420 | 'files.cases.samples.portions.slides.percent_necrosis', 421 | 'files.cases.samples.portions.slides.percent_neutrophil_infiltration', 422 | 'files.cases.samples.portions.slides.percent_normal_cells', 423 | 'files.cases.samples.portions.slides.percent_stromal_cells', 424 | 'files.cases.samples.portions.slides.percent_tumor_cells', 425 | 'files.cases.samples.portions.slides.percent_tumor_nuclei', 426 | 'files.cases.samples.portions.slides.section_location', 427 | 'files.cases.samples.portions.slides.slide_id', 428 | 'files.cases.samples.portions.slides.state', 429 | 'files.cases.samples.portions.slides.submitter_id', 430 | 'files.cases.samples.portions.slides.updated_datetime', 431 | 'files.cases.samples.portions.state', 432 | 'files.cases.samples.portions.submitter_id', 433 | 'files.cases.samples.portions.updated_datetime', 434 | 'files.cases.samples.portions.weight', 435 | 'files.cases.samples.preservation_method', 436 | 'files.cases.samples.sample_id', 437 | 'files.cases.samples.sample_type', 438 | 'files.cases.samples.sample_type_id', 439 | 'files.cases.samples.shortest_dimension', 440 | 'files.cases.samples.state', 441 | 'files.cases.samples.submitter_id', 442 | 'files.cases.samples.time_between_clamping_and_freezing', 443 | 'files.cases.samples.time_between_excision_and_freezing', 444 | 'files.cases.samples.tissue_type', 445 | 'files.cases.samples.tumor_code', 446 | 'files.cases.samples.tumor_code_id', 447 | 'files.cases.samples.tumor_descriptor', 448 | 'files.cases.samples.updated_datetime', 449 | 'files.cases.slide_ids', 450 | 'files.cases.state', 451 | 'files.cases.submitter_aliquot_ids', 452 | 'files.cases.submitter_analyte_ids', 453 | 'files.cases.submitter_id', 454 | 'files.cases.submitter_portion_ids', 455 | 'files.cases.submitter_sample_ids', 456 | 'files.cases.submitter_slide_ids', 457 | 'files.cases.summary.data_categories.data_category', 458 | 'files.cases.summary.data_categories.file_count', 459 | 'files.cases.summary.experimental_strategies.experimental_strategy', 460 | 'files.cases.summary.experimental_strategies.file_count', 461 | 'files.cases.summary.file_count', 462 | 'files.cases.summary.file_size', 463 | 'files.cases.tissue_source_site.bcr_id', 464 | 'files.cases.tissue_source_site.code', 465 | 'files.cases.tissue_source_site.name', 466 | 'files.cases.tissue_source_site.project', 467 | 'files.cases.tissue_source_site.tissue_source_site_id', 468 | 'files.cases.updated_datetime', 469 | 'files.center.center_id', 470 | 'files.center.center_type', 471 | 'files.center.code', 472 | 'files.center.name', 473 | 'files.center.namespace', 474 | 'files.center.short_name', 475 | 'files.created_datetime', 476 | 'files.data_category', 477 | 'files.data_format', 478 | 'files.data_type', 479 | 'files.downstream_analyses.analysis_id', 480 | 'files.downstream_analyses.analysis_type', 481 | 'files.downstream_analyses.created_datetime', 482 | 'files.downstream_analyses.output_files.created_datetime', 483 | 'files.downstream_analyses.output_files.data_category', 484 | 'files.downstream_analyses.output_files.data_format', 485 | 'files.downstream_analyses.output_files.data_type', 486 | 'files.downstream_analyses.output_files.error_type', 487 | 'files.downstream_analyses.output_files.experimental_strategy', 488 | 'files.downstream_analyses.output_files.file_id', 489 | 'files.downstream_analyses.output_files.file_name', 490 | 'files.downstream_analyses.output_files.file_size', 491 | 'files.downstream_analyses.output_files.file_state', 492 | 'files.downstream_analyses.output_files.md5sum', 493 | 'files.downstream_analyses.output_files.platform', 494 | 'files.downstream_analyses.output_files.revision', 495 | 'files.downstream_analyses.output_files.state', 496 | 'files.downstream_analyses.output_files.state_comment', 497 | 'files.downstream_analyses.output_files.submitter_id', 498 | 'files.downstream_analyses.output_files.updated_datetime', 499 | 'files.downstream_analyses.state', 500 | 'files.downstream_analyses.submitter_id', 501 | 'files.downstream_analyses.updated_datetime', 502 | 'files.downstream_analyses.workflow_end_datetime', 503 | 'files.downstream_analyses.workflow_link', 504 | 'files.downstream_analyses.workflow_start_datetime', 505 | 'files.downstream_analyses.workflow_type', 506 | 'files.downstream_analyses.workflow_version', 507 | 'files.error_type', 508 | 'files.experimental_strategy', 509 | 'files.file_id', 510 | 'files.file_name', 511 | 'files.file_size', 512 | 'files.file_state', 513 | 'files.index_files.created_datetime', 514 | 'files.index_files.data_category', 515 | 'files.index_files.data_format', 516 | 'files.index_files.data_type', 517 | 'files.index_files.error_type', 518 | 'files.index_files.experimental_strategy', 519 | 'files.index_files.file_id', 520 | 'files.index_files.file_name', 521 | 'files.index_files.file_size', 522 | 'files.index_files.file_state', 523 | 'files.index_files.md5sum', 524 | 'files.index_files.platform', 525 | 'files.index_files.revision', 526 | 'files.index_files.state', 527 | 'files.index_files.state_comment', 528 | 'files.index_files.submitter_id', 529 | 'files.index_files.updated_datetime', 530 | 'files.md5sum', 531 | 'files.metadata_files.access', 532 | 'files.metadata_files.created_datetime', 533 | 'files.metadata_files.data_category', 534 | 'files.metadata_files.data_format', 535 | 'files.metadata_files.data_type', 536 | 'files.metadata_files.error_type', 537 | 'files.metadata_files.file_id', 538 | 'files.metadata_files.file_name', 539 | 'files.metadata_files.file_size', 540 | 'files.metadata_files.file_state', 541 | 'files.metadata_files.md5sum', 542 | 'files.metadata_files.state', 543 | 'files.metadata_files.state_comment', 544 | 'files.metadata_files.submitter_id', 545 | 'files.metadata_files.type', 546 | 'files.metadata_files.updated_datetime', 547 | 'files.origin', 548 | 'files.platform', 549 | 'files.revision', 550 | 'files.state', 551 | 'files.state_comment', 552 | 'files.submitter_id', 553 | 'files.tags', 554 | 'files.type', 555 | 'files.updated_datetime', 556 | 'portion_ids', 557 | 'project.dbgap_accession_number', 558 | 'project.disease_type', 559 | 'project.name', 560 | 'project.primary_site', 561 | 'project.program.dbgap_accession_number', 562 | 'project.program.name', 563 | 'project.program.program_id', 564 | 'project.project_id', 565 | 'project.released', 566 | 'project.state', 567 | 'sample_ids', 568 | 'samples.annotations.annotation_id', 569 | 'samples.annotations.case_id', 570 | 'samples.annotations.case_submitter_id', 571 | 'samples.annotations.category', 572 | 'samples.annotations.classification', 573 | 'samples.annotations.created_datetime', 574 | 'samples.annotations.creator', 575 | 'samples.annotations.entity_id', 576 | 'samples.annotations.entity_submitter_id', 577 | 'samples.annotations.entity_type', 578 | 'samples.annotations.legacy_created_datetime', 579 | 'samples.annotations.legacy_updated_datetime', 580 | 'samples.annotations.notes', 581 | 'samples.annotations.state', 582 | 'samples.annotations.status', 583 | 'samples.annotations.submitter_id', 584 | 'samples.annotations.updated_datetime', 585 | 'samples.composition', 586 | 'samples.created_datetime', 587 | 'samples.current_weight', 588 | 'samples.days_to_collection', 589 | 'samples.days_to_sample_procurement', 590 | 'samples.freezing_method', 591 | 'samples.initial_weight', 592 | 'samples.intermediate_dimension', 593 | 'samples.is_ffpe', 594 | 'samples.longest_dimension', 595 | 'samples.oct_embedded', 596 | 'samples.pathology_report_uuid', 597 | 'samples.portions.analytes.a260_a280_ratio', 598 | 'samples.portions.analytes.aliquots.aliquot_id', 599 | 'samples.portions.analytes.aliquots.amount', 600 | 'samples.portions.analytes.aliquots.analyte_type', 601 | 'samples.portions.analytes.aliquots.analyte_type_id', 602 | 'samples.portions.analytes.aliquots.annotations.annotation_id', 603 | 'samples.portions.analytes.aliquots.annotations.case_id', 604 | 'samples.portions.analytes.aliquots.annotations.case_submitter_id', 605 | 'samples.portions.analytes.aliquots.annotations.category', 606 | 'samples.portions.analytes.aliquots.annotations.classification', 607 | 'samples.portions.analytes.aliquots.annotations.created_datetime', 608 | 'samples.portions.analytes.aliquots.annotations.creator', 609 | 'samples.portions.analytes.aliquots.annotations.entity_id', 610 | 'samples.portions.analytes.aliquots.annotations.entity_submitter_id', 611 | 'samples.portions.analytes.aliquots.annotations.entity_type', 612 | 'samples.portions.analytes.aliquots.annotations.legacy_created_datetime', 613 | 'samples.portions.analytes.aliquots.annotations.legacy_updated_datetime', 614 | 'samples.portions.analytes.aliquots.annotations.notes', 615 | 'samples.portions.analytes.aliquots.annotations.state', 616 | 'samples.portions.analytes.aliquots.annotations.status', 617 | 'samples.portions.analytes.aliquots.annotations.submitter_id', 618 | 'samples.portions.analytes.aliquots.annotations.updated_datetime', 619 | 'samples.portions.analytes.aliquots.center.center_id', 620 | 'samples.portions.analytes.aliquots.center.center_type', 621 | 'samples.portions.analytes.aliquots.center.code', 622 | 'samples.portions.analytes.aliquots.center.name', 623 | 'samples.portions.analytes.aliquots.center.namespace', 624 | 'samples.portions.analytes.aliquots.center.short_name', 625 | 'samples.portions.analytes.aliquots.concentration', 626 | 'samples.portions.analytes.aliquots.created_datetime', 627 | 'samples.portions.analytes.aliquots.source_center', 628 | 'samples.portions.analytes.aliquots.state', 629 | 'samples.portions.analytes.aliquots.submitter_id', 630 | 'samples.portions.analytes.aliquots.updated_datetime', 631 | 'samples.portions.analytes.amount', 632 | 'samples.portions.analytes.analyte_id', 633 | 'samples.portions.analytes.analyte_type', 634 | 'samples.portions.analytes.analyte_type_id', 635 | 'samples.portions.analytes.annotations.annotation_id', 636 | 'samples.portions.analytes.annotations.case_id', 637 | 'samples.portions.analytes.annotations.case_submitter_id', 638 | 'samples.portions.analytes.annotations.category', 639 | 'samples.portions.analytes.annotations.classification', 640 | 'samples.portions.analytes.annotations.created_datetime', 641 | 'samples.portions.analytes.annotations.creator', 642 | 'samples.portions.analytes.annotations.entity_id', 643 | 'samples.portions.analytes.annotations.entity_submitter_id', 644 | 'samples.portions.analytes.annotations.entity_type', 645 | 'samples.portions.analytes.annotations.legacy_created_datetime', 646 | 'samples.portions.analytes.annotations.legacy_updated_datetime', 647 | 'samples.portions.analytes.annotations.notes', 648 | 'samples.portions.analytes.annotations.state', 649 | 'samples.portions.analytes.annotations.status', 650 | 'samples.portions.analytes.annotations.submitter_id', 651 | 'samples.portions.analytes.annotations.updated_datetime', 652 | 'samples.portions.analytes.concentration', 653 | 'samples.portions.analytes.created_datetime', 654 | 'samples.portions.analytes.spectrophotometer_method', 655 | 'samples.portions.analytes.state', 656 | 'samples.portions.analytes.submitter_id', 657 | 'samples.portions.analytes.updated_datetime', 658 | 'samples.portions.analytes.well_number', 659 | 'samples.portions.annotations.annotation_id', 660 | 'samples.portions.annotations.case_id', 661 | 'samples.portions.annotations.case_submitter_id', 662 | 'samples.portions.annotations.category', 663 | 'samples.portions.annotations.classification', 664 | 'samples.portions.annotations.created_datetime', 665 | 'samples.portions.annotations.creator', 666 | 'samples.portions.annotations.entity_id', 667 | 'samples.portions.annotations.entity_submitter_id', 668 | 'samples.portions.annotations.entity_type', 669 | 'samples.portions.annotations.legacy_created_datetime', 670 | 'samples.portions.annotations.legacy_updated_datetime', 671 | 'samples.portions.annotations.notes', 672 | 'samples.portions.annotations.state', 673 | 'samples.portions.annotations.status', 674 | 'samples.portions.annotations.submitter_id', 675 | 'samples.portions.annotations.updated_datetime', 676 | 'samples.portions.center.center_id', 677 | 'samples.portions.center.center_type', 678 | 'samples.portions.center.code', 679 | 'samples.portions.center.name', 680 | 'samples.portions.center.namespace', 681 | 'samples.portions.center.short_name', 682 | 'samples.portions.created_datetime', 683 | 'samples.portions.creation_datetime', 684 | 'samples.portions.is_ffpe', 685 | 'samples.portions.portion_id', 686 | 'samples.portions.portion_number', 687 | 'samples.portions.slides.annotations.annotation_id', 688 | 'samples.portions.slides.annotations.case_id', 689 | 'samples.portions.slides.annotations.case_submitter_id', 690 | 'samples.portions.slides.annotations.category', 691 | 'samples.portions.slides.annotations.classification', 692 | 'samples.portions.slides.annotations.created_datetime', 693 | 'samples.portions.slides.annotations.creator', 694 | 'samples.portions.slides.annotations.entity_id', 695 | 'samples.portions.slides.annotations.entity_submitter_id', 696 | 'samples.portions.slides.annotations.entity_type', 697 | 'samples.portions.slides.annotations.legacy_created_datetime', 698 | 'samples.portions.slides.annotations.legacy_updated_datetime', 699 | 'samples.portions.slides.annotations.notes', 700 | 'samples.portions.slides.annotations.state', 701 | 'samples.portions.slides.annotations.status', 702 | 'samples.portions.slides.annotations.submitter_id', 703 | 'samples.portions.slides.annotations.updated_datetime', 704 | 'samples.portions.slides.created_datetime', 705 | 'samples.portions.slides.number_proliferating_cells', 706 | 'samples.portions.slides.percent_eosinophil_infiltration', 707 | 'samples.portions.slides.percent_granulocyte_infiltration', 708 | 'samples.portions.slides.percent_inflam_infiltration', 709 | 'samples.portions.slides.percent_lymphocyte_infiltration', 710 | 'samples.portions.slides.percent_monocyte_infiltration', 711 | 'samples.portions.slides.percent_necrosis', 712 | 'samples.portions.slides.percent_neutrophil_infiltration', 713 | 'samples.portions.slides.percent_normal_cells', 714 | 'samples.portions.slides.percent_stromal_cells', 715 | 'samples.portions.slides.percent_tumor_cells', 716 | 'samples.portions.slides.percent_tumor_nuclei', 717 | 'samples.portions.slides.section_location', 718 | 'samples.portions.slides.slide_id', 719 | 'samples.portions.slides.state', 720 | 'samples.portions.slides.submitter_id', 721 | 'samples.portions.slides.updated_datetime', 722 | 'samples.portions.state', 723 | 'samples.portions.submitter_id', 724 | 'samples.portions.updated_datetime', 725 | 'samples.portions.weight', 726 | 'samples.preservation_method', 727 | 'samples.sample_id', 728 | 'samples.sample_type', 729 | 'samples.sample_type_id', 730 | 'samples.shortest_dimension', 731 | 'samples.state', 732 | 'samples.submitter_id', 733 | 'samples.time_between_clamping_and_freezing', 734 | 'samples.time_between_excision_and_freezing', 735 | 'samples.tissue_type', 736 | 'samples.tumor_code', 737 | 'samples.tumor_code_id', 738 | 'samples.tumor_descriptor', 739 | 'samples.updated_datetime', 740 | 'slide_ids', 741 | 'state', 742 | 'submitter_aliquot_ids', 743 | 'submitter_analyte_ids', 744 | 'submitter_id', 745 | 'submitter_portion_ids', 746 | 'submitter_sample_ids', 747 | 'submitter_slide_ids', 748 | 'summary.data_categories.data_category', 749 | 'summary.data_categories.file_count', 750 | 'summary.experimental_strategies.experimental_strategy', 751 | 'summary.experimental_strategies.file_count', 752 | 'summary.file_count', 753 | 'summary.file_size', 754 | 'tissue_source_site.bcr_id', 755 | 'tissue_source_site.code', 756 | 'tissue_source_site.name', 757 | 'tissue_source_site.project', 758 | 'tissue_source_site.tissue_source_site_id', 759 | 'updated_datetime', 760 | ] 761 | 762 | CASES_DEFAULT_FIELDS = [field for field in CASES_FIELDS if '.' not in field] 763 | CASES_DIAGNOSES_FIELDS = [field for field in CASES_FIELDS if field.startswith('diagnoses')] 764 | CASES_SAMPLE_FIELDS = [field for field in CASES_FIELDS if field.startswith('samples')] 765 | CASES_FILE_FIELDS = [field for field in CASES_FIELDS if field.startswith('files')] 766 | CASES_EXPOSURE_FIELDS = [field for field in CASES_FIELDS if field.startswith('exposure')] -------------------------------------------------------------------------------- /pygdc/filters.py: -------------------------------------------------------------------------------- 1 | def and_filter(*ops): 2 | return { 3 | "op": "and", 4 | "content": [op for op in ops if op] 5 | } 6 | 7 | def equals_filter(field, value): 8 | return { 9 | "op": "=", 10 | "content": { 11 | "field": field, 12 | "value": value 13 | } 14 | } -------------------------------------------------------------------------------- /pygdc/urls.py: -------------------------------------------------------------------------------- 1 | GDC_API_URL = "https://gdc-api.nci.nih.gov" -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cohorts 2 | requests 3 | pandas -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [versioneer] 2 | VCS = git 3 | style = pep440 4 | versionfile_source = pygdc/_version.py 5 | versionfile_build = pygdc/_version.py 6 | tag_prefix = '' 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016. Mount Sinai School of Medicine 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from __future__ import print_function 16 | import os 17 | from os import path 18 | from codecs import open 19 | from setuptools import setup 20 | import versioneer 21 | 22 | current_directory = os.path.dirname(__file__) 23 | readme_filename = "README.md" 24 | readme_path = os.path.join(current_directory, readme_filename) 25 | 26 | readme = "" 27 | try: 28 | with open(readme_path, "r") as f: 29 | readme = f.read() 30 | except IOError as e: 31 | print(e) 32 | print("Failed to open %s" % readme_path) 33 | 34 | try: 35 | import pypandoc 36 | readme = pypandoc.convert(readme, to="rst", format="md") 37 | except ImportError as e: 38 | print(e) 39 | print("Failed to convert %s to reStructuredText", readme_filename) 40 | pass 41 | 42 | # get the dependencies and installs 43 | with open(path.join(current_directory, "requirements.txt"), encoding='utf-8') as f: 44 | all_reqs = f.read().split('\n') 45 | 46 | install_requires = [req.strip() for req in all_reqs if 'git+' not in req] 47 | dependency_links = [req.strip().replace('git+','') for req in all_reqs if 'git+' in req] 48 | 49 | if __name__ == "__main__": 50 | setup( 51 | name="pygdc", 52 | version=versioneer.get_version(), 53 | cmdclass=versioneer.get_cmdclass(), 54 | description="Python API for Genomic Data Commons", 55 | license="http://www.apache.org/licenses/LICENSE-2.0.html", 56 | classifiers=[ 57 | "Development Status :: 3 - Alpha", 58 | "Environment :: Console", 59 | "Operating System :: OS Independent", 60 | "Intended Audience :: Science/Research", 61 | "License :: OSI Approved :: Apache Software License", 62 | "Programming Language :: Python", 63 | "Topic :: Scientific/Engineering :: Bio-Informatics", 64 | ], 65 | install_requires=install_requires, 66 | dependency_links=dependency_links, 67 | long_description=readme, 68 | packages=["pygdc"], 69 | ) 70 | -------------------------------------------------------------------------------- /versioneer.py: -------------------------------------------------------------------------------- 1 | 2 | # Version: 0.16 3 | 4 | """The Versioneer - like a rocketeer, but for versions. 5 | 6 | The Versioneer 7 | ============== 8 | 9 | * like a rocketeer, but for versions! 10 | * https://github.com/warner/python-versioneer 11 | * Brian Warner 12 | * License: Public Domain 13 | * Compatible With: python2.6, 2.7, 3.3, 3.4, 3.5, and pypy 14 | * [![Latest Version] 15 | (https://pypip.in/version/versioneer/badge.svg?style=flat) 16 | ](https://pypi.python.org/pypi/versioneer/) 17 | * [![Build Status] 18 | (https://travis-ci.org/warner/python-versioneer.png?branch=master) 19 | ](https://travis-ci.org/warner/python-versioneer) 20 | 21 | This is a tool for managing a recorded version number in distutils-based 22 | python projects. The goal is to remove the tedious and error-prone "update 23 | the embedded version string" step from your release process. Making a new 24 | release should be as easy as recording a new tag in your version-control 25 | system, and maybe making new tarballs. 26 | 27 | 28 | ## Quick Install 29 | 30 | * `pip install versioneer` to somewhere to your $PATH 31 | * add a `[versioneer]` section to your setup.cfg (see below) 32 | * run `versioneer install` in your source tree, commit the results 33 | 34 | ## Version Identifiers 35 | 36 | Source trees come from a variety of places: 37 | 38 | * a version-control system checkout (mostly used by developers) 39 | * a nightly tarball, produced by build automation 40 | * a snapshot tarball, produced by a web-based VCS browser, like github's 41 | "tarball from tag" feature 42 | * a release tarball, produced by "setup.py sdist", distributed through PyPI 43 | 44 | Within each source tree, the version identifier (either a string or a number, 45 | this tool is format-agnostic) can come from a variety of places: 46 | 47 | * ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows 48 | about recent "tags" and an absolute revision-id 49 | * the name of the directory into which the tarball was unpacked 50 | * an expanded VCS keyword ($Id$, etc) 51 | * a `_version.py` created by some earlier build step 52 | 53 | For released software, the version identifier is closely related to a VCS 54 | tag. Some projects use tag names that include more than just the version 55 | string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool 56 | needs to strip the tag prefix to extract the version identifier. For 57 | unreleased software (between tags), the version identifier should provide 58 | enough information to help developers recreate the same tree, while also 59 | giving them an idea of roughly how old the tree is (after version 1.2, before 60 | version 1.3). Many VCS systems can report a description that captures this, 61 | for example `git describe --tags --dirty --always` reports things like 62 | "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the 63 | 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has 64 | uncommitted changes. 65 | 66 | The version identifier is used for multiple purposes: 67 | 68 | * to allow the module to self-identify its version: `myproject.__version__` 69 | * to choose a name and prefix for a 'setup.py sdist' tarball 70 | 71 | ## Theory of Operation 72 | 73 | Versioneer works by adding a special `_version.py` file into your source 74 | tree, where your `__init__.py` can import it. This `_version.py` knows how to 75 | dynamically ask the VCS tool for version information at import time. 76 | 77 | `_version.py` also contains `$Revision$` markers, and the installation 78 | process marks `_version.py` to have this marker rewritten with a tag name 79 | during the `git archive` command. As a result, generated tarballs will 80 | contain enough information to get the proper version. 81 | 82 | To allow `setup.py` to compute a version too, a `versioneer.py` is added to 83 | the top level of your source tree, next to `setup.py` and the `setup.cfg` 84 | that configures it. This overrides several distutils/setuptools commands to 85 | compute the version when invoked, and changes `setup.py build` and `setup.py 86 | sdist` to replace `_version.py` with a small static file that contains just 87 | the generated version data. 88 | 89 | ## Installation 90 | 91 | First, decide on values for the following configuration variables: 92 | 93 | * `VCS`: the version control system you use. Currently accepts "git". 94 | 95 | * `style`: the style of version string to be produced. See "Styles" below for 96 | details. Defaults to "pep440", which looks like 97 | `TAG[+DISTANCE.gSHORTHASH[.dirty]]`. 98 | 99 | * `versionfile_source`: 100 | 101 | A project-relative pathname into which the generated version strings should 102 | be written. This is usually a `_version.py` next to your project's main 103 | `__init__.py` file, so it can be imported at runtime. If your project uses 104 | `src/myproject/__init__.py`, this should be `src/myproject/_version.py`. 105 | This file should be checked in to your VCS as usual: the copy created below 106 | by `setup.py setup_versioneer` will include code that parses expanded VCS 107 | keywords in generated tarballs. The 'build' and 'sdist' commands will 108 | replace it with a copy that has just the calculated version string. 109 | 110 | This must be set even if your project does not have any modules (and will 111 | therefore never import `_version.py`), since "setup.py sdist" -based trees 112 | still need somewhere to record the pre-calculated version strings. Anywhere 113 | in the source tree should do. If there is a `__init__.py` next to your 114 | `_version.py`, the `setup.py setup_versioneer` command (described below) 115 | will append some `__version__`-setting assignments, if they aren't already 116 | present. 117 | 118 | * `versionfile_build`: 119 | 120 | Like `versionfile_source`, but relative to the build directory instead of 121 | the source directory. These will differ when your setup.py uses 122 | 'package_dir='. If you have `package_dir={'myproject': 'src/myproject'}`, 123 | then you will probably have `versionfile_build='myproject/_version.py'` and 124 | `versionfile_source='src/myproject/_version.py'`. 125 | 126 | If this is set to None, then `setup.py build` will not attempt to rewrite 127 | any `_version.py` in the built tree. If your project does not have any 128 | libraries (e.g. if it only builds a script), then you should use 129 | `versionfile_build = None`. To actually use the computed version string, 130 | your `setup.py` will need to override `distutils.command.build_scripts` 131 | with a subclass that explicitly inserts a copy of 132 | `versioneer.get_version()` into your script file. See 133 | `test/demoapp-script-only/setup.py` for an example. 134 | 135 | * `tag_prefix`: 136 | 137 | a string, like 'PROJECTNAME-', which appears at the start of all VCS tags. 138 | If your tags look like 'myproject-1.2.0', then you should use 139 | tag_prefix='myproject-'. If you use unprefixed tags like '1.2.0', this 140 | should be an empty string, using either `tag_prefix=` or `tag_prefix=''`. 141 | 142 | * `parentdir_prefix`: 143 | 144 | a optional string, frequently the same as tag_prefix, which appears at the 145 | start of all unpacked tarball filenames. If your tarball unpacks into 146 | 'myproject-1.2.0', this should be 'myproject-'. To disable this feature, 147 | just omit the field from your `setup.cfg`. 148 | 149 | This tool provides one script, named `versioneer`. That script has one mode, 150 | "install", which writes a copy of `versioneer.py` into the current directory 151 | and runs `versioneer.py setup` to finish the installation. 152 | 153 | To versioneer-enable your project: 154 | 155 | * 1: Modify your `setup.cfg`, adding a section named `[versioneer]` and 156 | populating it with the configuration values you decided earlier (note that 157 | the option names are not case-sensitive): 158 | 159 | ```` 160 | [versioneer] 161 | VCS = git 162 | style = pep440 163 | versionfile_source = src/myproject/_version.py 164 | versionfile_build = myproject/_version.py 165 | tag_prefix = 166 | parentdir_prefix = myproject- 167 | ```` 168 | 169 | * 2: Run `versioneer install`. This will do the following: 170 | 171 | * copy `versioneer.py` into the top of your source tree 172 | * create `_version.py` in the right place (`versionfile_source`) 173 | * modify your `__init__.py` (if one exists next to `_version.py`) to define 174 | `__version__` (by calling a function from `_version.py`) 175 | * modify your `MANIFEST.in` to include both `versioneer.py` and the 176 | generated `_version.py` in sdist tarballs 177 | 178 | `versioneer install` will complain about any problems it finds with your 179 | `setup.py` or `setup.cfg`. Run it multiple times until you have fixed all 180 | the problems. 181 | 182 | * 3: add a `import versioneer` to your setup.py, and add the following 183 | arguments to the setup() call: 184 | 185 | version=versioneer.get_version(), 186 | cmdclass=versioneer.get_cmdclass(), 187 | 188 | * 4: commit these changes to your VCS. To make sure you won't forget, 189 | `versioneer install` will mark everything it touched for addition using 190 | `git add`. Don't forget to add `setup.py` and `setup.cfg` too. 191 | 192 | ## Post-Installation Usage 193 | 194 | Once established, all uses of your tree from a VCS checkout should get the 195 | current version string. All generated tarballs should include an embedded 196 | version string (so users who unpack them will not need a VCS tool installed). 197 | 198 | If you distribute your project through PyPI, then the release process should 199 | boil down to two steps: 200 | 201 | * 1: git tag 1.0 202 | * 2: python setup.py register sdist upload 203 | 204 | If you distribute it through github (i.e. users use github to generate 205 | tarballs with `git archive`), the process is: 206 | 207 | * 1: git tag 1.0 208 | * 2: git push; git push --tags 209 | 210 | Versioneer will report "0+untagged.NUMCOMMITS.gHASH" until your tree has at 211 | least one tag in its history. 212 | 213 | ## Version-String Flavors 214 | 215 | Code which uses Versioneer can learn about its version string at runtime by 216 | importing `_version` from your main `__init__.py` file and running the 217 | `get_versions()` function. From the "outside" (e.g. in `setup.py`), you can 218 | import the top-level `versioneer.py` and run `get_versions()`. 219 | 220 | Both functions return a dictionary with different flavors of version 221 | information: 222 | 223 | * `['version']`: A condensed version string, rendered using the selected 224 | style. This is the most commonly used value for the project's version 225 | string. The default "pep440" style yields strings like `0.11`, 226 | `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section 227 | below for alternative styles. 228 | 229 | * `['full-revisionid']`: detailed revision identifier. For Git, this is the 230 | full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". 231 | 232 | * `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that 233 | this is only accurate if run in a VCS checkout, otherwise it is likely to 234 | be False or None 235 | 236 | * `['error']`: if the version string could not be computed, this will be set 237 | to a string describing the problem, otherwise it will be None. It may be 238 | useful to throw an exception in setup.py if this is set, to avoid e.g. 239 | creating tarballs with a version string of "unknown". 240 | 241 | Some variants are more useful than others. Including `full-revisionid` in a 242 | bug report should allow developers to reconstruct the exact code being tested 243 | (or indicate the presence of local changes that should be shared with the 244 | developers). `version` is suitable for display in an "about" box or a CLI 245 | `--version` output: it can be easily compared against release notes and lists 246 | of bugs fixed in various releases. 247 | 248 | The installer adds the following text to your `__init__.py` to place a basic 249 | version in `YOURPROJECT.__version__`: 250 | 251 | from ._version import get_versions 252 | __version__ = get_versions()['version'] 253 | del get_versions 254 | 255 | ## Styles 256 | 257 | The setup.cfg `style=` configuration controls how the VCS information is 258 | rendered into a version string. 259 | 260 | The default style, "pep440", produces a PEP440-compliant string, equal to the 261 | un-prefixed tag name for actual releases, and containing an additional "local 262 | version" section with more detail for in-between builds. For Git, this is 263 | TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags 264 | --dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the 265 | tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and 266 | that this commit is two revisions ("+2") beyond the "0.11" tag. For released 267 | software (exactly equal to a known tag), the identifier will only contain the 268 | stripped tag, e.g. "0.11". 269 | 270 | Other styles are available. See details.md in the Versioneer source tree for 271 | descriptions. 272 | 273 | ## Debugging 274 | 275 | Versioneer tries to avoid fatal errors: if something goes wrong, it will tend 276 | to return a version of "0+unknown". To investigate the problem, run `setup.py 277 | version`, which will run the version-lookup code in a verbose mode, and will 278 | display the full contents of `get_versions()` (including the `error` string, 279 | which may help identify what went wrong). 280 | 281 | ## Updating Versioneer 282 | 283 | To upgrade your project to a new release of Versioneer, do the following: 284 | 285 | * install the new Versioneer (`pip install -U versioneer` or equivalent) 286 | * edit `setup.cfg`, if necessary, to include any new configuration settings 287 | indicated by the release notes 288 | * re-run `versioneer install` in your source tree, to replace 289 | `SRC/_version.py` 290 | * commit any changed files 291 | 292 | ### Upgrading to 0.16 293 | 294 | Nothing special. 295 | 296 | ### Upgrading to 0.15 297 | 298 | Starting with this version, Versioneer is configured with a `[versioneer]` 299 | section in your `setup.cfg` file. Earlier versions required the `setup.py` to 300 | set attributes on the `versioneer` module immediately after import. The new 301 | version will refuse to run (raising an exception during import) until you 302 | have provided the necessary `setup.cfg` section. 303 | 304 | In addition, the Versioneer package provides an executable named 305 | `versioneer`, and the installation process is driven by running `versioneer 306 | install`. In 0.14 and earlier, the executable was named 307 | `versioneer-installer` and was run without an argument. 308 | 309 | ### Upgrading to 0.14 310 | 311 | 0.14 changes the format of the version string. 0.13 and earlier used 312 | hyphen-separated strings like "0.11-2-g1076c97-dirty". 0.14 and beyond use a 313 | plus-separated "local version" section strings, with dot-separated 314 | components, like "0.11+2.g1076c97". PEP440-strict tools did not like the old 315 | format, but should be ok with the new one. 316 | 317 | ### Upgrading from 0.11 to 0.12 318 | 319 | Nothing special. 320 | 321 | ### Upgrading from 0.10 to 0.11 322 | 323 | You must add a `versioneer.VCS = "git"` to your `setup.py` before re-running 324 | `setup.py setup_versioneer`. This will enable the use of additional 325 | version-control systems (SVN, etc) in the future. 326 | 327 | ## Future Directions 328 | 329 | This tool is designed to make it easily extended to other version-control 330 | systems: all VCS-specific components are in separate directories like 331 | src/git/ . The top-level `versioneer.py` script is assembled from these 332 | components by running make-versioneer.py . In the future, make-versioneer.py 333 | will take a VCS name as an argument, and will construct a version of 334 | `versioneer.py` that is specific to the given VCS. It might also take the 335 | configuration arguments that are currently provided manually during 336 | installation by editing setup.py . Alternatively, it might go the other 337 | direction and include code from all supported VCS systems, reducing the 338 | number of intermediate scripts. 339 | 340 | 341 | ## License 342 | 343 | To make Versioneer easier to embed, all its code is dedicated to the public 344 | domain. The `_version.py` that it creates is also in the public domain. 345 | Specifically, both are released under the Creative Commons "Public Domain 346 | Dedication" license (CC0-1.0), as described in 347 | https://creativecommons.org/publicdomain/zero/1.0/ . 348 | 349 | """ 350 | 351 | from __future__ import print_function 352 | try: 353 | import configparser 354 | except ImportError: 355 | import ConfigParser as configparser 356 | import errno 357 | import json 358 | import os 359 | import re 360 | import subprocess 361 | import sys 362 | 363 | 364 | class VersioneerConfig: 365 | """Container for Versioneer configuration parameters.""" 366 | 367 | 368 | def get_root(): 369 | """Get the project root directory. 370 | 371 | We require that all commands are run from the project root, i.e. the 372 | directory that contains setup.py, setup.cfg, and versioneer.py . 373 | """ 374 | root = os.path.realpath(os.path.abspath(os.getcwd())) 375 | setup_py = os.path.join(root, "setup.py") 376 | versioneer_py = os.path.join(root, "versioneer.py") 377 | if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): 378 | # allow 'python path/to/setup.py COMMAND' 379 | root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) 380 | setup_py = os.path.join(root, "setup.py") 381 | versioneer_py = os.path.join(root, "versioneer.py") 382 | if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): 383 | err = ("Versioneer was unable to run the project root directory. " 384 | "Versioneer requires setup.py to be executed from " 385 | "its immediate directory (like 'python setup.py COMMAND'), " 386 | "or in a way that lets it use sys.argv[0] to find the root " 387 | "(like 'python path/to/setup.py COMMAND').") 388 | raise VersioneerBadRootError(err) 389 | try: 390 | # Certain runtime workflows (setup.py install/develop in a setuptools 391 | # tree) execute all dependencies in a single python process, so 392 | # "versioneer" may be imported multiple times, and python's shared 393 | # module-import table will cache the first one. So we can't use 394 | # os.path.dirname(__file__), as that will find whichever 395 | # versioneer.py was first imported, even in later projects. 396 | me = os.path.realpath(os.path.abspath(__file__)) 397 | if os.path.splitext(me)[0] != os.path.splitext(versioneer_py)[0]: 398 | print("Warning: build in %s is using versioneer.py from %s" 399 | % (os.path.dirname(me), versioneer_py)) 400 | except NameError: 401 | pass 402 | return root 403 | 404 | 405 | def get_config_from_root(root): 406 | """Read the project setup.cfg file to determine Versioneer config.""" 407 | # This might raise EnvironmentError (if setup.cfg is missing), or 408 | # configparser.NoSectionError (if it lacks a [versioneer] section), or 409 | # configparser.NoOptionError (if it lacks "VCS="). See the docstring at 410 | # the top of versioneer.py for instructions on writing your setup.cfg . 411 | setup_cfg = os.path.join(root, "setup.cfg") 412 | parser = configparser.SafeConfigParser() 413 | with open(setup_cfg, "r") as f: 414 | parser.readfp(f) 415 | VCS = parser.get("versioneer", "VCS") # mandatory 416 | 417 | def get(parser, name): 418 | if parser.has_option("versioneer", name): 419 | return parser.get("versioneer", name) 420 | return None 421 | cfg = VersioneerConfig() 422 | cfg.VCS = VCS 423 | cfg.style = get(parser, "style") or "" 424 | cfg.versionfile_source = get(parser, "versionfile_source") 425 | cfg.versionfile_build = get(parser, "versionfile_build") 426 | cfg.tag_prefix = get(parser, "tag_prefix") 427 | if cfg.tag_prefix in ("''", '""'): 428 | cfg.tag_prefix = "" 429 | cfg.parentdir_prefix = get(parser, "parentdir_prefix") 430 | cfg.verbose = get(parser, "verbose") 431 | return cfg 432 | 433 | 434 | class NotThisMethod(Exception): 435 | """Exception raised if a method is not valid for the current scenario.""" 436 | 437 | # these dictionaries contain VCS-specific tools 438 | LONG_VERSION_PY = {} 439 | HANDLERS = {} 440 | 441 | 442 | def register_vcs_handler(vcs, method): # decorator 443 | """Decorator to mark a method as the handler for a particular VCS.""" 444 | def decorate(f): 445 | """Store f in HANDLERS[vcs][method].""" 446 | if vcs not in HANDLERS: 447 | HANDLERS[vcs] = {} 448 | HANDLERS[vcs][method] = f 449 | return f 450 | return decorate 451 | 452 | 453 | def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): 454 | """Call the given command(s).""" 455 | assert isinstance(commands, list) 456 | p = None 457 | for c in commands: 458 | try: 459 | dispcmd = str([c] + args) 460 | # remember shell=False, so use git.cmd on windows, not just git 461 | p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, 462 | stderr=(subprocess.PIPE if hide_stderr 463 | else None)) 464 | break 465 | except EnvironmentError: 466 | e = sys.exc_info()[1] 467 | if e.errno == errno.ENOENT: 468 | continue 469 | if verbose: 470 | print("unable to run %s" % dispcmd) 471 | print(e) 472 | return None 473 | else: 474 | if verbose: 475 | print("unable to find command, tried %s" % (commands,)) 476 | return None 477 | stdout = p.communicate()[0].strip() 478 | if sys.version_info[0] >= 3: 479 | stdout = stdout.decode() 480 | if p.returncode != 0: 481 | if verbose: 482 | print("unable to run %s (error)" % dispcmd) 483 | return None 484 | return stdout 485 | LONG_VERSION_PY['git'] = ''' 486 | # This file helps to compute a version number in source trees obtained from 487 | # git-archive tarball (such as those provided by githubs download-from-tag 488 | # feature). Distribution tarballs (built by setup.py sdist) and build 489 | # directories (produced by setup.py build) will contain a much shorter file 490 | # that just contains the computed version number. 491 | 492 | # This file is released into the public domain. Generated by 493 | # versioneer-0.16 (https://github.com/warner/python-versioneer) 494 | 495 | """Git implementation of _version.py.""" 496 | 497 | import errno 498 | import os 499 | import re 500 | import subprocess 501 | import sys 502 | 503 | 504 | def get_keywords(): 505 | """Get the keywords needed to look up the version information.""" 506 | # these strings will be replaced by git during git-archive. 507 | # setup.py/versioneer.py will grep for the variable names, so they must 508 | # each be defined on a line of their own. _version.py will just call 509 | # get_keywords(). 510 | git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" 511 | git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" 512 | keywords = {"refnames": git_refnames, "full": git_full} 513 | return keywords 514 | 515 | 516 | class VersioneerConfig: 517 | """Container for Versioneer configuration parameters.""" 518 | 519 | 520 | def get_config(): 521 | """Create, populate and return the VersioneerConfig() object.""" 522 | # these strings are filled in when 'setup.py versioneer' creates 523 | # _version.py 524 | cfg = VersioneerConfig() 525 | cfg.VCS = "git" 526 | cfg.style = "%(STYLE)s" 527 | cfg.tag_prefix = "%(TAG_PREFIX)s" 528 | cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" 529 | cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" 530 | cfg.verbose = False 531 | return cfg 532 | 533 | 534 | class NotThisMethod(Exception): 535 | """Exception raised if a method is not valid for the current scenario.""" 536 | 537 | 538 | LONG_VERSION_PY = {} 539 | HANDLERS = {} 540 | 541 | 542 | def register_vcs_handler(vcs, method): # decorator 543 | """Decorator to mark a method as the handler for a particular VCS.""" 544 | def decorate(f): 545 | """Store f in HANDLERS[vcs][method].""" 546 | if vcs not in HANDLERS: 547 | HANDLERS[vcs] = {} 548 | HANDLERS[vcs][method] = f 549 | return f 550 | return decorate 551 | 552 | 553 | def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): 554 | """Call the given command(s).""" 555 | assert isinstance(commands, list) 556 | p = None 557 | for c in commands: 558 | try: 559 | dispcmd = str([c] + args) 560 | # remember shell=False, so use git.cmd on windows, not just git 561 | p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, 562 | stderr=(subprocess.PIPE if hide_stderr 563 | else None)) 564 | break 565 | except EnvironmentError: 566 | e = sys.exc_info()[1] 567 | if e.errno == errno.ENOENT: 568 | continue 569 | if verbose: 570 | print("unable to run %%s" %% dispcmd) 571 | print(e) 572 | return None 573 | else: 574 | if verbose: 575 | print("unable to find command, tried %%s" %% (commands,)) 576 | return None 577 | stdout = p.communicate()[0].strip() 578 | if sys.version_info[0] >= 3: 579 | stdout = stdout.decode() 580 | if p.returncode != 0: 581 | if verbose: 582 | print("unable to run %%s (error)" %% dispcmd) 583 | return None 584 | return stdout 585 | 586 | 587 | def versions_from_parentdir(parentdir_prefix, root, verbose): 588 | """Try to determine the version from the parent directory name. 589 | 590 | Source tarballs conventionally unpack into a directory that includes 591 | both the project name and a version string. 592 | """ 593 | dirname = os.path.basename(root) 594 | if not dirname.startswith(parentdir_prefix): 595 | if verbose: 596 | print("guessing rootdir is '%%s', but '%%s' doesn't start with " 597 | "prefix '%%s'" %% (root, dirname, parentdir_prefix)) 598 | raise NotThisMethod("rootdir doesn't start with parentdir_prefix") 599 | return {"version": dirname[len(parentdir_prefix):], 600 | "full-revisionid": None, 601 | "dirty": False, "error": None} 602 | 603 | 604 | @register_vcs_handler("git", "get_keywords") 605 | def git_get_keywords(versionfile_abs): 606 | """Extract version information from the given file.""" 607 | # the code embedded in _version.py can just fetch the value of these 608 | # keywords. When used from setup.py, we don't want to import _version.py, 609 | # so we do it with a regexp instead. This function is not used from 610 | # _version.py. 611 | keywords = {} 612 | try: 613 | f = open(versionfile_abs, "r") 614 | for line in f.readlines(): 615 | if line.strip().startswith("git_refnames ="): 616 | mo = re.search(r'=\s*"(.*)"', line) 617 | if mo: 618 | keywords["refnames"] = mo.group(1) 619 | if line.strip().startswith("git_full ="): 620 | mo = re.search(r'=\s*"(.*)"', line) 621 | if mo: 622 | keywords["full"] = mo.group(1) 623 | f.close() 624 | except EnvironmentError: 625 | pass 626 | return keywords 627 | 628 | 629 | @register_vcs_handler("git", "keywords") 630 | def git_versions_from_keywords(keywords, tag_prefix, verbose): 631 | """Get version information from git keywords.""" 632 | if not keywords: 633 | raise NotThisMethod("no keywords at all, weird") 634 | refnames = keywords["refnames"].strip() 635 | if refnames.startswith("$Format"): 636 | if verbose: 637 | print("keywords are unexpanded, not using") 638 | raise NotThisMethod("unexpanded keywords, not a git-archive tarball") 639 | refs = set([r.strip() for r in refnames.strip("()").split(",")]) 640 | # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of 641 | # just "foo-1.0". If we see a "tag: " prefix, prefer those. 642 | TAG = "tag: " 643 | tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) 644 | if not tags: 645 | # Either we're using git < 1.8.3, or there really are no tags. We use 646 | # a heuristic: assume all version tags have a digit. The old git %%d 647 | # expansion behaves like git log --decorate=short and strips out the 648 | # refs/heads/ and refs/tags/ prefixes that would let us distinguish 649 | # between branches and tags. By ignoring refnames without digits, we 650 | # filter out many common branch names like "release" and 651 | # "stabilization", as well as "HEAD" and "master". 652 | tags = set([r for r in refs if re.search(r'\d', r)]) 653 | if verbose: 654 | print("discarding '%%s', no digits" %% ",".join(refs-tags)) 655 | if verbose: 656 | print("likely tags: %%s" %% ",".join(sorted(tags))) 657 | for ref in sorted(tags): 658 | # sorting will prefer e.g. "2.0" over "2.0rc1" 659 | if ref.startswith(tag_prefix): 660 | r = ref[len(tag_prefix):] 661 | if verbose: 662 | print("picking %%s" %% r) 663 | return {"version": r, 664 | "full-revisionid": keywords["full"].strip(), 665 | "dirty": False, "error": None 666 | } 667 | # no suitable tags, so version is "0+unknown", but full hex is still there 668 | if verbose: 669 | print("no suitable tags, using unknown + full revision id") 670 | return {"version": "0+unknown", 671 | "full-revisionid": keywords["full"].strip(), 672 | "dirty": False, "error": "no suitable tags"} 673 | 674 | 675 | @register_vcs_handler("git", "pieces_from_vcs") 676 | def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): 677 | """Get version from 'git describe' in the root of the source tree. 678 | 679 | This only gets called if the git-archive 'subst' keywords were *not* 680 | expanded, and _version.py hasn't already been rewritten with a short 681 | version string, meaning we're inside a checked out source tree. 682 | """ 683 | if not os.path.exists(os.path.join(root, ".git")): 684 | if verbose: 685 | print("no .git in %%s" %% root) 686 | raise NotThisMethod("no .git directory") 687 | 688 | GITS = ["git"] 689 | if sys.platform == "win32": 690 | GITS = ["git.cmd", "git.exe"] 691 | # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] 692 | # if there isn't one, this yields HEX[-dirty] (no NUM) 693 | describe_out = run_command(GITS, ["describe", "--tags", "--dirty", 694 | "--always", "--long", 695 | "--match", "%%s*" %% tag_prefix], 696 | cwd=root) 697 | # --long was added in git-1.5.5 698 | if describe_out is None: 699 | raise NotThisMethod("'git describe' failed") 700 | describe_out = describe_out.strip() 701 | full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) 702 | if full_out is None: 703 | raise NotThisMethod("'git rev-parse' failed") 704 | full_out = full_out.strip() 705 | 706 | pieces = {} 707 | pieces["long"] = full_out 708 | pieces["short"] = full_out[:7] # maybe improved later 709 | pieces["error"] = None 710 | 711 | # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] 712 | # TAG might have hyphens. 713 | git_describe = describe_out 714 | 715 | # look for -dirty suffix 716 | dirty = git_describe.endswith("-dirty") 717 | pieces["dirty"] = dirty 718 | if dirty: 719 | git_describe = git_describe[:git_describe.rindex("-dirty")] 720 | 721 | # now we have TAG-NUM-gHEX or HEX 722 | 723 | if "-" in git_describe: 724 | # TAG-NUM-gHEX 725 | mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) 726 | if not mo: 727 | # unparseable. Maybe git-describe is misbehaving? 728 | pieces["error"] = ("unable to parse git-describe output: '%%s'" 729 | %% describe_out) 730 | return pieces 731 | 732 | # tag 733 | full_tag = mo.group(1) 734 | if not full_tag.startswith(tag_prefix): 735 | if verbose: 736 | fmt = "tag '%%s' doesn't start with prefix '%%s'" 737 | print(fmt %% (full_tag, tag_prefix)) 738 | pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" 739 | %% (full_tag, tag_prefix)) 740 | return pieces 741 | pieces["closest-tag"] = full_tag[len(tag_prefix):] 742 | 743 | # distance: number of commits since tag 744 | pieces["distance"] = int(mo.group(2)) 745 | 746 | # commit: short hex revision ID 747 | pieces["short"] = mo.group(3) 748 | 749 | else: 750 | # HEX: no tags 751 | pieces["closest-tag"] = None 752 | count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], 753 | cwd=root) 754 | pieces["distance"] = int(count_out) # total number of commits 755 | 756 | return pieces 757 | 758 | 759 | def plus_or_dot(pieces): 760 | """Return a + if we don't already have one, else return a .""" 761 | if "+" in pieces.get("closest-tag", ""): 762 | return "." 763 | return "+" 764 | 765 | 766 | def render_pep440(pieces): 767 | """Build up version string, with post-release "local version identifier". 768 | 769 | Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you 770 | get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty 771 | 772 | Exceptions: 773 | 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] 774 | """ 775 | if pieces["closest-tag"]: 776 | rendered = pieces["closest-tag"] 777 | if pieces["distance"] or pieces["dirty"]: 778 | rendered += plus_or_dot(pieces) 779 | rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) 780 | if pieces["dirty"]: 781 | rendered += ".dirty" 782 | else: 783 | # exception #1 784 | rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], 785 | pieces["short"]) 786 | if pieces["dirty"]: 787 | rendered += ".dirty" 788 | return rendered 789 | 790 | 791 | def render_pep440_pre(pieces): 792 | """TAG[.post.devDISTANCE] -- No -dirty. 793 | 794 | Exceptions: 795 | 1: no tags. 0.post.devDISTANCE 796 | """ 797 | if pieces["closest-tag"]: 798 | rendered = pieces["closest-tag"] 799 | if pieces["distance"]: 800 | rendered += ".post.dev%%d" %% pieces["distance"] 801 | else: 802 | # exception #1 803 | rendered = "0.post.dev%%d" %% pieces["distance"] 804 | return rendered 805 | 806 | 807 | def render_pep440_post(pieces): 808 | """TAG[.postDISTANCE[.dev0]+gHEX] . 809 | 810 | The ".dev0" means dirty. Note that .dev0 sorts backwards 811 | (a dirty tree will appear "older" than the corresponding clean one), 812 | but you shouldn't be releasing software with -dirty anyways. 813 | 814 | Exceptions: 815 | 1: no tags. 0.postDISTANCE[.dev0] 816 | """ 817 | if pieces["closest-tag"]: 818 | rendered = pieces["closest-tag"] 819 | if pieces["distance"] or pieces["dirty"]: 820 | rendered += ".post%%d" %% pieces["distance"] 821 | if pieces["dirty"]: 822 | rendered += ".dev0" 823 | rendered += plus_or_dot(pieces) 824 | rendered += "g%%s" %% pieces["short"] 825 | else: 826 | # exception #1 827 | rendered = "0.post%%d" %% pieces["distance"] 828 | if pieces["dirty"]: 829 | rendered += ".dev0" 830 | rendered += "+g%%s" %% pieces["short"] 831 | return rendered 832 | 833 | 834 | def render_pep440_old(pieces): 835 | """TAG[.postDISTANCE[.dev0]] . 836 | 837 | The ".dev0" means dirty. 838 | 839 | Eexceptions: 840 | 1: no tags. 0.postDISTANCE[.dev0] 841 | """ 842 | if pieces["closest-tag"]: 843 | rendered = pieces["closest-tag"] 844 | if pieces["distance"] or pieces["dirty"]: 845 | rendered += ".post%%d" %% pieces["distance"] 846 | if pieces["dirty"]: 847 | rendered += ".dev0" 848 | else: 849 | # exception #1 850 | rendered = "0.post%%d" %% pieces["distance"] 851 | if pieces["dirty"]: 852 | rendered += ".dev0" 853 | return rendered 854 | 855 | 856 | def render_git_describe(pieces): 857 | """TAG[-DISTANCE-gHEX][-dirty]. 858 | 859 | Like 'git describe --tags --dirty --always'. 860 | 861 | Exceptions: 862 | 1: no tags. HEX[-dirty] (note: no 'g' prefix) 863 | """ 864 | if pieces["closest-tag"]: 865 | rendered = pieces["closest-tag"] 866 | if pieces["distance"]: 867 | rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) 868 | else: 869 | # exception #1 870 | rendered = pieces["short"] 871 | if pieces["dirty"]: 872 | rendered += "-dirty" 873 | return rendered 874 | 875 | 876 | def render_git_describe_long(pieces): 877 | """TAG-DISTANCE-gHEX[-dirty]. 878 | 879 | Like 'git describe --tags --dirty --always -long'. 880 | The distance/hash is unconditional. 881 | 882 | Exceptions: 883 | 1: no tags. HEX[-dirty] (note: no 'g' prefix) 884 | """ 885 | if pieces["closest-tag"]: 886 | rendered = pieces["closest-tag"] 887 | rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) 888 | else: 889 | # exception #1 890 | rendered = pieces["short"] 891 | if pieces["dirty"]: 892 | rendered += "-dirty" 893 | return rendered 894 | 895 | 896 | def render(pieces, style): 897 | """Render the given version pieces into the requested style.""" 898 | if pieces["error"]: 899 | return {"version": "unknown", 900 | "full-revisionid": pieces.get("long"), 901 | "dirty": None, 902 | "error": pieces["error"]} 903 | 904 | if not style or style == "default": 905 | style = "pep440" # the default 906 | 907 | if style == "pep440": 908 | rendered = render_pep440(pieces) 909 | elif style == "pep440-pre": 910 | rendered = render_pep440_pre(pieces) 911 | elif style == "pep440-post": 912 | rendered = render_pep440_post(pieces) 913 | elif style == "pep440-old": 914 | rendered = render_pep440_old(pieces) 915 | elif style == "git-describe": 916 | rendered = render_git_describe(pieces) 917 | elif style == "git-describe-long": 918 | rendered = render_git_describe_long(pieces) 919 | else: 920 | raise ValueError("unknown style '%%s'" %% style) 921 | 922 | return {"version": rendered, "full-revisionid": pieces["long"], 923 | "dirty": pieces["dirty"], "error": None} 924 | 925 | 926 | def get_versions(): 927 | """Get version information or return default if unable to do so.""" 928 | # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have 929 | # __file__, we can work backwards from there to the root. Some 930 | # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which 931 | # case we can only use expanded keywords. 932 | 933 | cfg = get_config() 934 | verbose = cfg.verbose 935 | 936 | try: 937 | return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, 938 | verbose) 939 | except NotThisMethod: 940 | pass 941 | 942 | try: 943 | root = os.path.realpath(__file__) 944 | # versionfile_source is the relative path from the top of the source 945 | # tree (where the .git directory might live) to this file. Invert 946 | # this to find the root from __file__. 947 | for i in cfg.versionfile_source.split('/'): 948 | root = os.path.dirname(root) 949 | except NameError: 950 | return {"version": "0+unknown", "full-revisionid": None, 951 | "dirty": None, 952 | "error": "unable to find root of source tree"} 953 | 954 | try: 955 | pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) 956 | return render(pieces, cfg.style) 957 | except NotThisMethod: 958 | pass 959 | 960 | try: 961 | if cfg.parentdir_prefix: 962 | return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) 963 | except NotThisMethod: 964 | pass 965 | 966 | return {"version": "0+unknown", "full-revisionid": None, 967 | "dirty": None, 968 | "error": "unable to compute version"} 969 | ''' 970 | 971 | 972 | @register_vcs_handler("git", "get_keywords") 973 | def git_get_keywords(versionfile_abs): 974 | """Extract version information from the given file.""" 975 | # the code embedded in _version.py can just fetch the value of these 976 | # keywords. When used from setup.py, we don't want to import _version.py, 977 | # so we do it with a regexp instead. This function is not used from 978 | # _version.py. 979 | keywords = {} 980 | try: 981 | f = open(versionfile_abs, "r") 982 | for line in f.readlines(): 983 | if line.strip().startswith("git_refnames ="): 984 | mo = re.search(r'=\s*"(.*)"', line) 985 | if mo: 986 | keywords["refnames"] = mo.group(1) 987 | if line.strip().startswith("git_full ="): 988 | mo = re.search(r'=\s*"(.*)"', line) 989 | if mo: 990 | keywords["full"] = mo.group(1) 991 | f.close() 992 | except EnvironmentError: 993 | pass 994 | return keywords 995 | 996 | 997 | @register_vcs_handler("git", "keywords") 998 | def git_versions_from_keywords(keywords, tag_prefix, verbose): 999 | """Get version information from git keywords.""" 1000 | if not keywords: 1001 | raise NotThisMethod("no keywords at all, weird") 1002 | refnames = keywords["refnames"].strip() 1003 | if refnames.startswith("$Format"): 1004 | if verbose: 1005 | print("keywords are unexpanded, not using") 1006 | raise NotThisMethod("unexpanded keywords, not a git-archive tarball") 1007 | refs = set([r.strip() for r in refnames.strip("()").split(",")]) 1008 | # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of 1009 | # just "foo-1.0". If we see a "tag: " prefix, prefer those. 1010 | TAG = "tag: " 1011 | tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) 1012 | if not tags: 1013 | # Either we're using git < 1.8.3, or there really are no tags. We use 1014 | # a heuristic: assume all version tags have a digit. The old git %d 1015 | # expansion behaves like git log --decorate=short and strips out the 1016 | # refs/heads/ and refs/tags/ prefixes that would let us distinguish 1017 | # between branches and tags. By ignoring refnames without digits, we 1018 | # filter out many common branch names like "release" and 1019 | # "stabilization", as well as "HEAD" and "master". 1020 | tags = set([r for r in refs if re.search(r'\d', r)]) 1021 | if verbose: 1022 | print("discarding '%s', no digits" % ",".join(refs-tags)) 1023 | if verbose: 1024 | print("likely tags: %s" % ",".join(sorted(tags))) 1025 | for ref in sorted(tags): 1026 | # sorting will prefer e.g. "2.0" over "2.0rc1" 1027 | if ref.startswith(tag_prefix): 1028 | r = ref[len(tag_prefix):] 1029 | if verbose: 1030 | print("picking %s" % r) 1031 | return {"version": r, 1032 | "full-revisionid": keywords["full"].strip(), 1033 | "dirty": False, "error": None 1034 | } 1035 | # no suitable tags, so version is "0+unknown", but full hex is still there 1036 | if verbose: 1037 | print("no suitable tags, using unknown + full revision id") 1038 | return {"version": "0+unknown", 1039 | "full-revisionid": keywords["full"].strip(), 1040 | "dirty": False, "error": "no suitable tags"} 1041 | 1042 | 1043 | @register_vcs_handler("git", "pieces_from_vcs") 1044 | def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): 1045 | """Get version from 'git describe' in the root of the source tree. 1046 | 1047 | This only gets called if the git-archive 'subst' keywords were *not* 1048 | expanded, and _version.py hasn't already been rewritten with a short 1049 | version string, meaning we're inside a checked out source tree. 1050 | """ 1051 | if not os.path.exists(os.path.join(root, ".git")): 1052 | if verbose: 1053 | print("no .git in %s" % root) 1054 | raise NotThisMethod("no .git directory") 1055 | 1056 | GITS = ["git"] 1057 | if sys.platform == "win32": 1058 | GITS = ["git.cmd", "git.exe"] 1059 | # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] 1060 | # if there isn't one, this yields HEX[-dirty] (no NUM) 1061 | describe_out = run_command(GITS, ["describe", "--tags", "--dirty", 1062 | "--always", "--long", 1063 | "--match", "%s*" % tag_prefix], 1064 | cwd=root) 1065 | # --long was added in git-1.5.5 1066 | if describe_out is None: 1067 | raise NotThisMethod("'git describe' failed") 1068 | describe_out = describe_out.strip() 1069 | full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) 1070 | if full_out is None: 1071 | raise NotThisMethod("'git rev-parse' failed") 1072 | full_out = full_out.strip() 1073 | 1074 | pieces = {} 1075 | pieces["long"] = full_out 1076 | pieces["short"] = full_out[:7] # maybe improved later 1077 | pieces["error"] = None 1078 | 1079 | # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] 1080 | # TAG might have hyphens. 1081 | git_describe = describe_out 1082 | 1083 | # look for -dirty suffix 1084 | dirty = git_describe.endswith("-dirty") 1085 | pieces["dirty"] = dirty 1086 | if dirty: 1087 | git_describe = git_describe[:git_describe.rindex("-dirty")] 1088 | 1089 | # now we have TAG-NUM-gHEX or HEX 1090 | 1091 | if "-" in git_describe: 1092 | # TAG-NUM-gHEX 1093 | mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) 1094 | if not mo: 1095 | # unparseable. Maybe git-describe is misbehaving? 1096 | pieces["error"] = ("unable to parse git-describe output: '%s'" 1097 | % describe_out) 1098 | return pieces 1099 | 1100 | # tag 1101 | full_tag = mo.group(1) 1102 | if not full_tag.startswith(tag_prefix): 1103 | if verbose: 1104 | fmt = "tag '%s' doesn't start with prefix '%s'" 1105 | print(fmt % (full_tag, tag_prefix)) 1106 | pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" 1107 | % (full_tag, tag_prefix)) 1108 | return pieces 1109 | pieces["closest-tag"] = full_tag[len(tag_prefix):] 1110 | 1111 | # distance: number of commits since tag 1112 | pieces["distance"] = int(mo.group(2)) 1113 | 1114 | # commit: short hex revision ID 1115 | pieces["short"] = mo.group(3) 1116 | 1117 | else: 1118 | # HEX: no tags 1119 | pieces["closest-tag"] = None 1120 | count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], 1121 | cwd=root) 1122 | pieces["distance"] = int(count_out) # total number of commits 1123 | 1124 | return pieces 1125 | 1126 | 1127 | def do_vcs_install(manifest_in, versionfile_source, ipy): 1128 | """Git-specific installation logic for Versioneer. 1129 | 1130 | For Git, this means creating/changing .gitattributes to mark _version.py 1131 | for export-time keyword substitution. 1132 | """ 1133 | GITS = ["git"] 1134 | if sys.platform == "win32": 1135 | GITS = ["git.cmd", "git.exe"] 1136 | files = [manifest_in, versionfile_source] 1137 | if ipy: 1138 | files.append(ipy) 1139 | try: 1140 | me = __file__ 1141 | if me.endswith(".pyc") or me.endswith(".pyo"): 1142 | me = os.path.splitext(me)[0] + ".py" 1143 | versioneer_file = os.path.relpath(me) 1144 | except NameError: 1145 | versioneer_file = "versioneer.py" 1146 | files.append(versioneer_file) 1147 | present = False 1148 | try: 1149 | f = open(".gitattributes", "r") 1150 | for line in f.readlines(): 1151 | if line.strip().startswith(versionfile_source): 1152 | if "export-subst" in line.strip().split()[1:]: 1153 | present = True 1154 | f.close() 1155 | except EnvironmentError: 1156 | pass 1157 | if not present: 1158 | f = open(".gitattributes", "a+") 1159 | f.write("%s export-subst\n" % versionfile_source) 1160 | f.close() 1161 | files.append(".gitattributes") 1162 | run_command(GITS, ["add", "--"] + files) 1163 | 1164 | 1165 | def versions_from_parentdir(parentdir_prefix, root, verbose): 1166 | """Try to determine the version from the parent directory name. 1167 | 1168 | Source tarballs conventionally unpack into a directory that includes 1169 | both the project name and a version string. 1170 | """ 1171 | dirname = os.path.basename(root) 1172 | if not dirname.startswith(parentdir_prefix): 1173 | if verbose: 1174 | print("guessing rootdir is '%s', but '%s' doesn't start with " 1175 | "prefix '%s'" % (root, dirname, parentdir_prefix)) 1176 | raise NotThisMethod("rootdir doesn't start with parentdir_prefix") 1177 | return {"version": dirname[len(parentdir_prefix):], 1178 | "full-revisionid": None, 1179 | "dirty": False, "error": None} 1180 | 1181 | SHORT_VERSION_PY = """ 1182 | # This file was generated by 'versioneer.py' (0.16) from 1183 | # revision-control system data, or from the parent directory name of an 1184 | # unpacked source archive. Distribution tarballs contain a pre-generated copy 1185 | # of this file. 1186 | 1187 | import json 1188 | import sys 1189 | 1190 | version_json = ''' 1191 | %s 1192 | ''' # END VERSION_JSON 1193 | 1194 | 1195 | def get_versions(): 1196 | return json.loads(version_json) 1197 | """ 1198 | 1199 | 1200 | def versions_from_file(filename): 1201 | """Try to determine the version from _version.py if present.""" 1202 | try: 1203 | with open(filename) as f: 1204 | contents = f.read() 1205 | except EnvironmentError: 1206 | raise NotThisMethod("unable to read _version.py") 1207 | mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", 1208 | contents, re.M | re.S) 1209 | if not mo: 1210 | raise NotThisMethod("no version_json in _version.py") 1211 | return json.loads(mo.group(1)) 1212 | 1213 | 1214 | def write_to_version_file(filename, versions): 1215 | """Write the given version number to the given _version.py file.""" 1216 | os.unlink(filename) 1217 | contents = json.dumps(versions, sort_keys=True, 1218 | indent=1, separators=(",", ": ")) 1219 | with open(filename, "w") as f: 1220 | f.write(SHORT_VERSION_PY % contents) 1221 | 1222 | print("set %s to '%s'" % (filename, versions["version"])) 1223 | 1224 | 1225 | def plus_or_dot(pieces): 1226 | """Return a + if we don't already have one, else return a .""" 1227 | if "+" in pieces.get("closest-tag", ""): 1228 | return "." 1229 | return "+" 1230 | 1231 | 1232 | def render_pep440(pieces): 1233 | """Build up version string, with post-release "local version identifier". 1234 | 1235 | Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you 1236 | get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty 1237 | 1238 | Exceptions: 1239 | 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] 1240 | """ 1241 | if pieces["closest-tag"]: 1242 | rendered = pieces["closest-tag"] 1243 | if pieces["distance"] or pieces["dirty"]: 1244 | rendered += plus_or_dot(pieces) 1245 | rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) 1246 | if pieces["dirty"]: 1247 | rendered += ".dirty" 1248 | else: 1249 | # exception #1 1250 | rendered = "0+untagged.%d.g%s" % (pieces["distance"], 1251 | pieces["short"]) 1252 | if pieces["dirty"]: 1253 | rendered += ".dirty" 1254 | return rendered 1255 | 1256 | 1257 | def render_pep440_pre(pieces): 1258 | """TAG[.post.devDISTANCE] -- No -dirty. 1259 | 1260 | Exceptions: 1261 | 1: no tags. 0.post.devDISTANCE 1262 | """ 1263 | if pieces["closest-tag"]: 1264 | rendered = pieces["closest-tag"] 1265 | if pieces["distance"]: 1266 | rendered += ".post.dev%d" % pieces["distance"] 1267 | else: 1268 | # exception #1 1269 | rendered = "0.post.dev%d" % pieces["distance"] 1270 | return rendered 1271 | 1272 | 1273 | def render_pep440_post(pieces): 1274 | """TAG[.postDISTANCE[.dev0]+gHEX] . 1275 | 1276 | The ".dev0" means dirty. Note that .dev0 sorts backwards 1277 | (a dirty tree will appear "older" than the corresponding clean one), 1278 | but you shouldn't be releasing software with -dirty anyways. 1279 | 1280 | Exceptions: 1281 | 1: no tags. 0.postDISTANCE[.dev0] 1282 | """ 1283 | if pieces["closest-tag"]: 1284 | rendered = pieces["closest-tag"] 1285 | if pieces["distance"] or pieces["dirty"]: 1286 | rendered += ".post%d" % pieces["distance"] 1287 | if pieces["dirty"]: 1288 | rendered += ".dev0" 1289 | rendered += plus_or_dot(pieces) 1290 | rendered += "g%s" % pieces["short"] 1291 | else: 1292 | # exception #1 1293 | rendered = "0.post%d" % pieces["distance"] 1294 | if pieces["dirty"]: 1295 | rendered += ".dev0" 1296 | rendered += "+g%s" % pieces["short"] 1297 | return rendered 1298 | 1299 | 1300 | def render_pep440_old(pieces): 1301 | """TAG[.postDISTANCE[.dev0]] . 1302 | 1303 | The ".dev0" means dirty. 1304 | 1305 | Eexceptions: 1306 | 1: no tags. 0.postDISTANCE[.dev0] 1307 | """ 1308 | if pieces["closest-tag"]: 1309 | rendered = pieces["closest-tag"] 1310 | if pieces["distance"] or pieces["dirty"]: 1311 | rendered += ".post%d" % pieces["distance"] 1312 | if pieces["dirty"]: 1313 | rendered += ".dev0" 1314 | else: 1315 | # exception #1 1316 | rendered = "0.post%d" % pieces["distance"] 1317 | if pieces["dirty"]: 1318 | rendered += ".dev0" 1319 | return rendered 1320 | 1321 | 1322 | def render_git_describe(pieces): 1323 | """TAG[-DISTANCE-gHEX][-dirty]. 1324 | 1325 | Like 'git describe --tags --dirty --always'. 1326 | 1327 | Exceptions: 1328 | 1: no tags. HEX[-dirty] (note: no 'g' prefix) 1329 | """ 1330 | if pieces["closest-tag"]: 1331 | rendered = pieces["closest-tag"] 1332 | if pieces["distance"]: 1333 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) 1334 | else: 1335 | # exception #1 1336 | rendered = pieces["short"] 1337 | if pieces["dirty"]: 1338 | rendered += "-dirty" 1339 | return rendered 1340 | 1341 | 1342 | def render_git_describe_long(pieces): 1343 | """TAG-DISTANCE-gHEX[-dirty]. 1344 | 1345 | Like 'git describe --tags --dirty --always -long'. 1346 | The distance/hash is unconditional. 1347 | 1348 | Exceptions: 1349 | 1: no tags. HEX[-dirty] (note: no 'g' prefix) 1350 | """ 1351 | if pieces["closest-tag"]: 1352 | rendered = pieces["closest-tag"] 1353 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) 1354 | else: 1355 | # exception #1 1356 | rendered = pieces["short"] 1357 | if pieces["dirty"]: 1358 | rendered += "-dirty" 1359 | return rendered 1360 | 1361 | 1362 | def render(pieces, style): 1363 | """Render the given version pieces into the requested style.""" 1364 | if pieces["error"]: 1365 | return {"version": "unknown", 1366 | "full-revisionid": pieces.get("long"), 1367 | "dirty": None, 1368 | "error": pieces["error"]} 1369 | 1370 | if not style or style == "default": 1371 | style = "pep440" # the default 1372 | 1373 | if style == "pep440": 1374 | rendered = render_pep440(pieces) 1375 | elif style == "pep440-pre": 1376 | rendered = render_pep440_pre(pieces) 1377 | elif style == "pep440-post": 1378 | rendered = render_pep440_post(pieces) 1379 | elif style == "pep440-old": 1380 | rendered = render_pep440_old(pieces) 1381 | elif style == "git-describe": 1382 | rendered = render_git_describe(pieces) 1383 | elif style == "git-describe-long": 1384 | rendered = render_git_describe_long(pieces) 1385 | else: 1386 | raise ValueError("unknown style '%s'" % style) 1387 | 1388 | return {"version": rendered, "full-revisionid": pieces["long"], 1389 | "dirty": pieces["dirty"], "error": None} 1390 | 1391 | 1392 | class VersioneerBadRootError(Exception): 1393 | """The project root directory is unknown or missing key files.""" 1394 | 1395 | 1396 | def get_versions(verbose=False): 1397 | """Get the project version from whatever source is available. 1398 | 1399 | Returns dict with two keys: 'version' and 'full'. 1400 | """ 1401 | if "versioneer" in sys.modules: 1402 | # see the discussion in cmdclass.py:get_cmdclass() 1403 | del sys.modules["versioneer"] 1404 | 1405 | root = get_root() 1406 | cfg = get_config_from_root(root) 1407 | 1408 | assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" 1409 | handlers = HANDLERS.get(cfg.VCS) 1410 | assert handlers, "unrecognized VCS '%s'" % cfg.VCS 1411 | verbose = verbose or cfg.verbose 1412 | assert cfg.versionfile_source is not None, \ 1413 | "please set versioneer.versionfile_source" 1414 | assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" 1415 | 1416 | versionfile_abs = os.path.join(root, cfg.versionfile_source) 1417 | 1418 | # extract version from first of: _version.py, VCS command (e.g. 'git 1419 | # describe'), parentdir. This is meant to work for developers using a 1420 | # source checkout, for users of a tarball created by 'setup.py sdist', 1421 | # and for users of a tarball/zipball created by 'git archive' or github's 1422 | # download-from-tag feature or the equivalent in other VCSes. 1423 | 1424 | get_keywords_f = handlers.get("get_keywords") 1425 | from_keywords_f = handlers.get("keywords") 1426 | if get_keywords_f and from_keywords_f: 1427 | try: 1428 | keywords = get_keywords_f(versionfile_abs) 1429 | ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) 1430 | if verbose: 1431 | print("got version from expanded keyword %s" % ver) 1432 | return ver 1433 | except NotThisMethod: 1434 | pass 1435 | 1436 | try: 1437 | ver = versions_from_file(versionfile_abs) 1438 | if verbose: 1439 | print("got version from file %s %s" % (versionfile_abs, ver)) 1440 | return ver 1441 | except NotThisMethod: 1442 | pass 1443 | 1444 | from_vcs_f = handlers.get("pieces_from_vcs") 1445 | if from_vcs_f: 1446 | try: 1447 | pieces = from_vcs_f(cfg.tag_prefix, root, verbose) 1448 | ver = render(pieces, cfg.style) 1449 | if verbose: 1450 | print("got version from VCS %s" % ver) 1451 | return ver 1452 | except NotThisMethod: 1453 | pass 1454 | 1455 | try: 1456 | if cfg.parentdir_prefix: 1457 | ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) 1458 | if verbose: 1459 | print("got version from parentdir %s" % ver) 1460 | return ver 1461 | except NotThisMethod: 1462 | pass 1463 | 1464 | if verbose: 1465 | print("unable to compute version") 1466 | 1467 | return {"version": "0+unknown", "full-revisionid": None, 1468 | "dirty": None, "error": "unable to compute version"} 1469 | 1470 | 1471 | def get_version(): 1472 | """Get the short version string for this project.""" 1473 | return get_versions()["version"] 1474 | 1475 | 1476 | def get_cmdclass(): 1477 | """Get the custom setuptools/distutils subclasses used by Versioneer.""" 1478 | if "versioneer" in sys.modules: 1479 | del sys.modules["versioneer"] 1480 | # this fixes the "python setup.py develop" case (also 'install' and 1481 | # 'easy_install .'), in which subdependencies of the main project are 1482 | # built (using setup.py bdist_egg) in the same python process. Assume 1483 | # a main project A and a dependency B, which use different versions 1484 | # of Versioneer. A's setup.py imports A's Versioneer, leaving it in 1485 | # sys.modules by the time B's setup.py is executed, causing B to run 1486 | # with the wrong versioneer. Setuptools wraps the sub-dep builds in a 1487 | # sandbox that restores sys.modules to it's pre-build state, so the 1488 | # parent is protected against the child's "import versioneer". By 1489 | # removing ourselves from sys.modules here, before the child build 1490 | # happens, we protect the child from the parent's versioneer too. 1491 | # Also see https://github.com/warner/python-versioneer/issues/52 1492 | 1493 | cmds = {} 1494 | 1495 | # we add "version" to both distutils and setuptools 1496 | from distutils.core import Command 1497 | 1498 | class cmd_version(Command): 1499 | description = "report generated version string" 1500 | user_options = [] 1501 | boolean_options = [] 1502 | 1503 | def initialize_options(self): 1504 | pass 1505 | 1506 | def finalize_options(self): 1507 | pass 1508 | 1509 | def run(self): 1510 | vers = get_versions(verbose=True) 1511 | print("Version: %s" % vers["version"]) 1512 | print(" full-revisionid: %s" % vers.get("full-revisionid")) 1513 | print(" dirty: %s" % vers.get("dirty")) 1514 | if vers["error"]: 1515 | print(" error: %s" % vers["error"]) 1516 | cmds["version"] = cmd_version 1517 | 1518 | # we override "build_py" in both distutils and setuptools 1519 | # 1520 | # most invocation pathways end up running build_py: 1521 | # distutils/build -> build_py 1522 | # distutils/install -> distutils/build ->.. 1523 | # setuptools/bdist_wheel -> distutils/install ->.. 1524 | # setuptools/bdist_egg -> distutils/install_lib -> build_py 1525 | # setuptools/install -> bdist_egg ->.. 1526 | # setuptools/develop -> ? 1527 | 1528 | # we override different "build_py" commands for both environments 1529 | if "setuptools" in sys.modules: 1530 | from setuptools.command.build_py import build_py as _build_py 1531 | else: 1532 | from distutils.command.build_py import build_py as _build_py 1533 | 1534 | class cmd_build_py(_build_py): 1535 | def run(self): 1536 | root = get_root() 1537 | cfg = get_config_from_root(root) 1538 | versions = get_versions() 1539 | _build_py.run(self) 1540 | # now locate _version.py in the new build/ directory and replace 1541 | # it with an updated value 1542 | if cfg.versionfile_build: 1543 | target_versionfile = os.path.join(self.build_lib, 1544 | cfg.versionfile_build) 1545 | print("UPDATING %s" % target_versionfile) 1546 | write_to_version_file(target_versionfile, versions) 1547 | cmds["build_py"] = cmd_build_py 1548 | 1549 | if "cx_Freeze" in sys.modules: # cx_freeze enabled? 1550 | from cx_Freeze.dist import build_exe as _build_exe 1551 | 1552 | class cmd_build_exe(_build_exe): 1553 | def run(self): 1554 | root = get_root() 1555 | cfg = get_config_from_root(root) 1556 | versions = get_versions() 1557 | target_versionfile = cfg.versionfile_source 1558 | print("UPDATING %s" % target_versionfile) 1559 | write_to_version_file(target_versionfile, versions) 1560 | 1561 | _build_exe.run(self) 1562 | os.unlink(target_versionfile) 1563 | with open(cfg.versionfile_source, "w") as f: 1564 | LONG = LONG_VERSION_PY[cfg.VCS] 1565 | f.write(LONG % 1566 | {"DOLLAR": "$", 1567 | "STYLE": cfg.style, 1568 | "TAG_PREFIX": cfg.tag_prefix, 1569 | "PARENTDIR_PREFIX": cfg.parentdir_prefix, 1570 | "VERSIONFILE_SOURCE": cfg.versionfile_source, 1571 | }) 1572 | cmds["build_exe"] = cmd_build_exe 1573 | del cmds["build_py"] 1574 | 1575 | # we override different "sdist" commands for both environments 1576 | if "setuptools" in sys.modules: 1577 | from setuptools.command.sdist import sdist as _sdist 1578 | else: 1579 | from distutils.command.sdist import sdist as _sdist 1580 | 1581 | class cmd_sdist(_sdist): 1582 | def run(self): 1583 | versions = get_versions() 1584 | self._versioneer_generated_versions = versions 1585 | # unless we update this, the command will keep using the old 1586 | # version 1587 | self.distribution.metadata.version = versions["version"] 1588 | return _sdist.run(self) 1589 | 1590 | def make_release_tree(self, base_dir, files): 1591 | root = get_root() 1592 | cfg = get_config_from_root(root) 1593 | _sdist.make_release_tree(self, base_dir, files) 1594 | # now locate _version.py in the new base_dir directory 1595 | # (remembering that it may be a hardlink) and replace it with an 1596 | # updated value 1597 | target_versionfile = os.path.join(base_dir, cfg.versionfile_source) 1598 | print("UPDATING %s" % target_versionfile) 1599 | write_to_version_file(target_versionfile, 1600 | self._versioneer_generated_versions) 1601 | cmds["sdist"] = cmd_sdist 1602 | 1603 | return cmds 1604 | 1605 | 1606 | CONFIG_ERROR = """ 1607 | setup.cfg is missing the necessary Versioneer configuration. You need 1608 | a section like: 1609 | 1610 | [versioneer] 1611 | VCS = git 1612 | style = pep440 1613 | versionfile_source = src/myproject/_version.py 1614 | versionfile_build = myproject/_version.py 1615 | tag_prefix = 1616 | parentdir_prefix = myproject- 1617 | 1618 | You will also need to edit your setup.py to use the results: 1619 | 1620 | import versioneer 1621 | setup(version=versioneer.get_version(), 1622 | cmdclass=versioneer.get_cmdclass(), ...) 1623 | 1624 | Please read the docstring in ./versioneer.py for configuration instructions, 1625 | edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. 1626 | """ 1627 | 1628 | SAMPLE_CONFIG = """ 1629 | # See the docstring in versioneer.py for instructions. Note that you must 1630 | # re-run 'versioneer.py setup' after changing this section, and commit the 1631 | # resulting files. 1632 | 1633 | [versioneer] 1634 | #VCS = git 1635 | #style = pep440 1636 | #versionfile_source = 1637 | #versionfile_build = 1638 | #tag_prefix = 1639 | #parentdir_prefix = 1640 | 1641 | """ 1642 | 1643 | INIT_PY_SNIPPET = """ 1644 | from ._version import get_versions 1645 | __version__ = get_versions()['version'] 1646 | del get_versions 1647 | """ 1648 | 1649 | 1650 | def do_setup(): 1651 | """Main VCS-independent setup function for installing Versioneer.""" 1652 | root = get_root() 1653 | try: 1654 | cfg = get_config_from_root(root) 1655 | except (EnvironmentError, configparser.NoSectionError, 1656 | configparser.NoOptionError) as e: 1657 | if isinstance(e, (EnvironmentError, configparser.NoSectionError)): 1658 | print("Adding sample versioneer config to setup.cfg", 1659 | file=sys.stderr) 1660 | with open(os.path.join(root, "setup.cfg"), "a") as f: 1661 | f.write(SAMPLE_CONFIG) 1662 | print(CONFIG_ERROR, file=sys.stderr) 1663 | return 1 1664 | 1665 | print(" creating %s" % cfg.versionfile_source) 1666 | with open(cfg.versionfile_source, "w") as f: 1667 | LONG = LONG_VERSION_PY[cfg.VCS] 1668 | f.write(LONG % {"DOLLAR": "$", 1669 | "STYLE": cfg.style, 1670 | "TAG_PREFIX": cfg.tag_prefix, 1671 | "PARENTDIR_PREFIX": cfg.parentdir_prefix, 1672 | "VERSIONFILE_SOURCE": cfg.versionfile_source, 1673 | }) 1674 | 1675 | ipy = os.path.join(os.path.dirname(cfg.versionfile_source), 1676 | "__init__.py") 1677 | if os.path.exists(ipy): 1678 | try: 1679 | with open(ipy, "r") as f: 1680 | old = f.read() 1681 | except EnvironmentError: 1682 | old = "" 1683 | if INIT_PY_SNIPPET not in old: 1684 | print(" appending to %s" % ipy) 1685 | with open(ipy, "a") as f: 1686 | f.write(INIT_PY_SNIPPET) 1687 | else: 1688 | print(" %s unmodified" % ipy) 1689 | else: 1690 | print(" %s doesn't exist, ok" % ipy) 1691 | ipy = None 1692 | 1693 | # Make sure both the top-level "versioneer.py" and versionfile_source 1694 | # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so 1695 | # they'll be copied into source distributions. Pip won't be able to 1696 | # install the package without this. 1697 | manifest_in = os.path.join(root, "MANIFEST.in") 1698 | simple_includes = set() 1699 | try: 1700 | with open(manifest_in, "r") as f: 1701 | for line in f: 1702 | if line.startswith("include "): 1703 | for include in line.split()[1:]: 1704 | simple_includes.add(include) 1705 | except EnvironmentError: 1706 | pass 1707 | # That doesn't cover everything MANIFEST.in can do 1708 | # (http://docs.python.org/2/distutils/sourcedist.html#commands), so 1709 | # it might give some false negatives. Appending redundant 'include' 1710 | # lines is safe, though. 1711 | if "versioneer.py" not in simple_includes: 1712 | print(" appending 'versioneer.py' to MANIFEST.in") 1713 | with open(manifest_in, "a") as f: 1714 | f.write("include versioneer.py\n") 1715 | else: 1716 | print(" 'versioneer.py' already in MANIFEST.in") 1717 | if cfg.versionfile_source not in simple_includes: 1718 | print(" appending versionfile_source ('%s') to MANIFEST.in" % 1719 | cfg.versionfile_source) 1720 | with open(manifest_in, "a") as f: 1721 | f.write("include %s\n" % cfg.versionfile_source) 1722 | else: 1723 | print(" versionfile_source already in MANIFEST.in") 1724 | 1725 | # Make VCS-specific changes. For git, this means creating/changing 1726 | # .gitattributes to mark _version.py for export-time keyword 1727 | # substitution. 1728 | do_vcs_install(manifest_in, cfg.versionfile_source, ipy) 1729 | return 0 1730 | 1731 | 1732 | def scan_setup_py(): 1733 | """Validate the contents of setup.py against Versioneer's expectations.""" 1734 | found = set() 1735 | setters = False 1736 | errors = 0 1737 | with open("setup.py", "r") as f: 1738 | for line in f.readlines(): 1739 | if "import versioneer" in line: 1740 | found.add("import") 1741 | if "versioneer.get_cmdclass()" in line: 1742 | found.add("cmdclass") 1743 | if "versioneer.get_version()" in line: 1744 | found.add("get_version") 1745 | if "versioneer.VCS" in line: 1746 | setters = True 1747 | if "versioneer.versionfile_source" in line: 1748 | setters = True 1749 | if len(found) != 3: 1750 | print("") 1751 | print("Your setup.py appears to be missing some important items") 1752 | print("(but I might be wrong). Please make sure it has something") 1753 | print("roughly like the following:") 1754 | print("") 1755 | print(" import versioneer") 1756 | print(" setup( version=versioneer.get_version(),") 1757 | print(" cmdclass=versioneer.get_cmdclass(), ...)") 1758 | print("") 1759 | errors += 1 1760 | if setters: 1761 | print("You should remove lines like 'versioneer.VCS = ' and") 1762 | print("'versioneer.versionfile_source = ' . This configuration") 1763 | print("now lives in setup.cfg, and should be removed from setup.py") 1764 | print("") 1765 | errors += 1 1766 | return errors 1767 | 1768 | if __name__ == "__main__": 1769 | cmd = sys.argv[1] 1770 | if cmd == "setup": 1771 | errors = do_setup() 1772 | errors += scan_setup_py() 1773 | if errors: 1774 | sys.exit(1) 1775 | --------------------------------------------------------------------------------