{"id":17259419,"url":"https://github.com/fbidu/feature-change","last_synced_at":"2025-04-14T06:20:28.993Z","repository":{"id":54804947,"uuid":"287954036","full_name":"fbidu/feature-change","owner":"fbidu","description":"A decorator that helps you run two different versions of a function at the same time and track differences *without* breaking the current behavior!","archived":false,"fork":false,"pushed_at":"2021-01-28T11:20:15.000Z","size":38,"stargazers_count":11,"open_issues_count":3,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-27T20:05:57.348Z","etag":null,"topics":["decorators","python","refactoring","refactoring-tools","testing-tools"],"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/fbidu.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-08-16T13:57:44.000Z","updated_at":"2023-11-08T16:54:09.000Z","dependencies_parsed_at":"2022-08-14T03:20:48.955Z","dependency_job_id":null,"html_url":"https://github.com/fbidu/feature-change","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fbidu%2Ffeature-change","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fbidu%2Ffeature-change/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fbidu%2Ffeature-change/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fbidu%2Ffeature-change/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fbidu","download_url":"https://codeload.github.com/fbidu/feature-change/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248830755,"owners_count":21168347,"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","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":["decorators","python","refactoring","refactoring-tools","testing-tools"],"created_at":"2024-10-15T07:25:49.051Z","updated_at":"2025-04-14T06:20:28.965Z","avatar_url":"https://github.com/fbidu.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Feature Change\n\n``` python\nfrom feature_change import change\n\ndef log_diff(**kwargs):\n    # This will be called if the results of\n    # `new_function` and `current_function` differ\n\ndef log_call(**kwargs):\n    # This will be called on every call to `current_function`\n\ndef new_function():\n    # This is a new function we want to check if\n    # it *really* behaves like the current\n\n@change(new=new_function, on_diff=log_diff, on_call=log_call)\ndef current_function():\n    # The current function. Just decorate it with `change` and everything\n    # will work! No need to change the code!\n\n```\n\n---\n**Table of Contents**\n\n* [Usage](#usage)\n* [Defining the Logging Functions](#defining-the-logging-functions)\n* [Working Example](#working-example)\n* [Warnings](#warnings)\n* [Other Libs](#other-libs)\n---\n\nA Python decorator that helps you run two different versions of a function at\nthe same time and track differences _without_ breaking the current behavior.\n\nIt is based on Auth0's [feature-change](https://github.com/dschenkelman/feature-change)\nfor Node.js. [Original article](https://auth0.com/blog/feature-changes-at-auth0/)\n\n## Usage\n\n1. **Install** with pip or your favorite package manager `pip install feature-change`\n\n    a. If you can't or don't want to add a new dependency, feel free to just\n       copy the current code from `change.py`. It is pretty straightforward.\n\n2. **Define the logging functions** you want to be called. They will receive two keyword\n   arguments `current` and `new`, with the current and the new result.\n\n    ```python\n    def log_diff(current, new):\n        print(f\"The current value is {current} but the new one is {new})\n    ```\n\n3. **Decorate** your current function with `@change`, passing the new implementation\n   that you want to test and any logging function you have defined. Currently\n   you can define logging on two occasions ― `on_diff` will be called if the\n   results are different and `on_call` will always be called\n\n    ```python\n    @change(new=new_func, on_diff=log_diff)\n    def current_function():\n        ...\n    ```\n\n## Defining the Logging Functions\n\nThe functions used for logging will receive two keyword arguments ― `current`\nand `new`. They will contain the return of both the current and the new functions.\n\n`change` can call custom functions on two situations:\n\n* `on_call` ― everytime the old function is called\n\n* `on_diff` ― when there's a difference between the return of the current function\n  and the new one.\n\n## Working Example\n\n```python\nfrom random import random\nfrom feature_change import change\n\ndef log_call(current, new):\n    print(\"Call detected!\")\n\ndef log_diff(current, new):\n    print(f\"Difference detected. Current = {current}; new = {new}\")\n\ndef new_sum(a, b):\n    \"\"\"\n    This function will be wrong on 50% of the calls\n    \"\"\"\n    if random() \u003e 0.5:\n        return 0\n\n    return a + b\n\n@change(new=new_sum, on_call=log_call, on_diff=log_diff)\ndef current_sum(a, b):\n    return a + b\n\nfor _ in range(10):\n    current_sum(1, 41)\n```\n\nThis code will return something like\n\n```text\nCall detected!\nDifference detected. Current = 42; new = 0\nCall detected!\nCall detected!\nCall detected!\nDifference detected. Current = 42; new = 0\nCall detected!\nCall detected!\nCall detected!\nCall detected!\nDifference detected. Current = 42; new = 0\nCall detected!\nCall detected!\nCall detected!\nDifference detected. Current = 42; new = 0\nCall detected!\nCall detected!\nCall detected!\nCall detected!\nCall detected!\nDifference detected. Current = 42; new = 0\nCall detected!\nDifference detected. Current = 42; new = 0\nCall detected!\nDifference detected. Current = 42; new = 0\nCall detected!\nCall detected!\n```\n\nKeep in mind that the result may be different in your machine because the\nnew function fails randomly.\n\n## Warnings\n\n1. **Both functions will be called** ― be aware of side effects like database\n   writes. If both functions write to a database, they will both be executed,\n   leading to possible inconsistencies.\n\n2. **Be aware of slow logs** ― the callables you define for `on_diff` and `on_call`\n   should not be very expensive. You could, by example, just count how many times\n   `on_diff` was called using some fast db like redis.\n\n3. **Equality is simple** ― currently the results are checked using simply the\n   `==` operator. Keep that in mind if your results are complex objects whose\n   equality is not well defined. An optional argument for a custom equality\n   checker will be add on the future.\n\n## Other Libs\n\nAuth0's `feature-change` is based on GitHub's [scientist](https://github.com/github/scientist).\n\n`scientist` itself has a lot more features than this lib and it\nalso has implementations on [different languages](https://github.com/github/scientist#alternatives)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffbidu%2Ffeature-change","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffbidu%2Ffeature-change","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffbidu%2Ffeature-change/lists"}