PK|Ikc&ffcodacy/__init__.pyfrom __future__ import absolute_import from . import reporter def main(): return reporter.run() PK|ICUnncodacy/reporter.py"""Codacy coverage reporter for Python""" import argparse import contextlib import json import logging import os from xml.dom import minidom import requests from requests.packages.urllib3 import util as urllib3_util logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') CODACY_PROJECT_TOKEN = os.getenv('CODACY_PROJECT_TOKEN') CODACY_BASE_API_URL = os.getenv('CODACY_API_BASE_URL', 'https://api.codacy.com') URL = CODACY_BASE_API_URL + '/2.0/coverage/{commit}/python' DEFAULT_REPORT_FILE = 'coverage.xml' MAX_RETRIES = 3 BAD_REQUEST = 400 class _Retry(urllib3_util.Retry): def is_forced_retry(self, method, status_code): return status_code >= BAD_REQUEST @contextlib.contextmanager def _request_session(): retry = _Retry(total=MAX_RETRIES, raise_on_redirect=False) session = requests.Session() session.mount("https://", requests.adapters.HTTPAdapter(max_retries=retry)) with session: yield session def get_git_revision_hash(): import subprocess return subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode("utf-8").strip() def get_git_directory(): import subprocess return subprocess.check_output(['git', 'rev-parse', '--show-toplevel']).decode("utf-8").strip() def file_exists(rootdir, filename): for root, subFolders, files in os.walk(rootdir): if filename in files: return True else: for subFolder in subFolders: return file_exists(os.path.join(rootdir, subFolder), filename) return False def generate_filename(sources, filename, git_directory): def strip_prefix(line, prefix): if line.startswith(prefix): return line[len(prefix):] else: return line if not git_directory: git_directory = get_git_directory() for source in sources: if file_exists(source, filename): return strip_prefix(source, git_directory).strip("/") + "/" + filename.strip("/") return filename def merge_and_round_reports(report_list): """Merges together several report structures from parse_report_file (and rounds all values)""" if len(report_list) == 1: final_report = report_list[0] else: final_report = { 'language': "python", 'fileReports': [] } total_lines = 0 for report in report_list: # First, merge together detailed report structures # This assumes no overlap # TODO: What should we do if there is a file listed multiple times? final_report['fileReports'] += report['fileReports'] total_lines += report['codeLines'] # Coverage weighted average (by number of lines of code) of all files average_sum = 0 for file_entry in final_report['fileReports']: average_sum += file_entry['total'] * file_entry['codeLines'] final_report['total'] = average_sum / total_lines final_report['codeLines'] = total_lines # Round all total values for file_entry in final_report['fileReports']: file_entry['total'] = int(file_entry['total']) final_report['total'] = int(final_report['total']) return final_report def parse_report_file(report_file, git_directory): """Parse XML file and POST it to the Codacy API :param report_file: """ # Convert decimal string to decimal percent value def percent(s): return float(s) * 100 # Parse the XML into the format expected by the API report_xml = minidom.parse(report_file) report = { 'language': "python", 'total': percent(report_xml.getElementsByTagName('coverage')[0].attributes['line-rate'].value), 'fileReports': [], } sources = [x.firstChild.nodeValue for x in report_xml.getElementsByTagName('source')] classes = report_xml.getElementsByTagName('class') total_lines = 0 for cls in classes: lines = cls.getElementsByTagName('line') total_lines += len(lines) file_report = { 'filename': generate_filename(sources, cls.attributes['filename'].value, git_directory), 'total': percent(cls.attributes['line-rate'].value), 'codeLines': len(lines), 'coverage': {}, } for line in lines: hits = int(line.attributes['hits'].value) if hits >= 1: # The API assumes 0 if a line is missing file_report['coverage'][line.attributes['number'].value] = hits report['fileReports'] += [file_report] report['codeLines'] = total_lines return report def upload_report(report, token, commit): """Try to send the data, raise an exception if we fail""" url = URL.format(commit=commit) data = json.dumps(report) headers = { "project_token": token, "Content-Type": "application/json" } logging.debug(data) with _request_session() as session: r = session.post(url, data=data, headers=headers, allow_redirects=True) logging.debug(r.content) r.raise_for_status() response = json.loads(r.text) try: logging.info(response['success']) except KeyError: logging.error(response['error']) def run(): parser = argparse.ArgumentParser(description='Codacy coverage reporter for Python.') parser.add_argument("-r", "--report", help="coverage report file", default=[], type=str, action='append') parser.add_argument("-c", "--commit", type=str, help="git commit hash") parser.add_argument("-d", "--directory", type=str, help="git top level directory") parser.add_argument("-v", "--verbose", help="show debug information", action="store_true") args = parser.parse_args() if args.verbose: logging.Logger.setLevel(logging.getLogger(), logging.DEBUG) if not CODACY_PROJECT_TOKEN: logging.error("environment variable CODACY_PROJECT_TOKEN is not defined.") exit(1) if not args.commit: args.commit = get_git_revision_hash() if not args.report: args.report.append(DEFAULT_REPORT_FILE) # Explictly check ALL files before parsing any for rfile in args.report: if not os.path.isfile(rfile): logging.error("Coverage report " + rfile + " not found.") exit(1) reports = [] for rfile in args.report: logging.info("Parsing report file %s...", rfile) reports.append(parse_report_file(rfile, args.directory)) report = merge_and_round_reports(reports) logging.info("Uploading report...") upload_report(report, CODACY_PROJECT_TOKEN, args.commit) PK|Ix1,/codacy_coverage-1.3.6.dist-info/DESCRIPTION.rstpython-codacy-coverage ====================== Credits to Ryan for creating this! Python coverage reporter for Codacy https://www.codacy.com .. image:: https://api.codacy.com/project/badge/grade/3a8cf06a9db94d0ab3d55e0357bc8f9d :target: https://www.codacy.com/app/Codacy/python-codacy-coverage :alt: Codacy Badge .. image:: https://api.codacy.com/project/badge/coverage/3a8cf06a9db94d0ab3d55e0357bc8f9d :target: https://www.codacy.com/app/Codacy/python-codacy-coverage :alt: Codacy Badge .. image:: https://circleci.com/gh/codacy/python-codacy-coverage.png?style=shield&circle-token=:circle-token :target: https://circleci.com/gh/codacy/python-codacy-coverage :alt: Build Status .. image:: https://badge.fury.io/py/codacy-coverage.svg :target: https://badge.fury.io/py/codacy-coverage :alt: PyPI version Setup ----- Codacy assumes that coverage is previously configured for your project. To generate the required coverage XML file, calculate coverage for your project as normal, by running: ``coverage xml`` Install codacy-coverage ~~~~~~~~~~~~~~~~~~~~~~~ You can install the coverage reporter by running: ``pip install codacy-coverage`` Updating Codacy --------------- To update Codacy, you will need your project API token. You can create the token in `Project -> Settings -> Integrations -> Add Integration -> Project API` Then set it in your terminal, replacing %Project_Token% with your own token: ``export CODACY_PROJECT_TOKEN=%Project_Token%`` **Enterprise only** (Skip this step if you are using https://www.codacy.com) To send coverage in the enterprise version you should: ``export CODACY_API_BASE_URL=:16006`` **Upload Coverage** Next, simply run the Codacy reporter. It will find the current commit and send all details to your project dashboard: ``python-codacy-coverage -r coverage.xml`` Note: You should keep your API token well **protected**, as it grants owner permissions to your projects. Troubleshoot --------------- If you are using any CI that does not have .git information, you can specify the commit with -c and the clone directory with -d. For example if you are using AppVeyor you can: ``python-codacy-coverage -c $APPVEYOR_REPO_COMMIT -d $APPVEYOR_BUILD_FOLDER -r coverage.xml`` PK|I:880codacy_coverage-1.3.6.dist-info/entry_points.txt[console_scripts] python-codacy-coverage = codacy:main PK|I BB-codacy_coverage-1.3.6.dist-info/metadata.json{"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Topic :: Software Development :: Build Tools", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3"], "extensions": {"python.commands": {"wrap_console": {"python-codacy-coverage": "codacy:main"}}, "python.details": {"contacts": [{"email": "team@codacy.com", "name": "Codacy", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/codacy/python-codacy-coverage"}}, "python.exports": {"console_scripts": {"python-codacy-coverage": "codacy:main"}}}, "extras": ["dev", "test"], "generator": "bdist_wheel (0.26.0)", "keywords": ["development", "coverage"], "license": "MIT", "metadata_version": "2.0", "name": "codacy-coverage", "run_requires": [{"extra": "dev", "requires": ["check-manifest"]}, {"extra": "test", "requires": ["coverage", "nosetests"]}, {"requires": ["requests (>=2.9.1)"]}], "summary": "Codacy coverage reporter for Python", "version": "1.3.6"}PK|I+e-codacy_coverage-1.3.6.dist-info/top_level.txtcodacy PK|Indnn%codacy_coverage-1.3.6.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any PK|I8! (codacy_coverage-1.3.6.dist-info/METADATAMetadata-Version: 2.0 Name: codacy-coverage Version: 1.3.6 Summary: Codacy coverage reporter for Python Home-page: https://github.com/codacy/python-codacy-coverage Author: Codacy Author-email: team@codacy.com License: MIT Keywords: development coverage Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Topic :: Software Development :: Build Tools Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Requires-Dist: requests (>=2.9.1) Provides-Extra: dev Requires-Dist: check-manifest; extra == 'dev' Provides-Extra: test Requires-Dist: coverage; extra == 'test' Requires-Dist: nosetests; extra == 'test' python-codacy-coverage ====================== Credits to Ryan for creating this! Python coverage reporter for Codacy https://www.codacy.com .. image:: https://api.codacy.com/project/badge/grade/3a8cf06a9db94d0ab3d55e0357bc8f9d :target: https://www.codacy.com/app/Codacy/python-codacy-coverage :alt: Codacy Badge .. image:: https://api.codacy.com/project/badge/coverage/3a8cf06a9db94d0ab3d55e0357bc8f9d :target: https://www.codacy.com/app/Codacy/python-codacy-coverage :alt: Codacy Badge .. image:: https://circleci.com/gh/codacy/python-codacy-coverage.png?style=shield&circle-token=:circle-token :target: https://circleci.com/gh/codacy/python-codacy-coverage :alt: Build Status .. image:: https://badge.fury.io/py/codacy-coverage.svg :target: https://badge.fury.io/py/codacy-coverage :alt: PyPI version Setup ----- Codacy assumes that coverage is previously configured for your project. To generate the required coverage XML file, calculate coverage for your project as normal, by running: ``coverage xml`` Install codacy-coverage ~~~~~~~~~~~~~~~~~~~~~~~ You can install the coverage reporter by running: ``pip install codacy-coverage`` Updating Codacy --------------- To update Codacy, you will need your project API token. You can create the token in `Project -> Settings -> Integrations -> Add Integration -> Project API` Then set it in your terminal, replacing %Project_Token% with your own token: ``export CODACY_PROJECT_TOKEN=%Project_Token%`` **Enterprise only** (Skip this step if you are using https://www.codacy.com) To send coverage in the enterprise version you should: ``export CODACY_API_BASE_URL=:16006`` **Upload Coverage** Next, simply run the Codacy reporter. It will find the current commit and send all details to your project dashboard: ``python-codacy-coverage -r coverage.xml`` Note: You should keep your API token well **protected**, as it grants owner permissions to your projects. Troubleshoot --------------- If you are using any CI that does not have .git information, you can specify the commit with -c and the clone directory with -d. For example if you are using AppVeyor you can: ``python-codacy-coverage -c $APPVEYOR_REPO_COMMIT -d $APPVEYOR_BUILD_FOLDER -r coverage.xml`` PK|IP&codacy_coverage-1.3.6.dist-info/RECORDcodacy/__init__.py,sha256=C8HkvBGjmT3V54VTjPA58EVv2TQ0gvsEMjLe0T-Km8Q,102 codacy/reporter.py,sha256=sHHTVjT4sExe5UXOOUCkhXY-VSCEbh9hIk80IkSJ0Mw,6766 codacy_coverage-1.3.6.dist-info/DESCRIPTION.rst,sha256=pKNTQQLVZOC3lLnqekabnE_rB4uw0tcBAsjEpG17GZc,2276 codacy_coverage-1.3.6.dist-info/METADATA,sha256=qNTgpPcBHlNyQZvW-eZ8gw9sbo8nyAzLl2qbn1ZpIgs,3058 codacy_coverage-1.3.6.dist-info/RECORD,, codacy_coverage-1.3.6.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110 codacy_coverage-1.3.6.dist-info/entry_points.txt,sha256=jXtZe0XJBztNSYRWsZJz4VNUcIwrd6y7Z6tbMKMmAvI,56 codacy_coverage-1.3.6.dist-info/metadata.json,sha256=5HD0_25qngFMnTP4b0yAwax3Ydyi8gRtmeb3gJF1miY,1090 codacy_coverage-1.3.6.dist-info/top_level.txt,sha256=ZAwMxK_v8tiOcPkMoeYiPh8mSrPz_s2aW3ouB2Tj8o0,7 PK|Ikc&ffcodacy/__init__.pyPK|ICUnncodacy/reporter.pyPK|Ix1,/4codacy_coverage-1.3.6.dist-info/DESCRIPTION.rstPK|I:880e$codacy_coverage-1.3.6.dist-info/entry_points.txtPK|I BB-$codacy_coverage-1.3.6.dist-info/metadata.jsonPK|I+e-x)codacy_coverage-1.3.6.dist-info/top_level.txtPK|Indnn%)codacy_coverage-1.3.6.dist-info/WHEELPK|I8! ({*codacy_coverage-1.3.6.dist-info/METADATAPK|IP&6codacy_coverage-1.3.6.dist-info/RECORDPK :