{"id":39509961,"url":"https://github.com/annahadji/unitreport","last_synced_at":"2026-01-18T06:01:07.547Z","repository":{"id":62586421,"uuid":"276966633","full_name":"annahadji/unitreport","owner":"annahadji","description":"A small unittest-based tool for generating single page html reports in Python","archived":false,"fork":false,"pushed_at":"2023-06-23T19:40:43.000Z","size":159,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-19T03:38:07.705Z","etag":null,"topics":["generation","plotting","python","report","unittest"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/annahadji.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-07-03T18:45:39.000Z","updated_at":"2023-12-07T00:24:49.000Z","dependencies_parsed_at":"2023-01-31T19:10:12.658Z","dependency_job_id":null,"html_url":"https://github.com/annahadji/unitreport","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/annahadji/unitreport","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/annahadji%2Funitreport","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/annahadji%2Funitreport/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/annahadji%2Funitreport/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/annahadji%2Funitreport/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/annahadji","download_url":"https://codeload.github.com/annahadji/unitreport/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/annahadji%2Funitreport/sbom","scorecard":{"id":197489,"data":{"date":"2025-08-11","repo":{"name":"github.com/annahadji/unitreport","commit":"579984bdfca94d0f54eba4ae8b62f421b1801739"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/publish-to-pypi.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish-to-pypi.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/annahadji/unitreport/publish-to-pypi.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish-to-pypi.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/annahadji/unitreport/publish-to-pypi.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/publish-to-pypi.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/annahadji/unitreport/publish-to-pypi.yml/main?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/publish-to-pypi.yml:20","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   0 out of   1 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-16T22:05:44.750Z","repository_id":62586421,"created_at":"2025-08-16T22:05:44.751Z","updated_at":"2025-08-16T22:05:44.751Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28531991,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T00:39:45.795Z","status":"online","status_checked_at":"2026-01-18T02:00:07.578Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["generation","plotting","python","report","unittest"],"created_at":"2026-01-18T06:01:06.803Z","updated_at":"2026-01-18T06:01:07.529Z","avatar_url":"https://github.com/annahadji.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# :page_facing_up: unitreport ![](https://github.com/annahadji/unitreport/workflows/Publish%20to%20PyPI/badge.svg) ![PyPI](https://img.shields.io/pypi/v/unitreport)\n\nUnitReport is a small unittest-based tool for generating single page html reports in Python.\nThe reports can include matplotlib figures and html tables.\nIt is designed to be minimal and fit into the unittesting framework, allowing users to make assertions about their data (e.g. data quality) before generating figures.\n\n![unitreport](https://raw.githubusercontent.com/annahadji/unitreport/master/screenshot.png)\n\n## Getting Started\n\nYou can install the library using,\n\n`pip3 install unitreport`\n\nThere are usage examples in `test_plots.py` and `test_tables.py`.\nYou need to create test cases using the unittest Python library, and utilise unitreport's decorators:\n\n```python\nimport unittest\nimport unitreport\n\nimport seaborn as sns\nimport pandas as pd\n\nclass TestExample(unittest.TestCase):\n    \"\"\"Example test suite producing plots and tables in a report using unitreport.\"\"\"\n\n    dataset: pd.DataFrame = None\n\n    @classmethod\n    def setUpClass(cls):\n        \"\"\"Load dataset on setup.\"\"\"\n        cls.dataset = sns.load_dataset(\"fmri\")\n\n    @unitreport.plotting\n    def test_timepoint_vs_signal_by_event(self):\n        \"\"\"*fMRI data:* timepoint versus signal for stim and cue events.\"\"\"\n        # you can still run assertions to check data quality before plotting\n        self.assertEqual(self.dataset.shape, (1064, 5))\n\n        # plotting decorator will call plt.savefig() to generate the plot\n        sns.relplot(\n            x=\"timepoint\",\n            y=\"signal\",\n            hue=\"event\",\n            style=\"event\",\n            kind=\"line\",\n            data=self.dataset,\n        )\n        # could also return a figure \u0026 .savefig() will be called on returned object\n\n    @unitreport.tabling\n    def test_region_counts(self):\n        \"\"\"*fMRI data:* table summary description of timepoints and signals.\"\"\"\n        # you can still run assertions to check data quality before making table\n        self.assertEqual(self.dataset.shape, (1064, 5))\n\n        return self.dataset.describe().to_html()\n```\n\nYou can run the tests using,\n\n`python3 -m unitreport`\n\nThis will discover and run the tests (which could be across multiple test files), generate the report and save it to the current directory.\n\nFor extra parameters you can run the following,\n\n```\npython3 -m unitreport -h\n\nusage: __main__.py [-h] [--tests_dir TESTS_DIR] [--pattern PATTERN]\n                   [--templates_dir TEMPLATES_DIR] [--output_file OUTPUT_FILE]\n\noptional arguments:\n  -h, --help            show this help message and exit\n  --tests_dir TESTS_DIR\n                        Path to test files. (default: .)\n  --pattern PATTERN     File patterns to discover test cases in. (default:\n                        test*.py)\n  --templates_dir TEMPLATES_DIR\n                        Path to jinja2 templates directory including\n                        index.html and main.css. (default: (unitreport) templates)\n  --output_file OUTPUT_FILE\n                        Output path including name. (default: report.html)\n```\n\nThere are template `index.html` and `main.css` files which will be used by default to generate the style of the report.\nYou can also specify a path to your own templates using `--templates_dir`, where the html Jinja2 template can expect to receive `date` (today's date), and `figures`, a dictionary with test function names as keys mapped to values of `type` (table or plot), `content` (svg or html table) and `description` (test function's docstring).\n\nYou can also invoke unitreport from a Python script using the library's `main()` (runs tests and generates report), `discover_and_run()` (only run tests) and `generate_report()` (only generate report) functions.\nThese utilise the default values for the above parameters if not specified by the user, and `generate_report()` uses the global FIGURES dictionary generated from the tests if not passed. For more details on what they do, you can check the source code.\n\n```python\nimport unitreport\n\n# result is a unittest.TestResult which you can access things such as result.errors\nresult, figs = unitreport.discover_and_run()\nprint(result) # \u003cunittest.runner.TextTestResult run=3 errors=0 failures=0\u003e\nprint(figs) # Dict with test function names as keys mapped to 'type', 'content', and 'description'\n# html_report is a string containing the generated report\nhtml_report = unitreport.generate_report()\n\n# same as above, raises assertion error if there are errors in tests or no tests found\nresult, figs, html_report = unitreport.main()\n```\n\n## Built With\n\n- [unittest](https://docs.python.org/3/library/unittest.html) - underlying testing framework\n- [Jinja2](https://jinja.palletsprojects.com/en/2.11.x/) - rendering and generating the report\n- [matplotlib](https://matplotlib.org/) - plotting library\n- [Markdown](https://python-markdown.github.io/) - Python markdown library for markdown captions\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fannahadji%2Funitreport","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fannahadji%2Funitreport","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fannahadji%2Funitreport/lists"}