{"id":34774864,"url":"https://github.com/cloudtruth/interposer","last_synced_at":"2025-12-25T08:13:53.031Z","repository":{"id":37985983,"uuid":"288465632","full_name":"cloudtruth/interposer","owner":"cloudtruth","description":"A code intercept wrapper with recording and playback options to turn third party live integration tests into unit tests, audit calls, or inject secrets.","archived":false,"fork":false,"pushed_at":"2023-09-25T02:46:03.000Z","size":276,"stargazers_count":0,"open_issues_count":10,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-09-22T04:39:42.034Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cloudtruth.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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-08-18T13:35:58.000Z","updated_at":"2022-01-02T13:43:03.000Z","dependencies_parsed_at":"2023-02-10T19:35:19.374Z","dependency_job_id":null,"html_url":"https://github.com/cloudtruth/interposer","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/cloudtruth/interposer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudtruth%2Finterposer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudtruth%2Finterposer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudtruth%2Finterposer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudtruth%2Finterposer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cloudtruth","download_url":"https://codeload.github.com/cloudtruth/interposer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudtruth%2Finterposer/sbom","scorecard":{"id":293346,"data":{"date":"2025-08-11","repo":{"name":"github.com/cloudtruth/interposer","commit":"be066096112fca2e6c1fc8b8e0c7b6c8116d6538"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.2,"checks":[{"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":"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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Info: jobLevel 'actions' permission set to 'read': .github/workflows/test.yaml:21","Info: jobLevel 'contents' permission set to 'read': .github/workflows/test.yaml:22","Warn: no topLevel permission defined: .github/workflows/release.yaml:1","Warn: no topLevel permission defined: .github/workflows/test.yaml: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":"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":"Code-Review","score":0,"reason":"Found 1/13 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":"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/release.yaml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/cloudtruth/interposer/release.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yaml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/cloudtruth/interposer/release.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yaml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/cloudtruth/interposer/release.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yaml:58: update your workflow using https://app.stepsecurity.io/secureworkflow/cloudtruth/interposer/test.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yaml:61: update your workflow using https://app.stepsecurity.io/secureworkflow/cloudtruth/interposer/test.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yaml:66: update your workflow using https://app.stepsecurity.io/secureworkflow/cloudtruth/interposer/test.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yaml:75: update your workflow using https://app.stepsecurity.io/secureworkflow/cloudtruth/interposer/test.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yaml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/cloudtruth/interposer/test.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yaml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/cloudtruth/interposer/test.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yaml:34: update your workflow using https://app.stepsecurity.io/secureworkflow/cloudtruth/interposer/test.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yaml:39: update your workflow using https://app.stepsecurity.io/secureworkflow/cloudtruth/interposer/test.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yaml:45: update your workflow using https://app.stepsecurity.io/secureworkflow/cloudtruth/interposer/test.yaml/main?enable=pin","Info:   0 out of   7 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   5 third-party GitHubAction 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":"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":"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: Apache License 2.0: 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":"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":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"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"}},{"name":"Vulnerabilities","score":0,"reason":"19 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2024-230 / GHSA-248v-346w-9cwc","Warn: Project is vulnerable to: PYSEC-2022-42986 / GHSA-43fp-rhv2-5gv8","Warn: Project is vulnerable to: PYSEC-2023-135 / GHSA-xqr8-7jwr-rhp7","Warn: Project is vulnerable to: PYSEC-2024-4 / GHSA-2mqj-m65w-jghx","Warn: Project is vulnerable to: PYSEC-2023-165 / GHSA-cwvm-v4w8-q58c","Warn: Project is vulnerable to: PYSEC-2022-42992 / GHSA-hcpj-qp55-gfph","Warn: Project is vulnerable to: PYSEC-2023-137 / GHSA-pr76-5cm5-w9cj","Warn: Project is vulnerable to: PYSEC-2023-161 / GHSA-wfm5-v35h-vwf4","Warn: Project is vulnerable to: PYSEC-2024-60 / GHSA-jjg7-2v4v-x38h","Warn: Project is vulnerable to: PYSEC-2022-42969","Warn: Project is vulnerable to: GHSA-9hjg-9r4m-mvj7","Warn: Project is vulnerable to: GHSA-9wx4-h78v-vm56","Warn: Project is vulnerable to: PYSEC-2023-74 / GHSA-j8r2-6x86-q33q","Warn: Project is vulnerable to: GHSA-34jh-p97f-mpxf","Warn: Project is vulnerable to: PYSEC-2023-212 / GHSA-g4mx-q9vg-27p4","Warn: Project is vulnerable to: GHSA-pq67-6m6q-mj2v","Warn: Project is vulnerable to: PYSEC-2023-192 / GHSA-v845-jxx5-vc9f","Warn: Project is vulnerable to: PYSEC-2024-187 / GHSA-rqc4-2hc7-8c8v","Warn: Project is vulnerable to: GHSA-jfmj-5v4g-7637"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":7,"reason":"SAST tool detected but not run on all commits","details":["Info: SAST configuration detected: CodeQL","Warn: 0 commits out of 20 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T18:52:28.954Z","repository_id":37985983,"created_at":"2025-08-17T18:52:28.954Z","updated_at":"2025-08-17T18:52:28.954Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28024397,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-25T02:00:05.988Z","response_time":58,"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":[],"created_at":"2025-12-25T08:13:51.798Z","updated_at":"2025-12-25T08:13:53.018Z","avatar_url":"https://github.com/cloudtruth.png","language":"Python","readme":"# interposer\n\n[![Build Status](https://github.com/cloudtruth/interposer/workflows/Test/badge.svg)](https://github.com/cloudtruth/interposer/actions?query=workflow%3Atest)\n[![Release Status](https://github.com/cloudtruth/interposer/workflows/release/badge.svg)](https://github.com/cloudtruth/interposer/actions?query=workflow%3Arelease)\n[![codecov](https://codecov.io/gh/cloudtruth/interposer/branch/main/graph/badge.svg?token=JUplpBrqB0)](https://codecov.io/gh/cloudtruth/interposer)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n\nThe interposer package core allows you to wrap a module, class, object, method,\nor function with the ability to perform pre- and post- call analysis or\nmanipulation on the arguments, result, or exception.  This behavior can either\nbe \"always on\" (i.e. in production code) or patched in through tests.  With\ninterposer you can:\n\n- Audit calls and their responses or exceptions.\n- Block calls that should not be made (for example, read-only vs. read-write).\n- Modify arguments before calls are made.\n- Record and playback interactions with packages for hybrid testing.\n\nClassic unit testing involves writing mocks or simulators for third party\nservices.  When a service is mocked, the test is typically only as good as\nthe simulation.  Classic integration testing runs live against a service,\nbut it can take too long to be useful in normal development workflow.  What\nif you could have both?  You can - we call it hybrid testing.\n\nHybrid testing allows you to test your code live against a third party service\nonly when necessary and avoid the need to write your own mocks.  It is\nessentially a self-writing mock for your interaction.  Service mocks tend to\nbe incomplete simulations and can lead to a false sense of security, however by\nusing hybrid testing, you no longer have to worry about that.  Even better, the\nprovided recording system includes a way to automatically redact secrets and\nstill be able to play back.  If a live test against a service takes minutes,\nit will only take seconds when played back.\n\n## TL;DR;\n\nInterposer can be inserted around anything - modules, classes, or functions.\nWhat you do with it from there is up to you.  A recording and playback system\nis provided that works with just about anything.\n\n### Hybrid Testing\n\nTo get started with hybrid testing, use the `RecordedTestCase` test fixture.\nAn example of this can be found in the\n[example_weather_test](https://github.com/tuono/interposer/blob/develop/tests/example_weather_test.py).\nThis is a simple test that demonstrates how easy it is to hook in recording\nand playback against an external service.  In contrast to projects like `vcrpy`\nwhich only patch into specific network libraries, interposer allows you to\ncapture the call and responses for anything.\n\nTo generate a recording, `RecordedTestCase` looks for an environment variable\nnamed `RECORDING` and if set (and not empty), will generate a recording of the\ninteraction with the interposed class(es) automatically:\n\n```bash\n$ time RECORDING=1 make example\n...\nreal    0m8.651s\nuser    0m1.911s\nsys     0m0.219s\n\n$ tests/\ntests/:\ntotal 44\n-rw-r--r-- 1 testr testr    83 Sep 18 13:59 __init__.py\n-rw-r--r-- 1 testr testr   535 Sep 18 13:59 example_weather_test.py\n-rw-r--r-- 1 testr testr 11795 Sep 18 21:15 interposer_test.py\n-rw-r--r-- 1 testr testr  8483 Sep 19 22:44 recorder_test.py\n-rw-r--r-- 1 testr testr  8152 Sep 19 22:44 tapedeck_test.py\ndrwxr-xr-x 3 testr testr  4096 Sep 20 07:57 tapes\n\ntests/tapes:\ntotal 4\ndrwxr-xr-x 2 testr testr 4096 Sep 20 07:29 example_weather_test\n\ntests/tapes/example_weather_test:\ntotal 4\n-rw-r--r-- 1 testr testr 1678 Sep 20 07:14 TestWeather.db.gz\n```\n\nOnce the recording is generated, running the test again without the\nenvironment variable causes the playback to happen:\n\n```bash\n$ time tox example_weather_test.py\n...\nreal    0m2.039s\nuser    0m1.822s\nsys     0m0.212s\n```\n\nGiven tox has a roughly 2 second startup time, we see the playback is\nessentially as fast as a handcrafted mock, but took way less time to make!\nMore details can be found in the Recording and Playback section below.\n\n## Background\n\nAt Tuono when we first started working with the AWS and Azure SDKs, we\nrealized that it would not be practical to mock those services in our\ntests.  Mocking a complex multi-step interaction with a third party service\nsuch as a cloud provider can be very time-consuming and error-prone.\nEntire projects already exist which attempt to mock these service interfaces,\nand those projects are often both incomplete and incorrect at any given time.\nMaintaining such a footprint requires tremendous effort, and if the mock\nresponses are not correct, it leads to a false sense of code quality which\ncan then fail in front of a customer when used against the real thing.\n\nSome may argue that separate integration testing would catch this failure mode,\nhowever that defers the problem until after the code is developed and mocked,\nwhich makes it more expensive to remedy.  We started to wonder if there was\na way to mix unit testing and integration testing to solve this problem.\n\nThese learnings have led us to the interposer - a python package designed to\nallow the engineer to patch a recording and playback system into production\ncode, and then replay the interaction in future runs.  The benefits here are\ntremendous for testing complex external services:\n\n- The complete interaction with the external service is recorded and can be\n  faithfully played back.\n- Ensures future code changes will not break your interactions.\n- Complex operations that require significant time to run during recording\n  have no such delays during playback because it never actually goes out to\n  the external service.\n- Testing real interactions with external services can be done in isolation,\n  without loading the entire project.\n\n## Recording and Playback\n\nInterposer can be used in place of a mock to record and playback interactions.\nUnlike network-based recording and playback libraries, interposer can record\nand playback anything - be it a module, class, or function.\nThere is a simple example in this repository of a Weather object that\nleverages an external service.  Mocking this service would take time, as the\nresponse is fairly complex, but with interposer it's as easy as adding a patch.\n\nRecordedTestCase is a testing class that makes it easy to manage your\nrecordings automatically based on the name of the test module, class, and tests.\nEach test class receives its own recording file, and each test method is recorded\ninto its own channel within the recording file, so it is safe to use in\nparallel testing.  This example test case inserts itself between the Weather\nclass and the `noaa` class that it uses.\n\n```python\n# -*- coding: utf-8 -*-\n#\n# Copyright (C) 2020 Tuono, Inc.\n# Copyright (C) 2021 - 2022 CloudTruth, Inc.\n#\nfrom noaa_sdk import noaa\n\nfrom interposer.example.weather import Weather\nfrom interposer.recorder import recorded\nfrom interposer.recorder import RecordedTestCase\n\n\nclass TestWeather(RecordedTestCase):\n    \"\"\" Example of a record/playback aware test. \"\"\"\n\n    @recorded(patches={\"interposer.example.weather.noaa\": noaa})\n    def test_print_forecast(self) -\u003e None:\n        uut = Weather()\n        assert len(uut.forecast(\"01001\", \"US\", False, 3)) == 3\n```\n\nTo generate a recording (this works if you \"make prerequisites\" first):\n\n```bash\n$ time RECORDING=1 make example\n...\ntests/example_weather_test.py::TestWeather::test_print_forecast\n------------------------------------------------------------------------------------------------- live log call -------------------------------------------------------------------------------------------------\nINFO     interposer.interposer:interposer.py:147 TAPE: Opened /home/testr/interposer/tests/tapes/example_weather_test.TestWeather.test_print_forecast.db for Mode.Recording using version 5\nDEBUG    urllib3.connectionpool:connectionpool.py:943 Starting new HTTPS connection (1): nominatim.openstreetmap.org:443\nDEBUG    urllib3.connectionpool:connectionpool.py:442 https://nominatim.openstreetmap.org:443 \"GET //search?postalcode=11365\u0026country=US\u0026format=json HTTP/1.1\" 200 None\nDEBUG    urllib3.connectionpool:connectionpool.py:943 Starting new HTTPS connection (1): api.weather.gov:443\nDEBUG    urllib3.connectionpool:connectionpool.py:442 https://api.weather.gov:443 \"GET //points/40.73874584464741,-73.79325760300824 HTTP/1.1\" 301 481\nDEBUG    urllib3.connectionpool:connectionpool.py:442 https://api.weather.gov:443 \"GET /points/40.7387,-73.7933 HTTP/1.1\" 200 810\nDEBUG    urllib3.connectionpool:connectionpool.py:943 Starting new HTTPS connection (1): api.weather.gov:443\nDEBUG    urllib3.connectionpool:connectionpool.py:442 https://api.weather.gov:443 \"GET //gridpoints/OKX/39,36/forecast HTTP/1.1\" 200 1428\nDEBUG    interposer.interposer:interposer.py:361 TAPE: Recording RESULT 25c0bc73bd753f18e53c1b803d8d37e2ce8a7d7a.results call #0 for params {'method': 'get_forecasts', 'args': ('11365', 'US', False), 'kwargs': {}, 'channel': 'default'} hash=25c0bc73bd753f18e53c1b803d8d37e2ce8a7d7a type=list: [{'detailedForecast': 'Partly cloudy, with a low around 72. West wind around 8 '\n...\n{'number': 1, 'name': 'Overnight', 'startTime': '2020-09-04T04:00:00-04:00', 'endTime': '2020-09-04T06:00:00-04:00', 'isDaytime': False, 'temperature': 72, 'temperatureUnit': 'F', 'temperatureTrend': None, 'windSpeed': '8 mph', 'windDirection': 'W', 'icon': 'https://api.weather.gov/icons/land/night/sct?size=medium', 'shortForecast': 'Partly Cloudy', 'detailedForecast': 'Partly cloudy, with a low around 72. West wind around 8 mph.'}\n{'number': 2, 'name': 'Friday', 'startTime': '2020-09-04T06:00:00-04:00', 'endTime': '2020-09-04T18:00:00-04:00', 'isDaytime': True, 'temperature': 87, 'temperatureUnit': 'F', 'temperatureTrend': 'falling', 'windSpeed': '8 to 13 mph', 'windDirection': 'W', 'icon': 'https://api.weather.gov/icons/land/day/sct?size=medium', 'shortForecast': 'Mostly Sunny', 'detailedForecast': 'Mostly sunny. High near 87, with temperatures falling to around 84 in the afternoon. West wind 8 to 13 mph.'}\n{'number': 3, 'name': 'Friday Night', 'startTime': '2020-09-04T18:00:00-04:00', 'endTime': '2020-09-05T06:00:00-04:00', 'isDaytime': False, 'temperature': 66, 'temperatureUnit': 'F', 'temperatureTrend': None, 'windSpeed': '8 to 12 mph', 'windDirection': 'NW', 'icon': 'https://api.weather.gov/icons/land/night/few?size=medium', 'shortForecast': 'Mostly Clear', 'detailedForecast': 'Mostly clear, with a low around 66. Northwest wind 8 to 12 mph.'}\nINFO     interposer.interposer:interposer.py:158 TAPE: Closed /home/testr/interposer/tests/tapes/example_weather_test.TestWeather.test_print_forecast.db for Mode.Recording using version 5\nPASSED\n\n=============================================================================================== 1 passed in 6.65s ===============================================================================================\n____________________________________________________________________________________________________ summary ____________________________________________________________________________________________________\n  py37: commands succeeded\n  congratulations :)\n\nreal    0m8.651s\nuser    0m1.911s\nsys     0m0.219s\n```\n\nNote the calls to urllib3 used by the noaa class, and note the amount of time\nthat the test ran.  This command produced a new file:\n\n```bash\n$ ls tests/tapes/example_weather_test\ntests/tapes/example_weather_test:\ntotal 4\n-rw-r--r-- 1 testr testr 1678 Sep 20 07:14 TestWeather.db.gz\n```\n\nNow that the recording is in place, any time the test runs in the future it\nwill avoid actually calling the noaa class, but instead use a recorded\nresponse that matches the method and parameters:\n\n```bash\n$ time make example\n...\ntests/example_weather_test.py::TestWeather::test_print_forecast\n------------------------------------------------------------------------------------------------- live log call -------------------------------------------------------------------------------------------------\nINFO     interposer.interposer:interposer.py:147 TAPE: Opened /home/testr/interposer/tests/tapes/example_weather_test.TestWeather.test_print_forecast.db for Mode.Playback using version 5\nDEBUG    interposer.interposer:interposer.py:313 TAPE: Playing back RESULT for 25c0bc73bd753f18e53c1b803d8d37e2ce8a7d7a.results call #0 for params {'method': 'get_forecasts', 'args': ('11365', 'US', False), 'kwargs': {}, 'channel': 'default'} hash=25c0bc73bd753f18e53c1b803d8d37e2ce8a7d7a type=list: [{'detailedForecast': 'Partly cloudy, with a low around 72. West wind around 8 '\n{'number': 1, 'name': 'Overnight', 'startTime': '2020-09-04T04:00:00-04:00', 'endTime': '2020-09-04T06:00:00-04:00', 'isDaytime': False, 'temperature': 72, 'temperatureUnit': 'F', 'temperatureTrend': None, 'windSpeed': '8 mph', 'windDirection': 'W', 'icon': 'https://api.weather.gov/icons/land/night/sct?size=medium', 'shortForecast': 'Partly Cloudy', 'detailedForecast': 'Partly cloudy, with a low around 72. West wind around 8 mph.'}\n{'number': 2, 'name': 'Friday', 'startTime': '2020-09-04T06:00:00-04:00', 'endTime': '2020-09-04T18:00:00-04:00', 'isDaytime': True, 'temperature': 87, 'temperatureUnit': 'F', 'temperatureTrend': 'falling', 'windSpeed': '8 to 13 mph', 'windDirection': 'W', 'icon': 'https://api.weather.gov/icons/land/day/sct?size=medium', 'shortForecast': 'Mostly Sunny', 'detailedForecast': 'Mostly sunny. High near 87, with temperatures falling to around 84 in the afternoon. West wind 8 to 13 mph.'}\n{'number': 3, 'name': 'Friday Night', 'startTime': '2020-09-04T18:00:00-04:00', 'endTime': '2020-09-05T06:00:00-04:00', 'isDaytime': False, 'temperature': 66, 'temperatureUnit': 'F', 'temperatureTrend': None, 'windSpeed': '8 to 12 mph', 'windDirection': 'NW', 'icon': 'https://api.weather.gov/icons/land/night/few?size=medium', 'shortForecast': 'Mostly Clear', 'detailedForecast': 'Mostly clear, with a low around 66. Northwest wind 8 to 12 mph.'}\nINFO     interposer.interposer:interposer.py:158 TAPE: Closed /home/testr/interposer/tests/tapes/example_weather_test.TestWeather.test_print_forecast.db for Mode.Playback using version 5\nPASSED\n\n=============================================================================================== 1 passed in 0.06s ===============================================================================================\n____________________________________________________________________________________________________ summary ____________________________________________________________________________________________________\n  py37: commands succeeded\n  congratulations :)\n\nreal    0m2.039s\nuser    0m1.822s\nsys     0m0.212s\n```\n\nRecording has advantages and disadvantages, so the right solution\nfor your situation depends on many things.  Recording eliminates\nthe need to produce and maintain mocks.  Mocks of third party\nlibraries that change or are not well understood are fragile and\nlead to a false sense of safety.  Recordings on the other hand\nare always correct, but they need to be regenerated when your\nlogic changes around the third party calls.\n\n## Restrictions\n\n- Return values and exceptions must be safe for pickling.  Some\n  third party APIs use local definitions for exceptions, for example,\n  and local definitions cannot be pickled.  If you get a pickling\n  error, you can insert a CallHandler to run before the TapeDeckCallHandler\n  by specifying `prehandlers` in the @recorded decorator.\n- Randomness between test runs generally defeats recording and playback,\n  however you can record the randomness!\n\n## Dealing with Randomness\n\nIf you have code that uses the uuid package to generate unique IDs,\nand those IDs end up in parameters used by the class being recorded,\nthe same IDs need to be used during playback.  The same issue occurs\nwith time-based identifiers.  The easiest way to get around this is to\nrecord the randomness!\n\n```python\nimport uuid\n\nfrom some.example.project.randomness import Randomness\nfrom interposer.recorder import RecordedTestCase\nfrom interposer.recorder import recorder\n\nclass TestRandomness(RecordedTestCase):\n\n    @recorded(patches={\"some.example.project.randomness.uuid.uuid4\": uuid.uuid4})\n    def test_uuid(self) -\u003e None:\n        uut = Randomness()\n        uut.call_a_method_that_uses_uuids()\n```\n\nIn this fictituous and non-working example (some.example.project is not\nprovided), calls to create uuids would be recorded.\n\n## Call Auditing\n\nUse the Interposer to wrap a module, class, object, method, or function with\na CallHandler that reports all the calls to an auditing service.\n\nTo facilitate auditing and call verification, use Interposer directly in\nyour production code.  Interposer leverages the fantastic\n[wrapt](https://github.com/GrahamDumpleton/wrapt) package to provide\ndoppleganger support, with almost no performance degradation.\n\n## Call Blocking\n\nYou may want to limit the types of methods that can be called in\nthird party libraries as an extra measure of protection in certain\nruntime modes.  Interposer lets you intercept every method called\nin a wrapped class.  You just have to implement a CallHandler and\nthen wrap the module, class, object, method, or function you want to\nraise an exception when a call is not allowed.\n\n## Secrets!\n\nThe recording system has a built-in secrets redacter.  In a test method,\nbefore a secret is used, call `self.redact(secret)`.  If the tape deck is\nin recording mode, the secret is passed to the tape deck for redaction.\nThis means:\n\n1. The real secret is passed to the actual call during recording.\n2. The secret is then replaced by typesafe redaction holistically and reliably\n   in the argument list, and result or exception so the secret can never exist\n   in the recording file.\n3. The recording's call signature is calculated with redacted secrets so that\n   when redacted secrets are used during playback, the calls can be found.\n\nIn playback mode, call `self.redact(secret)` and it will return a redacted\nstring for you to use in place of the secret.  This allows the playback call\nsignatures to match the recorded call signatures.  This means no special\nbranches are needed to handle recording and playback separately.\n\n## Misaligned Playback\n\nIf code or libraries change, the recording may no longer match the call\npatterns.  When you see a `RecordedCallNotFoundError` you should try to\nregenerate your recording.  If this does not work, there is likely a piece\nof information in the recording that is not idempotent, such as a timestamp\nor a uuid.\n\nIf you set the logging level to 7 (more than DEBUG, which is 10), any mismatch\nencountered during playback will be accompanied by a \"diff\" of the recorded\ncall and the requested playback call.  See `make example` for tips on\nhow to do this with pytest.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloudtruth%2Finterposer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcloudtruth%2Finterposer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloudtruth%2Finterposer/lists"}